Skip to content

Instantly share code, notes, and snippets.

@wearhere
Last active April 11, 2024 19:47
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wearhere/f46ab9d837acaeaabfa86a813c44ad25 to your computer and use it in GitHub Desktop.
Save wearhere/f46ab9d837acaeaabfa86a813c44ad25 to your computer and use it in GitHub Desktop.
A generic implementation of the `UITextDocumentProxy` protocol that should work for anything that conforms to `UIResponder` and `UITextInput`. Useful to put text fields inside custom keyboards and then reuse your keyboard's regular handling logic with this text field. See https://github.com/danielsaidi/KeyboardKit/issues/45 for more info.
//
// documentProxy.swift
// KeyboardKitDemoKeyboard
//
// Created by Jeffrey Wear on 4/28/20.
//
import UIKit
class TextDocumentProxy<TextDocument: UIResponder & UITextInput>: NSObject, UITextDocumentProxy {
init(document: TextDocument) {
self.document = document
super.init()
}
private unowned let document: TextDocument
// MARK: - UITextDocumentProxy
var documentInputMode: UITextInputMode? {
document.textInputMode
}
var documentContextAfterInput: String? {
guard let selectedTextRange = document.selectedTextRange else {
return nil
}
guard let rangeAfterInput = document.textRange(from: selectedTextRange.end, to: document.endOfDocument) else {
return nil
}
return document.text(in: rangeAfterInput)
}
var documentContextBeforeInput: String? {
guard let selectedTextRange = document.selectedTextRange else {
return nil
}
guard let rangeBeforeInput = document.textRange(from: document.beginningOfDocument, to: selectedTextRange.start) else {
return nil
}
return document.text(in: rangeBeforeInput)
}
// https://stackoverflow.com/a/41023439/495611 suggests adjusting the text
// position (i.e. moving the cursor) by adjusting the selected text range.
func adjustTextPosition(byCharacterOffset offset: Int) {
guard let selectedTextRange = document.selectedTextRange else { return }
// Not sure what's supposed to happen if the range is non-empty. Let's
// abort if it is.
guard selectedTextRange.isEmpty else { return }
// Now that it's empty, the start and end should be the same. Move that position.
// The guard is a bounds check.
guard let newPosition = document.position(from: selectedTextRange.start, offset: offset) else { return }
document.selectedTextRange = document.textRange(from: newPosition, to: newPosition)
}
var selectedText: String? {
guard let selectedTextRange = document.selectedTextRange else {
return nil
}
return document.text(in: selectedTextRange)
}
let documentIdentifier: UUID = UUID()
func setMarkedText(_ markedText: String, selectedRange: NSRange) {
document.setMarkedText(markedText, selectedRange: selectedRange)
}
func unmarkText() {
document.unmarkText()
}
// MARK: - UIKeyInput
func insertText(_ text: String) {
document.insertText(text)
}
func deleteBackward() {
document.deleteBackward()
}
var hasText: Bool {
document.hasText
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment