26

更新 これは、IOS 7 のみの問題のようです。受け入れられた回答に優れた回避策が追加されました。

テキストビューのタイトル、つまり私のコントロールを含む UITextView と UILabel を含むカスタムコントロールを作成しました。私のコントロールは自動的にサイズを変更して、テキストビューとタイトルを調整します。これが発生する前に、テキストビューのサイズをテキストに合わせて変更します。これは最適に機能します。

テキストビューが自動的に最後の行までスクロールするように機能を追加しました。または、少なくとも私が試みていることです。最後の行に空のテキスト以外が含まれている限り、問題なく機能します。テキストが空の場合は、ロールダウンするため、カーソルの約半分しか表示されません。

私は何を間違っていますか?

よりよく理解できるように、いくつかの画像を作成しました。

これは、単語を入力して改行を作成する私です。(まだスクロールするには不十分です)

改行する前に

そして、私は改行を行います。(Enter キーを押して)カーソルが半分になっている様子をよく見てください。これが問題です!

問題

私が期待したものを正確に見ることができるように、次の写真を作成しました。

私が欲しいもの!

4

15 に答える 15

8

textViewDidChange:次のようなスニペットを挿入しようとしました:

if([textView.text hasSuffix:@"\n"])
    [self.textView setContentOffset:CGPointMake(0,INT_MAX) animated:YES];

それは本当にきれいではありません。私はいくつかのより良いものを見つけるために取り組んでいますが、今のところ動作します:D

更新: これは iOS 7 (現時点では Beta 5) でのみ発生するバグであるため、次のコードで回避策を実行できます。

if([textView.text hasSuffix:@"\n"]) { 
    double delayInSeconds = 0.2; 
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); 
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 
        CGPoint bottomOffset = CGPointMake(0, self.textView.contentSize.height - self.textView.bounds.size.height); 
        [self.textView setContentOffset:bottomOffset animated:YES]; 
    }); 
}

次に、iOS 6 では、遅延を 0.0 に設定するか、ブロックのコンテンツのみを使用するかを選択できます。

于 2013-08-09T09:39:31.393 に答える
4

Swift 3 の使用 :-

let line : CGRect = textView.caretRect(for: (textView.selectedTextRange?.start)!)
    print("line = \(line)")

    let overFlow = line.origin.y + line.size.height - (textView.contentOffset.y + textView.bounds.size.height - textView.contentInset.bottom - textView.contentInset.top)

    print("\n OverFlow = \(overFlow)")

    if (0 < overFlow)
    {
        // We are at the bottom of the visible text and introduced a line feed, scroll down (iOS 7 does not do it)
        // Scroll caret to visible area

        var offSet : CGPoint = textView.contentOffset

        print("offSet = \(offSet)")

        //leave 7 pixels margin
        offSet.y += (overFlow + 7)

        //Cannot animate with setContentOffset:animated: or caret will not appear

        UIView.animate(withDuration: 0.3, animations: {
            textView.setContentOffset(offSet, animated: true)
        })
    }
于 2016-12-23T13:06:27.677 に答える
2

Vikの回答を次のように変更すると、うまくいきました。

if([_textView.text hasSuffix:@"\n"])
{
    if (_textView.contentSize.height - _textView.bounds.size.height > -30)
    {
        double delayInSeconds = 0.2;
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void)
        {
            CGPoint bottomOffset = CGPointMake(0, _textView.contentSize.height - _textView.bounds.size.height);
            [_textView setContentOffset:bottomOffset animated:YES];
        });
    }
}
于 2013-09-24T10:16:33.167 に答える
1

以下をviewWillAppearに入れると、これと、ベータ版でUITextViewにあると思われる他のいくつかの問題が解決されることがわかりました。

[self.textView.layoutManager ensureLayoutForTextContainer:self.textView.textContainer];

于 2013-08-18T22:38:00.800 に答える
1

スウィフト3では

ここに画像の説明を入力

参照アウトレットとテキストビューのデリゲートを設定

class ViewController: UIViewController , UITextViewDelegate{

@IBOutlet var txtViewRef: UITextView!

viewDidLoad で、KeyboardFrame を変更するためのデリゲートと通知を設定するか、キーボードを非表示にします

