
Wally 5.3 lets you pick a custom font for navigation titles and decorative texts. You can pick between two hand-drawn fonts, a font for dyslexia, and all system fonts.
This version also adds a new denim skin and a brand new corduroy skin collection with four skins.
You can read more about this update on the Wally website
]]>
Emoji Picker 1.4 adds a new, adorable icon/mascot. Picky fits right into your dock and home screen, and brightens your day with a smile. The app also improves performance, and keyboard navigation.
The KeyboardKit app improves the theme editor. You can now long press any color picker to copy and paste colors. The app also fixes some theme bugs.
One Touch Paste 1.6 improves the keyboard performance, adds support for many new languages (73 in total), and adds support for Emoji 16. English autocorrection is also drastically improved.
Wally 5.2 lets you scan things to quickly add them to your wallet. Just snap a photo of your card, ID, etc. and Wally will automatically extract it from the photo. The app also adds a quick-edit mode.
These app updates are nice improvements that polish the overall in-app experience. Download the apps from the the Kankoda App Store page and let us know what you think.
]]>
Vietnamese Input has been extracted from KeyboardKit Pro’s Vietnamese typing support, and has improved the input logic a great deal.
The SDK has an input engine that lets you type a sequence of characters into a string, using any of the three main input methods - TELEX, VNI & VIQR - without switching to a Vietnamese keyboard.
Vietnamese Input works on iOS, iPadOS, macOS, tvOS, visionOS, and watchOS, and can be used in both apps and SDKs.
Vietnamese Input requires a commercial license to be used. We’re currently working on getting it up on Paddle. Until this is done, you can reach out to sign up for a license with regular invoicing.
]]>
LicenseKit is a Swift SDK that lets you protect your software with commercial licenses. You can use it with boths apps and libraries, to require users to purchase a license in order to use your software.
LicenseKit lets you define licenses in code, read licenses from plain or encrypted files, fetch licenses from any custom APIs, and integrate with services like Gumroad, Paddle and Lemon Squeezy.
LicenseKit can validate expiration date, platform, bundle ID, tier, environment, features, etc. It also lets you handle temporary connectivity loss, and combine many data sources for flexible validation.
LicenseKit is free to start using, using the limited “FREE” license key, and affordable to scale. You can then purchase a license or try out a free, unlimited trial from the LicenseKit website.
With LicenseKit, first create a LicenseEngine with the license key that you get when you sign up for LicenseKit, and define which LicenseServiceType you want to use to use to fetch customer licenses.
For instance, this would create a license engine with two licenses that are defined with source code and that will be validated on-device:
let licenseEngine = try await LicenseEngine(
licenseKey: "your-license-key",
licenseService: { .binary(
licenses: [
License(licenseKey: "license-key-1", ...),
License(licenseKey: "license-key-2", ...)
]
)}
)
There are many service types to choose from, as described in the documentation. You can define a license collection with source code, read licenses from plain or encrypted files, fetch licenses from a custom API, integrate with services like Gumroad, Paddle and Lemon Squeezy, etc.
Once you have an engine, you can use its getLicense(withKey:) function to get & validate customer licenses by providing a way for your customers to enter their license key.
An app can have a UI for this, while a library can have a well-defined, central setup/unlock function:
// Place this kind of function in your library, where it makes sense.
// You don't need a license key when you use encrypted license files.
public static func unlock(
withLicenseKey key: String
) async throws {
let license = try await engine.getLicense(for: "license-key-1")
// Perform further license validations, if needed.
// Set up your app/SDK with the license, if it's valid.
// Store the license for the current session, etc.
}
The engine will fetch licenses with the service(s) you defined, then validate any matching license for the current platform, bundle, and date. You can perform more validations after getting the license.
The LicenseEngine will automatically validate any fetched license for the current platform, product bundle and date, and will throw a License.ValidationError if a license isn’t valid for the app/library.
This means that any license that the engine returns to you is valid for the current app/library. You can then perform additional validations to ensure that a license meets any additional requirements.
For instance, you can define a custom LicenseFeature and make sure that a license can access it:
enum MyLibraryFeature: String, LicenseFeature {
// Define various features that your app/library provides.
case maps, weather, ...
// A feature ID should be returned for all features.
// It's optional since some enums may not handle all cases as features.
var featureId: String? { rawValue }
// You can validate a feature either directly, or implicitly by tier.
var unlockedByTier: License.Tier? {
switch self {
case .maps: return .silver
case .weather: return .gold
}
}
}
You can then validate these features when creating certain types, and throw an error if the current license doesn’t have the right access to that specific feature:
public class MyBasicFeature {
// Validate that an optional license is set and is valid.
public init() throws {
try License.validate(license)
}
}
public class MyMapsFeature {
// Validate that an optional license is set and is valid,
// and also gives access to the required feature.
public init() throws {
try License.validate(license) {
try $0.validateFeature(MyLibraryFeature.maps)
}
}
}
This means that you can make it impossible to create certain types in your library, or access certain features, if a license doesn’t meet certain, custom conditions.
Most apps and libraries will have a single user at a time, per running instance. For these cases, you can use a LicenseStore to store the current valid license in a thread-safe way.
For instance, consider that you have a single license store singleton for your entire app or library:
extension LicenseStore {
static var myLicenseStore = LicenseStore()
}
You can then use its storeLicense(_:) function to store a license you receive with a license engine, or inject the store into the LicenseEngine to make the engine auto-persist the last fetched license:
let myLicenseEngine = try await LicenseEngine(
licenseKey: "your-license-key",
licenseStore: .myLicenseStore,
licenseService: { .binary(
licenses: [
License(licenseKey: "license-key-1", ...),
License(licenseKey: "license-key-2", ...)
]
)
}
)
After performing a successful license retrieval, the engine will automatically persist the fetched and validated license. You can then use License.validate(myLicenseStore.license, ...) with that license, to both validate that a valid license exists, and to perform additional validations.
To simplify things, you can create a License extension to access the current license, for instance:
public extension License {
static var current: Self? {
LicenseStore.myLicenseStore.license
}
}
You can then use License.validate(.current, ...) without having to refer to the store in your code.
Important! Make sure to keep your license store and engine internal, to avoid that your customers are able to access them, which could make it possible to inject fake licenses into the store.
You will have to set up your license engine and validation a bit differently when using LicenseKit in an app vs. when using it in a library.
An app can create a LicenseEngine on launch, or whenever needed. You can then have a UI where users can enter their license key to unlock more features in the app.
Licenses can be used as an better alternative to in-app purchases or subscriptions, as long as you also offer any of those options. Otherwise, Apple may reject your app.
LicenseKit will provide pre-made license unlock screens in a future update of the library, to make it easier to add license unlocks to any app. Reach out if this is important to you, and we’ll prioritize it.
A library should provide a way for developers to set up the library with their license key, or with an encrypted license file that is added to the main bundle.
Have a look at the demo app and demo library in the LicenseKit GitHub repository for examples on how to do set up LicenseKit for an app and libray.
LicenseKit doesn’t put any restrictions on how you use the LicenseEngine or LicenseStore, but you can use these demos for inspiration.
See the online documentation’s license article for more information about the LicenseKit license model, and the service article for more information about the available license services types.
]]>
Wally 5.1 cleans up and simplifies the app design. It aims to keep the classic, skeumorphic design while also making the app cleaner and easier to use.
The new design provides more screen estate, to give your things room to breathe. It also removes most of the onboarding hints, and replaces them with new, easily dismissable panels.
Item types have been adjusted to no longer have any “premium” item types. As such, all types are shown in the menu. You can hide types you don’t use in Settings.
Wally 5.1 cleans up the item screen and makes it easier to manage the information of your things.
You can now edit the front and back image of any already added item, add more information to your things and easily export and delete items directly from the item screen.
Wally 5.1 adds support for IDs, travel documents, and tickets. You can now add identity cards and driver’s licenses that don’t have a card aspect ratio, and add travel and event tickets to the app.
Wally 5.1 is much cleaner than earlier versions. It streamlines the app, adds support for more item types, and lets you add more information to your things.
You can download Wally 5.1 from the App Store. We hope that you will love these changes, and hope that you reach out with any suggestions you may have.
]]>
The Kankoda Newsletter is managed through our Gumroad account and is used to send periodic updates about our products.
While this site isn’t always updated with the latest information on products that have their own sites and social media accounts, like KeyboardKit, the newsletter is a holistic source of news.
So sign up today, and we’ll update you when something significant happens to us and our products.
]]>
This will affect the EmojiKit GitHub project, which has been transferred to the founder’s personal GitHub account. It will from now on be listed together with his other open-source projects.
This also affects the Gumroad product, which has been deleted, as well as all EmojiKit-related pages on this website.
]]>
The affected products will be converted to product pages on this website and product news will be posted on Kankoda’s X and Mastodon accounts and on the Kankoda blog.
The GitHub repos for affected SDK products will also be moved into the Kankoda organization.
This change applies to LicenseKit, which means that the old LicenseKit website is now a product page on this website, and the GitHub account is now found here.
This change will for now not apply to KeyboardKit, Appamini or Wally, which for now keeps their own sites and accounts.
]]>
EmojiKit was just released as a first 0.1 beta version. There are still work being done on the logo, the header, demo apps, etc. but you can find out more information about it on the product site.
]]>
KeyboardKit 8 cleans up the library to make it better structured and easier to use. It reorganizes types into namespaces and removes low-value utilities, resulting in a cleaner API.
This is the biggest upgrade to KeyboardKit yet. You will most likely be affected by some of the many changes, but the SDK should help you update with minimum hassle.
See the release article for a more information.
We hope that you will love using KeyboardKit 8!
]]>