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 with five library products. Link the ones your keyboard extension target actually uses — an extension’s memory budget is the reason they are separate.
Add the package
In Xcode, File ▸ Add Package Dependencies…, or in a Package.swift:
.package(url: "https://github.com/PressboardKit/PressboardKit.git", from: "0.1.0")
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"),
]
)
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
)