Last active
March 17, 2025 21:57
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 | |
} | |
} | |
Oh I see, thank you anyway! I managed to hack around this limitation, so it's working now :)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I haven't used this in awhile now, sorry. (Stopped working on my custom keyboard when iOS added its own emoji search bar.)