 override func viewDidLoad() {
    super.viewDidLoad()

    txtViewRef.delegate = self
    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.updateTextView(notification:)), name: Notification.Name.UIKeyboardWillChangeFrame, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.updateTextView(notification:)), name: Notification.Name.UIKeyboardWillHide, object: nil)    
}

関数 updateTextView を作成します。この関数では、キーボードのフレームを取得し、コンテンツのインセットとスクロール インジケーターを変更して、テキストビューをスクロールします。

func updateTextView(notification : Notification)
{
    let userInfo = notification.userInfo!
    let keyboardEndFrameScreenCoordinates = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
    let keyboardEndFrame = self.view.convert(keyboardEndFrameScreenCoordinates, to: view.window)

    if notification.name == Notification.Name.UIKeyboardWillHide{
        txtViewRef.contentInset = UIEdgeInsets.zero
    }
    else
    {
        txtViewRef.contentInset = UIEdgeInsetsMake(0, 0, keyboardEndFrame.height, 0)
        txtViewRef.scrollIndicatorInsets = txtViewRef.contentInset
    }

    txtViewRef.scrollRangeToVisible(txtViewRef.selectedRange)

}
于 2017-05-25T09:17:19.573 に答える
0

私の自動サイズ設定 UITextView の iOS10 では、私にとっての鍵は

// my method called on text change

- (void)updateLayout {

    [self invalidateIntrinsicContentSize];

    [UIView animateWithDuration:0.33 animations:^{

        [self.superview layoutIfNeeded];

        CGPoint bottomOffset = CGPointMake(0, self.contentSize.height - self.bounds.size.height);
        [self setContentOffset:bottomOffset animated:NO];

    } completion:nil];

}

クラス全員

#import "AutosizeTextView.h"

@implementation AutosizeTextView

- (instancetype)initWithFrame:(CGRect)frame {

    if (self = [super initWithFrame:frame]) {
        [self setup];
    }
    return self;
}

- (void)awakeFromNib {

    [super awakeFromNib];

    [self setup];
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidChangeNotification object:self];
}

- (void)setText:(NSString *)text {
    [super setText:text];
    [self updateLayout];
}

- (CGSize)intrinsicContentSize {
    CGRect textRect = [self.layoutManager usedRectForTextContainer:self.textContainer];
    CGFloat height = textRect.size.height + self.textContainerInset.top + self.textContainerInset.bottom;
    return CGSizeMake(UIViewNoIntrinsicMetric, height);
}


////////////////////////////////////////////////////////////////////////
#pragma mark - Private
////////////////////////////////////////////////////////////////////////

- (void)setup {

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChangeNotification:) name:UITextViewTextDidChangeNotification object:self];
    self.textContainer.lineFragmentPadding = 0;
    self.textContainerInset = UIEdgeInsetsMake(4, 4, 4, 4);

}

- (void)updateLayout {

    [self invalidateIntrinsicContentSize];

    [UIView animateWithDuration:0.33 animations:^{

        [self.superview layoutIfNeeded];

        CGPoint bottomOffset = CGPointMake(0, self.contentSize.height - self.bounds.size.height);
        [self setContentOffset:bottomOffset animated:NO];

    } completion:nil];

}

////////////////////////////////////////////////////////////////////////
#pragma mark - Notification
////////////////////////////////////////////////////////////////////////

- (void)textDidChangeNotification:(NSNotification *)notification {

    [self updateLayout];

}

@end
于 2016-10-28T12:40:15.457 に答える
0

受け入れられた回答の解決策は使用できません。

textView に 1000 語あり、最後の文字が「\n」であるとします。textView の最初の行を編集すると、hasSuffix:@"\n"が返さYESれ、textView はすぐにドキュメントの一番下までスクロールします。

または、空白の textView から始めて単語を 1 つ入力し、Return キーを押します。テキストが一番下までスクロールします。

============  ============   ============   ============
 Te|           Text |         Text           
                              |


                                             Text
                                             |
============  ============   ============   ============

