web-dev-qa-db-ja.com

iOS 7.1 UITextViewが、改行後もカーソル/キャレットにスクロールしない

IOS 7以降、ユーザーが新しい行にフローするテキストを入力しても、UITextViewは自動的にカーソルまでスクロールしません。この問題は、SOやその他の場所で詳細に文書化されています。私にとって、問題はiOS 7.1にもまだ存在しています。何が問題なのですか?

enter image description here

Xcode 5.1をインストールし、iOS 7.1をターゲットにしました。自動レイアウトを使用しています。

テキストビューのコンテンツをキーボードの上に配置する方法は次のとおりです。

- (void)keyboardUp:(NSNotification *)notification
{
    NSDictionary *info = [notification userInfo];
    CGRect keyboardRect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    keyboardRect = [self.view convertRect:keyboardRect fromView:nil];

    UIEdgeInsets contentInset = self.textView.contentInset;
    contentInset.bottom = keyboardRect.size.height;
    self.textView.contentInset = contentInset;
}

私が試したもの:iOSに関連するため、この問題についてSOに投稿されたソリューションの多くを試してみました7.私が試したすべての解決策は、属性付き文字列を表示するテキストビューではうまく機能しないようです。次の3つの手順では、 SO( https://stackoverflow.com/a/19277383/123926 )で最も投票された回答がユーザーのReturnキーのタップにどのように応答するかを概説する初めて。

(1.)viewDidLoadでテキストビューが最初のレスポンダーになりました。カーソルが置かれているテキストビューの下部までスクロールします。

enter image description here

(2.)単一の文字を入力する前に、キーボードのReturnキーをタップします。キャレットが見えなくなります。

enter image description here

(3.)ただし、Returnキーをもう一度タップすると、状況が正常化するようです。 (ただし、後者の新しい行を削除すると、キャレットが再び消えます)。

enter image description here

25
bilobatum

UITextView下位クラスのソリューションのコードを改善しました:

#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define is_iOS7 SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")
#define is_iOS8 SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")

@implementation MyTextView {
    BOOL settingText;
}

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleTextViewDidChangeNotification:) name:UITextViewTextDidChangeNotification object:self];
    }
    return self;
}

- (void)scrollToCaretInTextView:(UITextView *)textView animated:(BOOL)animated {
    CGRect rect = [textView caretRectForPosition:textView.selectedTextRange.end];
    rect.size.height += textView.textContainerInset.bottom;
    [textView scrollRectToVisible:rect animated:animated];
}

- (void)handleTextViewDidChangeNotification:(NSNotification *)notification {
    if (notification.object == self && is_iOS7 && !is_iOS8 && !settingText) {
        UITextView *textView = self;
        if ([textView.text hasSuffix:@"\n"]) {
            [CATransaction setCompletionBlock:^{
                [self scrollToCaretInTextView:textView animated:NO];
            }];
        } else {
            [self scrollToCaretInTextView:textView animated:NO];
        }
    }
}

- (void)setText:(NSString *)text {
    settingText = YES;
    [super setText:text];
    settingText = NO;
}

Bluetoothキーボードで下キーを押しても機能しないことに注意してください。

11
Dmitry

堅牢なソリューションは、次の状況に耐える必要があります。

(1.)属性付き文字列を表示するテキストビュー

(2.)キーボードのReturnキーをタップして作成された新しい行

(3.)次の行にオーバーフローするテキストを入力して作成された新しい行

(4.)テキストのコピーと貼り付け

(5.)初めてReturnキーをタップして作成された新しい行(OPの3つのステップを参照)

(6.)デバイスの回転

(7.)あなたがそうすることを私が考えることができない場合...

IOS 7.1でこれらの要件を満たすには、キャレットまで手動でスクロールする必要があるようです。

テキストビューのデリゲートメソッドtextViewDidChange:が呼び出されたときに、キャレットまで手動でスクロールするソリューションを確認するのが一般的です。ただし、この手法では上記の状況#5を満たさないことがわかりました。キャレットにスクロールする前にlayoutIfNeededを呼び出しても役に立ちませんでした。代わりに、CATransaction完了ブロック内のキャレットまでスクロールする必要がありました。

// this seems to satisfy all of the requirements listed above–if you are targeting iOS 7.1
- (void)textViewDidChange:(UITextView *)textView
{
    if ([textView.text hasSuffix:@"\n"]) {

        [CATransaction setCompletionBlock:^{
            [self scrollToCaretInTextView:textView animated:NO];
        }];

    } else {
        [self scrollToCaretInTextView:textView animated:NO];
    }
}

なぜこれが機能するのですか?何も思いつきません。 Appleエンジニアに尋ねる必要があります。

