web-dev-qa-db-ja.com

UITextViewのテキストが限界を超えています

LayoutManagerのmaximumNumberOfLinesが9に設定されたスクロール不可能なUITextViewがありますが、これは正常に機能しますが、テキストがUITextViewのフレームを超えないように制限するメソッドがNSLayoutManagerに見つからないようです。

このスクリーンショットを例にとると、カーソルは9行目にあります(1行目はスクリーンショットの上部でクリップされているため、無視してください)。ユーザーが新しい文字やスペースを入力し続けるか、リターンキーを押すと、カーソルは画面外に表示され続け、UITextViewの文字列は長くなり続けます。

enter image description here

外部文字のサイズが異なるため、UITextViewの文字数を制限したくありません。

私はこれを数週間修正しようとしています。助けていただければ幸いです。

CustomTextView.h

#import <UIKit/UIKit.h>

@interface CustomTextView : UITextView <NSLayoutManagerDelegate>

@end

CustomTextView.m

#import "CustomTextView.h"

@implementation CustomTextView

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self)
    {
        self.backgroundColor = [UIColor clearColor];
        self.font = [UIFont systemFontOfSize:21.0];
        self.dataDetectorTypes = UIDataDetectorTypeAll;
        self.layoutManager.delegate = self;
        self.tintColor = [UIColor companyBlue];
        [self setLinkTextAttributes:@{NSForegroundColorAttributeName:[UIColor companyBlue]}];
        self.scrollEnabled = NO;
        self.textContainerInset = UIEdgeInsetsMake(8.5, 0, 0, 0);
        self.textContainer.maximumNumberOfLines = 9;
    }
    return self;
}

- (CGFloat)layoutManager:(NSLayoutManager *)layoutManager lineSpacingAfterGlyphAtIndex:(NSUInteger)glyphIndex withProposedLineFragmentRect:(CGRect)rect
{
    return 4.9;
}

@end

更新、まだ解決されていません

19
klcjr89

これが私が思うより良い答えです。 shouldChangeTextInRangeデリゲートメソッドが呼び出されるたびに、doesFit:string:range関数を呼び出して、結果のテキストの高さがビューの高さを超えているかどうかを確認します。その場合、変更が行われないようにNOを返します。

-(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
    FLOG(@" called");

    // allow deletes
    if (text.length == 0)
        return YES;

    // Check if the text exceeds the size of the UITextView
    return [self doesFit:textView string:text range:range];

}
- (float)doesFit:(UITextView*)textView string:(NSString *)myString range:(NSRange) range;
{
    // Get the textView frame
    float viewHeight = textView.frame.size.height;
    float width = textView.textContainer.size.width;

    NSMutableAttributedString *atrs = [[NSMutableAttributedString alloc] initWithAttributedString: textView.textStorage];
    [atrs replaceCharactersInRange:range withString:myString];

    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:atrs];
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize: CGSizeMake(width, FLT_MAX)];
    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];

    [layoutManager addTextContainer:textContainer];
    [textStorage addLayoutManager:layoutManager];
    float textHeight = [layoutManager
            usedRectForTextContainer:textContainer].size.height;
    FLOG(@" viewHeight = %f", viewHeight);
    FLOG(@" textHeight = %f", textHeight);

    if (textHeight >= viewHeight - 1) {
        FLOG(@" textHeight >= viewHeight - 1");
        return NO;
    } else
        return YES;
}

編集OKテキストの形式を変更する場合は、いくつかのチェックを追加する必要もあります。私の場合、ユーザーはフォントを変更したり太字にしたり、段落スタイルを変更したりできます。したがって、これらの変更により、テキストがtextViewの境界線を超える可能性もあります。

したがって、最初に、これらの変更をtextViewsundoManagerに登録していることを確認する必要があります。例については、以下を参照してください(undoが呼び出された場合に元に戻すことができるように、attributedString全体をコピーするだけです)。

// This is in my UITextView subclass but could be anywhere