これはより良い回避策かもしれませんが、完全ではありません。キャレットが最大点を下回っているかどうかを確認し、最大点までスクロールします。

-(void)textViewDidChange:(UITextView *)textView {

    // Get caret frame
    UITextPosition *caret = [textView positionFromPosition:textView.beginningOfDocument offset:textView.selectedRange.location];
    CGRect caretFrame     = [textView caretRectForPosition:caret];

    // Get absolute y position of caret in textView
    float absCaretY       = caretFrame.origin.y - textView.contentOffset.y;

    // Set a max y for the caret (in this case the textView is resized to avoid the keyboard and an arbitrary padding is added)
    float maxCaretY       = textView.frame.size.height - 70;

    // Get how far below the maxY the caret is
    float overflow        = absCaretY - maxCaretY;

    // No need to scroll if the caret is above the maxY
    if (overflow < 0)
        return;

    // Need to add a delay for this to work
    double delayInSeconds = 0.2;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){

        // Scroll to the maxCaretY
        CGPoint contentOffset = CGPointMake(0, textView.contentOffset.y + overflow);
        [textView setContentOffset:contentOffset animated:YES];
    });
}
于 2013-09-27T07:23:55.520 に答える
0

使ってみて

   textView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
   textView.autoresizingSubviews = YES;

iOS7の問題を解決しました。

于 2013-12-13T06:22:33.780 に答える
0

私は同じ問題を抱えていましたが、UITableView内のUITextViewについて、いくつかの調査の後、それを修正する「簡単な」方法が見つからなかったため、受け入れられた回答に基づいて、完全に機能するソリューションを作成しました( UICollectionView、UIScrollView内でも機能するはずです)この拡張機能内でいくつかの変更がコメントされています)。

したがって、簡単に再利用するには、UIKit の上にいくつかの拡張機能が必要です。

extension UITextView {

    func scrollToCursor(animated: Bool = false, verticalInset: CGFloat = 8) {
        guard let selectedTextRange = selectedTextRange else { return }
        var cursorRect = caretRect(for: selectedTextRange.start)

        // NOTE: can't point UIScrollView, coz on iOS 10 closest view will be UITableWrapperView
        // to extend functionality for UICollectionView or plain UIScrollView it's better to search them one by one
        let scrollView = findParent(of: UITableView.self) ?? self
        cursorRect = convert(cursorRect, to: scrollView)

        if cursorRect.origin.x.isInfinite || cursorRect.origin.y.isInfinite {
            return
        }

        let bottomOverflow = cursorRect.maxY - (scrollView.contentOffset.y + scrollView.bounds.height - scrollView.contentInset.bottom - scrollView.contentInset.top)

        if bottomOverflow > 0 {
            let offset = CGPoint(x: scrollView.contentOffset.x, y: scrollView.contentOffset.y + bottomOverflow + verticalInset)
            scrollView.setContentOffset(offset, animated: animated)
            return
        }

        let topOverflow = scrollView.contentOffset.y - cursorRect.minY
        if topOverflow > 0 {
            let offset = CGPoint(x: scrollView.contentOffset.x, y: scrollView.contentOffset.y - topOverflow - verticalInset)
            scrollView.setContentOffset(offset, animated: animated)
        }
    }
}

UIView:

extension UIView {
    func findParent<Parent: UIView>(of parentType: Parent.Type) -> Parent? {
        return superview?.findNext(of: parentType)
    }

    private func findNext<Parent: UIView>(of parentType: Parent.Type) -> Parent? {
        if let res = self as? Parent {
            return res
        }

        return superview?.findNext(of: parentType)
    }
}

そのため、UITextViewDelegate では、テキストが変更されたときに、必要な場所を呼び出します (ディスパッチ キューのメイン非同期ブロック内にある可能性があります - これには ReactiveSwift コールバックを使用しています)。

textView.scrollToCursor()

カーソル位置の変更 (画面の上部) に上への移動を追加する場合は、textViewDidChangeSelectionデリゲートのメソッド内でこのメソッドを呼び出す必要があります (もちろん、選択の長さをチェックして)。

于 2019-02-06T09:12:50.570 に答える