LayoutManagerのmaximumNumberOfLinesが9に設定されたスクロール不可能なUITextViewがありますが、これは正常に機能しますが、テキストがUITextViewのフレームを超えないように制限するメソッドがNSLayoutManagerに見つからないようです。
このスクリーンショットを例にとると、カーソルは9行目にあります(1行目はスクリーンショットの上部でクリップされているため、無視してください)。ユーザーが新しい文字やスペースを入力し続けるか、リターンキーを押すと、カーソルは画面外に表示され続け、UITextViewの文字列は長くなり続けます。
外部文字のサイズが異なるため、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
更新、まだ解決されていません
これが私が思うより良い答えです。 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);
}
}
}
boundingRectWithSize:options:attributes:context:
は、テキストビューのさまざまな属性(パディングなど)をとらないため、テキストビューにはお勧めしません。そのため、誤った値または不正確な値が返されます。
テキストビューのテキストサイズを決定するには、レイアウトマネージャのusedRectForTextContainer:
をテキストビューのテキストコンテナとともに使用して、必要なすべてのレイアウト制約とテキストビューの癖を考慮して、テキストに必要な正確な長方形を取得します。
CGRect rect = [self.textView.layoutManager usedRectForTextContainer:self.textView.textContainer];
super
実装を呼び出した後、processEditingForTextStorage:edited:range:changeInLength:invalidatedRange:
でこれを行うことをお勧めします。これは、独自のテキストコンテナを提供し、そのレイアウトマネージャをサブクラスのインスタンスに設定することにより、textviewのレイアウトマネージャを置き換えることを意味します。このようにして、ユーザーが行ったテキストビューから変更をコミットし、rectがまだ受け入れ可能かどうかを確認し、受け入れられない場合は元に戻すことができます。
これは自分で行う必要があります。基本的には次のように機能します。
UITextViewDelegate
のtextView:shouldChangeTextInRange:replacementText:
メソッドで、現在のテキストのサイズを見つけます(たとえば、NSString sizeWithFont:constrainedToSize:
)。編集: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];
境界矩形のサイズを確認できます。大きすぎる場合は、元に戻すマネージャを呼び出して、最後のアクションを元に戻します。貼り付け操作か、テキストまたは改行文字を入力できます。
これは、テキストの高さが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;
}
IOS 7には、NSTextContainerクラスであるUITextviewsと連携して機能する新しいクラスがあります。
Textviewsテキストコンテナプロパティを介してUITextviewと連携します
サイズと呼ばれるこのプロパティがあります...
sizeレシーバーの外接する四角形のサイズを制御します。デフォルト値:CGSizeZero。
@property(nonatomic)CGSize sizeディスカッションこのプロパティは、lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect:から返されるレイアウト領域の最大サイズを定義します。 0.0以下の値は、制限がないことを意味します。
私はまだそれを理解して試してみる過程にありますが、それはあなたの問題を解決するはずだと信じています。
テスト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
行数を見つける必要はありません。テキストビューからカーソル位置を計算することでこれらすべてを取得でき、それに応じてUITextViewの高さに応じてUITextViewのUIFontを最小化できます。
以下のリンクをご覧ください。こちらをご覧ください。 https://github.com/jayaprada-behera/CustomTextView