Skip to content
PressboardKit
InstallationBrowse docs

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:

  1. 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.
  2. Set the deployment target to iOS 26 on both targets.
  3. Set RequestsOpenAccess in the extension’s Info.plist only if you need haptics. The key click sound works without Full Access, because the engine plays the system sound directly through AudioServicesPlaySystemSound. Note that the Simulator does not play those clicks at all — test key sound on a device.
  4. Make the views clear if you want the iOS 26 translucent backdrop to show through. The engine draws its own background clear when translucentBackground is on, but anything opaque behind it puts the rectangle back. PressboardInputViewController does 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.

  1. Add the App Groups capability to both targets and use the same group identifier.
  2. Persist KeyboardBehavior — it is Codable — and the enabled locales in that suite.
  3. Pass the load and save hooks to PressboardConfiguration. The controller reloads on viewWillAppear and 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
)

Edit this page on GitHub