// This gets called to undo any formatting changes 
- (void)setMyAttributedString:(NSAttributedString*) atstr {
    self.attributedText = atstr;
    self.selectedRange = _undoSelection;
}
// Before we make any format changes save the attributed string with undoManager
// Also save the current selection (maybe should save this with undoManager as well using a custom object containing selection and attributedString)
- (void)formatText:(id)sender {
    //LOG(@"formatText: called");
    NSAttributedString *atstr = [[NSAttributedString alloc] initWithAttributedString:self.textStorage];
    [[self undoManager] registerUndoWithTarget:self
                               selector:@selector(setMyAttributedString:)
                                 object:atstr];
    // Remember selection
    _undoSelection = self.selectedRange;

   // Add text formatting attributes
   ...
   // Now tell the delegate that something changed
   [self.delegate textViewDidChange:self];
}

次に、デリゲートのサイズを確認し、サイズが収まらない場合は元に戻します。

-(void)textViewDidChange:(UITextView *)textView {
    FLOG(@" called");
    if ([self isTooBig:textView]) {
        FLOG(@" text is too big so undo it!");
        @try {
            [[textView undoManager] undo];
        }
        @catch (NSException *exception) {
            FLOG(@" exception undoing things %@", exception);
        }
    }
}
8

boundingRectWithSize:options:attributes:context:は、テキストビューのさまざまな属性(パディングなど)をとらないため、テキストビューにはお勧めしません。そのため、誤った値または不正確な値が返されます。

テキストビューのテキストサイズを決定するには、レイアウトマネージャのusedRectForTextContainer:をテキストビューのテキストコンテナとともに使用して、必要なすべてのレイアウト制約とテキストビューの癖を考慮して、テキストに必要な正確な長方形を取得します。

CGRect rect = [self.textView.layoutManager usedRectForTextContainer:self.textView.textContainer];

super実装を呼び出した後、processEditingForTextStorage:edited:range:changeInLength:invalidatedRange:でこれを行うことをお勧めします。これは、独自のテキストコンテナを提供し、そのレイアウトマネージャをサブクラスのインスタンスに設定することにより、textviewのレイアウトマネージャを置き換えることを意味します。このようにして、ユーザーが行ったテキストビューから変更をコミットし、rectがまだ受け入れ可能かどうかを確認し、受け入れられない場合は元に戻すことができます。

3
Leo Natan

これは自分で行う必要があります。基本的には次のように機能します。

  1. UITextViewDelegatetextView:shouldChangeTextInRange:replacementText:メソッドで、現在のテキストのサイズを見つけます(たとえば、NSString sizeWithFont:constrainedToSize:)。
  2. サイズが許可したサイズよりも大きい場合はFALSEを返し、それ以外の場合はTRUEを返します。
  3. ユーザーが許可したよりも大きなものを入力した場合は、ユーザーに独自のフィードバックを提供します。

編集:sizeWithFont:は非推奨であるため、boundingRectWithSize:options:attributes:context:を使用してください

例:

NSString *string = @"Hello World"; 

UIFont *font = [UIFont fontWithName:@"Helvetica-BoldOblique" size:21];

CGSize constraint = CGSizeMake(300,NSUIntegerMax);

NSDictionary *attributes = @{NSFontAttributeName: font};

CGRect rect = [string boundingRectWithSize:constraint 
                                   options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)  
                                attributes:attributes 
                                   context:nil];
2
Patrick Tescher

境界矩形のサイズを確認できます。大きすぎる場合は、元に戻すマネージャを呼び出して、最後のアクションを元に戻します。貼り付け操作か、テキストまたは改行文字を入力できます。

これは、テキストの高さがtextViewの高さに近すぎるかどうかをチェックする簡単なハックです。また、textViewrectにテキストrectが含まれていることも確認します。ニーズに合わせて、これをもう少しいじる必要があるかもしれません。

-(void)textViewDidChange:(UITextView *)textView {
    if ([self isTooBig:textView]) {
        FLOG(@" too big so undo");
        [[textView undoManager] undo];
    }
}
/** Checks if the frame of the selection is bigger than the frame of the textView
 */
