IOS 7以降、ユーザーが新しい行にフローするテキストを入力しても、UITextView
は自動的にカーソルまでスクロールしません。この問題は、SOやその他の場所で詳細に文書化されています。私にとって、問題はiOS 7.1にもまだ存在しています。何が問題なのですか?
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
でテキストビューが最初のレスポンダーになりました。カーソルが置かれているテキストビューの下部までスクロールします。
(2.)単一の文字を入力する前に、キーボードのReturnキーをタップします。キャレットが見えなくなります。
(3.)ただし、Returnキーをもう一度タップすると、状況が正常化するようです。 (ただし、後者の新しい行を削除すると、キャレットが再び消えます)。
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キーボードで下キーを押しても機能しないことに注意してください。
堅牢なソリューションは、次の状況に耐える必要があります。
(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
これが機能しない状況を見つけたら、私に知らせてください。
キャレットの実際の位置を取得して調整することで解決しました。これが私の方法です。
- (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];
}
}
それがあなたのために働くかどうか私に知らせてください!
UITextView
のすべてのscrolling0関連の問題を解決するサブクラスを作成した人もいます。実装はこれ以上簡単ではありません-UITextView
をサブクラスPSPDFTextView
に切り替えます。
修正内容を示す(Nice Gifアニメーションを使用した)それに関する投稿はこちらです iOS 7でのUITextViewの修正
Gitはここにあります: PSPDFTextView
オリジナルのソースが見つかりませんが、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;
}
}