問題は、キーボードのリターンキーをタップすると、テキストフィールドがメッセージを送信する前textFieldShouldReturn:
に、選択した範囲(カーソル位置)をテキストの最後に設定することです。
以前の位置に戻すことができるように、カーソル位置を追跡する必要があります。プロパティのテキストフィールドへの参照があるとします。
@interface ViewController () <UITextFieldDelegate>
@property (strong, nonatomic) IBOutlet UITextField *textField;
@end
以前に選択したテキスト範囲(リターンキーがタップされる前から)を保持するためのインスタンス変数が必要です。
@implementation ViewController {
UITextRange *priorSelectedTextRange_;
}
次に、選択したテキスト範囲をインスタンス変数に保存するメソッドを記述できます。
- (void)saveTextFieldSelectedTextRange {
priorSelectedTextRange_ = self.textField.selectedTextRange;
}
でtextFieldShouldReturn:
、段落記号を挿入する前に、選択したテキスト範囲を以前の値に戻すことができます。
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
textField.selectedTextRange = priorSelectedTextRange_;
[textField insertText:@"¶"];
return NO;
}
しかし、saveTextFieldSelectedTextRange
必要なときにシステムにメッセージを送信させるにはどうすればよいでしょうか。
プロトコルには、選択した範囲への変更に関するUITextFieldDelegate
メッセージはありません。
UITextField
選択した範囲への変更に関する通知は投稿されません。
UITextInputDelegate
プロトコルにはメッセージがありますが、テキストフィールドの編集が開始されると、システムはテキストフィールドselectionWillChange:
を独自のオブジェクトに設定するため、を使用することはできません。selectionDidChange:
inputDelegate
UIKeyboardImpl
inputDelegate
テキストフィールドのselectedTextRange
プロパティを監視するKey-Valueは信頼できません。iOS 6.0シミュレーターでのテストでは、テキストフィールドをタップしてカーソルをテキストの中央から最後に移動しても、KVOメッセージが表示されません。
テキストフィールドの選択された範囲への変更を確実に追跡するために私が考えることができる唯一の方法は、実行ループにオブザーバーを追加することです。イベントループを通過するたびに、オブザーバーはイベント処理の前に実行されるため、現在選択されている範囲が変更される前に取得できます。
したがって、実行ループオブザーバーへの参照を保持するために、実際には別のインスタンス変数が必要です。
@implementation ViewController {
UITextRange *priorSelectedTextRange_;
CFRunLoopObserverRef runLoopObserver_;
}
オブザーバーを作成しますviewDidLoad
:
- (void)viewDidLoad {
[super viewDidLoad];
[self createRunLoopObserver];
}
viewDidUnload
そして、私たちは両方とでそれを破壊しdealloc
ます:
- (void)viewDidUnload {
[super viewDidUnload];
[self destroyRunLoopObserver];
}
- (void)dealloc {
[self destroyRunLoopObserver];
}
オブザーバーを作成するには、それを呼び出すための単純な古いC関数が必要です。その機能は次のとおりです。
static void runLoopObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
__unsafe_unretained ViewController *self = (__bridge ViewController *)info;
[self saveTextFieldSelectedTextRange];
}
これで、実際にオブザーバーを作成して、メインの実行ループに登録できます。
- (void)createRunLoopObserver {
runLoopObserver_ = CFRunLoopObserverCreate(NULL, kCFRunLoopAfterWaiting, YES, 0, &runLoopObserverCallback, &(CFRunLoopObserverContext){
.version = 0,
.info = (__bridge void *)self,
.retain = CFRetain,
.release = CFRelease,
.copyDescription = CFCopyDescription
});
CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver_, kCFRunLoopCommonModes);
}
実際にオブザーバーの登録を解除して破棄する方法は次のとおりです。
- (void)destroyRunLoopObserver {
if (runLoopObserver_) {
CFRunLoopRemoveObserver(CFRunLoopGetMain(), runLoopObserver_, kCFRunLoopCommonModes);
CFRelease(runLoopObserver_);
runLoopObserver_ = NULL;
}
}
このアプローチは、iOS6.0シミュレーターでのテストで機能します。