Getting started
Installation
Adding the package, choosing which modules to link, setting up the keyboard extension target, and sharing settings through an App Group.
PressboardKit is distributed as a Swift Package of binary frameworks — one XCFramework per module, verified by the checksums in the package manifest. Link the ones your keyboard extension actually uses: an extension’s memory budget is the reason they are separate, and the autocomplete module alone carries about 15 MB of word lists.
Add the package
In Xcode, File ▸ Add Package Dependencies…, or in a Package.swift:
.package(url: "https://github.com/PressboardKit/PressboardKit.git", from: "1.0.0")
Nothing else is needed to make the frameworks appear: Xcode processes and embeds them for the application target. If your keyboard extension fails to link with undefined symbols, the usual cause is that only the extension declares the dependency — add the same products to the app target as well.
One consequence of binary distribution is worth knowing before the compiler tells you: the
frameworks are built with library evolution, so the engine’s public enums are not frozen. A
switch over one — LicenceStatus, KeyboardAction — needs an @unknown default: branch. That
is not a nuisance, it is the reason a build of yours from today keeps linking against a version of
ours that adds a case tomorrow.
Choose your modules
Most integrations want one import. PressboardKitApp assembles the whole keyboard — controller,
root view, text editing, sizing, input-trait adaptation — and depends on the other four modules,
so linking it gives you everything:
.target(
name: "MyKeyboardExtension",
dependencies: [
.product(name: "PressboardKitApp", package: "PressboardKit"),
]
)
PressboardKitLicensing is the one module that belongs to your app target rather than the
extension — it is what fetches your licence (see Licensing), and keeping it
out of the keyboard is how the extension stays free of code that can open a connection.
If you are assembling the keyboard yourself rather than subclassing
PressboardInputViewController, link the core and the layouts, and add the optional modules only
if you use them:
.target(
name: "MyKeyboardExtension",
dependencies: [
.product(name: "PressboardKit", package: "PressboardKit"),
.product(name: "PressboardKitLayouts", package: "PressboardKit"),
.product(name: "PressboardKitAutocomplete", package: "PressboardKit"), // optional
.product(name: "PressboardKitEmoji", package: "PressboardKit"), // optional
]
)
A keyboard extension runs on a small fraction of an app’s memory, and iOS kills it rather than warning it when it overruns. Autocomplete and emoji are their own products precisely so you can leave them out. See Performance for what else stays inside that budget.
The keyboard extension target
Add a Custom Keyboard Extension target to your app, then:
- Link the package products to the extension target, not just to the containing app. An extension is its own process and gets its own copy of everything it links.
- Set the deployment target to iOS 26 on both targets.
- Set
RequestsOpenAccessin the extension’sInfo.plistonly if you need haptics. The key click sound works without Full Access, because the engine plays the system sound directly throughAudioServicesPlaySystemSound. Note that the Simulator does not play those clicks at all — test key sound on a device. - Make the views clear if you want the iOS 26 translucent backdrop to show through. The
engine draws its own background clear when
translucentBackgroundis on, but anything opaque behind it puts the rectangle back.PressboardInputViewControllerdoes this for you; a hand-built controller must do it itself. See Theming.
Your extension class is then a subclass with one overridden method:
import PressboardKitApp
final class KeyboardViewController: PressboardInputViewController {
override func makeConfiguration() -> PressboardConfiguration {
PressboardConfiguration(locales: [.english])
}
}
That is the whole extension. The quick start fills it out.
App Group setup
The containing app and the extension are separate processes with separate UserDefaults. To let
a settings screen in your app change the keyboard, put the shared state in an App Group.
- Add the App Groups capability to both targets and use the same group identifier.
- Persist
KeyboardBehavior— it isCodable— and the enabled locales in that suite. - Pass the load and save hooks to
PressboardConfiguration. The controller reloads onviewWillAppearand saves on every change.
import Foundation
import PressboardKit
import PressboardKitApp
enum BehaviorStore {
static let appGroup = "group.com.yourcompany.app"
static var defaults: UserDefaults { UserDefaults(suiteName: appGroup) ?? .standard }
private static let behaviorKey = "keyboardBehavior"
private static let localesKey = "keyboardLocales"
static func save(_ behavior: KeyboardBehavior) {
guard let data = try? JSONEncoder().encode(behavior) else { return }
defaults.set(data, forKey: behaviorKey)
}
static func load() -> KeyboardBehavior {
guard let data = defaults.data(forKey: behaviorKey),
let behavior = try? JSONDecoder().decode(KeyboardBehavior.self, from: data)
else { return .pressboardDefault }
return behavior
}
static func saveLocales(_ locales: [KeyboardLocale]) {
defaults.set(locales.map(\.rawValue).joined(separator: ","), forKey: localesKey)
}
static func loadLocales() -> [KeyboardLocale] {
let list = (defaults.string(forKey: localesKey) ?? "")
.split(separator: ",")
.compactMap { KeyboardLocale(rawValue: String($0)) }
return list.isEmpty ? [.english] : list
}
}
KeyboardBehavior decodes forward-compatibly: every key is optional and falls back to its
default. Adding a parameter in a later release does not reset the choices your users already
made.
The same suite is where the emoji recents and the learned word pairs belong:
PressboardConfiguration(
behavior: BehaviorStore.load(),
locales: BehaviorStore.loadLocales(),
emojiDefaults: BehaviorStore.defaults,
loadBehavior: BehaviorStore.load,
saveBehavior: BehaviorStore.save,
loadLocales: BehaviorStore.loadLocales,
saveLocales: BehaviorStore.saveLocales
)