- (bool)isTooBig:(UITextView *)textView {
    FLOG(@" called");

    // Get the rect for the full range
    CGRect rect = [textView.layoutManager usedRectForTextContainer:textView.textContainer];

    // Now convert to textView coordinates
    CGRect rectRange = [textView convertRect:rect fromView:textView.textInputView];
    // Now convert to contentView coordinates
    CGRect rectText = [self.contentView convertRect:rectRange fromView:textView];

    // Get the textView frame
    CGRect rectTextView = textView.frame;

    // Check the height
    if (rectText.size.height > rectTextView.size.height - 16) {
        FLOG(@" rectText height too close to rectTextView");
        return YES;
    }

    // Find the intersection of the two (in the same coordinate space)
    if (CGRectContainsRect(rectTextView, rectText)) {
        FLOG(@" rectTextView contains rectText");
        return NO;
    } else
        return YES;
}

別のオプション-ここでは、サイズを確認し、サイズが大きすぎる場合は、削除する場合を除いて、新しい文字を入力できないようにします。これは、高さを超えた場合に上部の線を埋めることも妨げるため、きれいではありません。

bool _isFull;

-(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
    FLOG(@" called");

    // allow deletes
    if (text.length == 0)
        return YES;

    // Check if the text exceeds the size of the UITextView
    if (_isFull) {
        return NO;
    }

    return YES;
}
-(void)textViewDidChange:(UITextView *)textView {
    FLOG(@" called");
    if ([self isTooBig:textView]) {
        FLOG(@" text is too big!");
        _isFull = YES;
    } else {
        FLOG(@" text is not too big!");
        _isFull = NO;
    }
}

/** Checks if the frame of the selection is bigger than the frame of the textView
 */
- (bool)isTooBig:(UITextView *)textView {
    FLOG(@" called");

    // Get the rect for the full range
    CGRect rect = [textView.layoutManager usedRectForTextContainer:textView.textContainer];

    // Now convert to textView coordinates
    CGRect rectRange = [textView convertRect:rect fromView:textView.textInputView];
    // Now convert to contentView coordinates
    CGRect rectText = [self.contentView convertRect:rectRange fromView:textView];

    // Get the textView frame
    CGRect rectTextView = textView.frame;

    // Check the height
    if (rectText.size.height >= rectTextView.size.height - 10) {
        return YES;
    }

    // Find the intersection of the two (in the same coordinate space)
    if (CGRectContainsRect(rectTextView, rectText)) {
        return NO;
    } else
        return YES;
}
1

IOS 7には、NSTextContainerクラスであるUITextviewsと連携して機能する新しいクラスがあります。

Textviewsテキストコンテナプロパティを介してUITextviewと連携します

サイズと呼ばれるこのプロパティがあります...

sizeレシーバーの外接する四角形のサイズを制御します。デフォルト値:CGSizeZero。

@property(nonatomic)CGSize sizeディスカッションこのプロパティは、lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect:から返されるレイアウト領域の最大サイズを定義します。 0.0以下の値は、制限がないことを意味します。

私はまだそれを理解して試してみる過程にありますが、それはあなたの問題を解決するはずだと信じています。

1
Paulo

テストVCを作成しました。 UITextViewで新しい行に到達するたびに、行カウンターが増加します。私が理解しているように、テキスト入力を9行以内に制限したいと考えています。これがあなたの質問に答えることを願っています。

#import "ViewController.h"

@interface ViewController ()

@property IBOutlet UITextView *myTextView;

@property CGRect previousRect;
@property int lineCounter;

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

[self.myTextView setDelegate:self];

self.previousRect = CGRectZero;
self.lineCounter = 0;
}

- (void)textViewDidChange:(UITextView *)textView {
UITextPosition* position = textView.endOfDocument;

CGRect currentRect = [textView caretRectForPosition:position];

if (currentRect.Origin.y > self.previousRect.Origin.y){
    self.lineCounter++;
    if(self.lineCounter > 9) {
        NSLog(@"Reached line 10");
        // do whatever you need to here...
    }
}
self.previousRect = currentRect;

}

@end
1
sangony

行数を見つける必要はありません。テキストビューからカーソル位置を計算することでこれらすべてを取得でき、それに応じてUITextViewの高さに応じてUITextViewのUIFontを最小化できます。

以下のリンクをご覧ください。こちらをご覧ください。 https://github.com/jayaprada-behera/CustomTextView

1
Jayaprada