azooKey
azooKey copied to clipboard
[Documentation] Get more detailed information of `markedText` via textDocumentProxy
trafficstars
プライベートなAPIであること、特に現状必要がないことから使うことはなさそう。
// このcomputed propertyをKeyboardViewControllerに追加
@objc var markedText: NSString {
fatalError("Do not directly call this getter")
}
// trueになる
debug("markedText", self.textDocumentProxy.responds(to: #selector(getter: markedText)))
let result = self.textDocumentProxy.perform(#selector(getter: markedText))
if let result {
// marked textがある場合は値が返ってくる
debug("markedText", result)
}
これは使えるかもしれない!?!?!?
private func getMarkedTextDetailedInformation() {
// textDocumentProxyに対して`_controllerState`を得るようselectorでリクエストすると、UIInputViewControllerStateのインスタンスが得られる
// このインスタンスのdescriptionをとると、`documentState = <TIDocumentState: 0x2812794c0; text = "なしあ|{{}ああ""|">;`のようなテキストが含まれる。
// このテキストは周辺の行の状態を表しており、入力管理に利用できる
// なお、同様にしてdocumentStateを取得することもできるが、クラッシュを招く。これは排他アクセスが失敗するからと見られるが、詳しい理由は不明。
// 「text」は扱いづらい仕様であるが、うまく利用すれば適切にmarkedTextの状態をトラッキングするのに役立つ
// 1. SelectedTextは[]で囲まれる
// 2. MarkedTextの範囲は{}で囲まれる
// 3. Cursorの位置は|で示される
// 4. これらの特殊記号は特にエスケープされないので、重複しうる。
let controllerStateSelector = sel_registerName("_controllerState")
let isValidSelector = self.textDocumentProxy.responds(to: controllerStateSelector)
guard isValidSelector else {
debug("getDocumentState", controllerStateSelector, "is not a valid selector")
return
}
let _uiInputViewControllerState = self.textDocumentProxy.perform(controllerStateSelector)
debug("getDocumentState", _uiInputViewControllerState.debugDescription)
let regex = #"<TIDocumentState:.*text = "(.*)">"#
let captureRegex = try! NSRegularExpression(
pattern: regex,
options: []
)
let description = _uiInputViewControllerState.debugDescription
let matches = captureRegex.matches(
in: description,
options: [],
range: NSRange(description.startIndex ..< description.endIndex, in: description)
)
if let match = matches.first {
debug("getDocumentState", match)
// 0番目はmatch全体、captureは1番目に当たる。
let matchRange = match.range(at: 1)
debug("getDocumentState", matchRange)
// Extract the substring matching the capture group
if let substringRange = Range(matchRange, in: description) {
let capture = String(description[substringRange])
debug("getDocumentState", capture)
}
}
}
これが通るなら「入力中のテキストを保護」をデフォルト挙動にできちゃうぞ……
情報のフォーマットがフォーマットなので綺麗にはできないけど、多分こんな感じでできそう。エッジケースの多い実装になると思うからフェイルセーフを徹底する。
- textの中に
{...}というフォーマットの部分を探す- ない場合→Marked Textなし(return)
- 1つある場合→そこがMarked Text
- 2つ以上ある場合→絞り込みを実施
|をreplaceして消去し、DisplayedTextCotroller側が持っているdisplayedTextの|をreplaceして消去したものと一致するものを選ぶ。ただし|がそもそも含まれていない場合は除外する。- ない場合→諦め
- 1つある場合→そこがMarked Text
- 2つ以上ある場合→諦め
- Marked Textの中で
|の位置を探す- ない場合→謎。諦め
- 1つある場合→そこがカーソルの位置
- 2つ以上ある場合→前にあるものから見ていき、displayedTextと比較して異なる位置にあるものを選ぶ。そこがカーソルの位置
ただし依然通知は来ないので、数十ミリ秒おきにこの値を確認するタイマーをかける必要がある。多分0.2秒に一回とかで十分。
なので、どうしようもないケースとしてはたとえば以下のような例がある。
{a|aa} {aaa|}(後者はMarkedText、前者はただのテキスト)
これは十分レアだろうと推測できる。