2

UITextField の「return」キーを使用してカスタム文字を挿入しようとしています。私の UITextFieldDelegate メソッドは次のようになります。

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    [textField insertText:@"¶"];
    return NO;
}

残念ながら、これは時々しか機能しません:

  • 「ワンツー|」-->カーソルを移動--> "one| two" -->戻る--> "one¶| two" ( OK )
  • "ワンツー|" --> return --> "onetwo¶|" ( OK )
  • "ワンツー|" -->カーソル移動--> "one|two" -->戻る--> "onetwo¶|" (失敗)

最後のケースでは、"one¶|two" を期待していたでしょう。

挿入されたテキストが常にカーソル位置に挿入されるようにするにはどうすればよいですか?

ありがとう。

4

2 に答える 2

3

問題は、キーボードのリターンキーをタップすると、テキストフィールドがメッセージを送信する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:inputDelegateUIKeyboardImplinputDelegate

  • テキストフィールドの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シミュレーターでのテストで機能します。

于 2012-11-21T22:29:18.110 に答える
0

ここで起こっているのは、選択範囲とも呼ばれる挿入ポイントを追跡していないということです。

そのためには、UITextField でできることの本質をもう少し深く理解する必要があります。

UITextInput (UITextField が使用するプロトコルとしてアクセス可能) を使用すると、キャレット (カーソル、挿入ポイント) がどこにあり、そこに特殊文字を挿入する必要がある場所を示す" selectedTextRange" プロパティを取得できます。これUITextInput、オブジェクトを " " プロトコルに準拠するデリゲートに設定した場合に機能します。

于 2012-11-21T21:22:57.917 に答える