完全を期すために、ここに私のソリューションに関連するすべてのコードを示します。

#import "ViewController.h"

@interface ViewController () <UITextViewDelegate>

@property (weak, nonatomic) IBOutlet UITextView *textView; // full-screen

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSString *string = @"All work and no play makes Jack a dull boy.\n\nAll work and no play makes Jack a dull boy. All work and no play makes Jack a dull boy. All work and no play makes Jack a dull boy. All work and no play makes Jack a dull boy. All work and no play makes Jack a dull boy. All work and no play makes Jack a dull boy. All work and no play makes Jack a dull boy. All work and no play makes Jack a dull boy.";

    NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:string attributes:@{NSFontAttributeName: [UIFont fontWithName:@"Verdana" size:30.0]}];

    self.textView.attributedText = attrString;

    self.textView.delegate = self;
    self.textView.backgroundColor = [UIColor yellowColor];
    [self.textView becomeFirstResponder];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardIsUp:) name:UIKeyboardDidShowNotification object:nil];
}

// helper method
- (void)scrollToCaretInTextView:(UITextView *)textView animated:(BOOL)animated
{
    CGRect rect = [textView caretRectForPosition:textView.selectedTextRange.end];
    rect.size.height += textView.textContainerInset.bottom;
    [textView scrollRectToVisible:rect animated:animated];
}

- (void)keyboardIsUp:(NSNotification *)notification
{
    NSDictionary *info = [notification userInfo];
    CGRect keyboardRect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    keyboardRect = [self.view convertRect:keyboardRect fromView:nil];

    UIEdgeInsets inset = self.textView.contentInset;
    inset.bottom = keyboardRect.size.height;
    self.textView.contentInset = inset;
    self.textView.scrollIndicatorInsets = inset;

    [self scrollToCaretInTextView:self.textView animated:YES];
}

- (void)textViewDidChange:(UITextView *)textView
{
    if ([textView.text hasSuffix:@"\n"]) {

        [CATransaction setCompletionBlock:^{
            [self scrollToCaretInTextView:textView animated:NO];
        }];

    } else {
        [self scrollToCaretInTextView:textView animated:NO];
    }
}

@end

これが機能しない状況を見つけたら、私に知らせてください。

9
bilobatum

キャレットの実際の位置を取得して調整することで解決しました。これが私の方法です。

- (void) alignTextView:(UITextView *)textView withAnimation:(BOOL)shouldAnimate {

    // where the blinky caret is
    CGRect caretRect = [textView caretRectForPosition:textView.selectedTextRange.start];
    CGFloat offscreen = caretRect.Origin.y + caretRect.size.height - (textView.contentOffset.y + textView.bounds.size.height - textView.contentInset.bottom - textView.contentInset.top);

    CGPoint offsetP = textView.contentOffset;
    offsetP.y += offscreen + 3; // 3 px -- margin puts caret 3 px above bottom

    if (offsetP.y >= 0) {
        if (shouldAnimate) {
            [UIView animateWithDuration:0.2 animations:^{
                [textView setContentOffset:offsetP];
            }];
        }
        else {
            [textView setContentOffset:offsetP];
        }
    }
}

ユーザーがReturnキーまたはEnterキーを押した後にのみ方向付けする必要がある場合は、以下を試してください。

- (void) textViewDidChange:(UITextView *)textView {
    if ([textView.text hasSuffix:@"\n"]) {
        [self alignTextView:textView withAnimation:NO];
    }
}

それがあなたのために働くかどうか私に知らせてください!

3
Logan

UITextViewのすべてのscrolling0関連の問題を解決するサブクラスを作成した人もいます。実装はこれ以上簡単ではありません-UITextViewをサブクラスPSPDFTextViewに切り替えます。

修正内容を示す(Nice Gifアニメーションを使用した)それに関する投稿はこちらです iOS 7でのUITextViewの修正

Gitはここにあります: PSPDFTextView

0
Aviel Gross

オリジナルのソースが見つかりませんが、iOS7.1で動作します

 - (void)textViewDidChangeSelection:(UITextView *)textView
 {
   if ([textView.text characterAtIndex:textView.text.length-1] != ' ') {
        textView.text = [textView.text stringByAppendingString:@" "];
    }

    NSRange range0 = textView.selectedRange;
    NSRange range = range0;
    if (range0.location == textView.text.length) {
        range = NSMakeRange(range0.location - 1, range0.length);
    } else if (range0.length > 0 &&
               range0.location + range0.length == textView.text.length) {
        range = NSMakeRange(range0.location, range0.length - 1);
    }
    if (!NSEqualRanges(range, range0)) {
        textView.selectedRange = range;
    }
 }
0
shtefane