【问题标题】:UITextView's text going beyond boundsUITextView 的文字越界
【发布时间】:2014-03-20 07:47:39
【问题描述】:

我有一个不可滚动的 UITextView,它的 layoutManager maximumNumberOfLines 设置为 9,效果很好,但是,我似乎无法在 NSLayoutManager 中找到一种方法来限制文本不超出 UITextView 的框架。

以这个截图为例,光标在第 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

更新,仍未解决

【问题讨论】:

  • 我要做的只是加载一个 UIWebView,它从你的 Xcode 文件中提取一个 .html 文件,该文件有一个 限制了这一点,因为它是一个相当简单的 javascript 函数。然后使用javascript注入来查询输入。还通过javascript和objective-c将你的UIWebView背景颜色设置为透明,这样它看起来不像是加载了一个网站,而只是一个UITextView(即使它不是一个UITextView)......当然这不是“答案”,所以我把它放在这个问题的 cmets 中。
  • UITextView,尤其是在 iOS 7 中有很多已知的 bug。您应该考虑使用PSPDFTextView 并查看是否可以为您解决问题。
  • 我不认为我的问题与错误有关,因为我遇到的相同问题会追溯到 iOS 5 和 6。
  • 只是想我会添加这个仍未解决。
  • 如果您不想限制字符数量,这将很难做到。是否有特定原因使其不可滚动?问题是不可滚动字符和无限字符的组合。你将不得不在其他地方设置一个限制,否则它永远不会起作用。您不能将不确定的项目放在预定义的空间中。

标签: ios objective-c ios7 uitextview


【解决方案1】:

我认为这是一个更好的答案。每当调用 shouldChangeTextInRange 委托方法时,我们都会调用 dosFit: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;
}

编辑 好的,如果您更改文本的格式,您还需要添加一些检查。在我的情况下,用户可以更改字体或使其变为粗体、更改段落样式等。所以现在这些更改中的任何一个也可能导致文本超出 textView 边框。

所以首先你需要确保你正在使用 textViews undoManager 注册这些更改。请参阅下面的示例(我只是复制整个属性字符串,以便在调用 undo 时将其放回)。

// 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);
        }
    }
}

【讨论】:

  • Duncan 我无法获取您的代码来解决我的问题;另外,我根本不允许用户更改文本的格式。有没有办法让我向你展示我想要完成的这个示例应用程序?提前谢谢你好心的先生。
  • 将其压缩并通过电子邮件发送给我,地址为 duncan.groenewald@ossh.com.au
  • 我投票你的答案,因为它非常好!还可以考虑inset 更好! (textHeight &lt; viewHeight-1-textView.textContainerInset.top-textView.textContainerInset.bottom)
【解决方案2】:

boundingRectWithSize:options:attributes:context: 不推荐用于 textviews,因为它不采用 textview 的各种属性(例如填充),因此返回不正确或不精确的值。

要确定 textview 的文本大小,请使用布局管理器的 usedRectForTextContainer: 和 textview 的文本容器来获得文本所需的精确矩形,同时考虑到所有必需的布局约束和 textview 怪癖。

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

我建议在调用super 实现之后在processEditingForTextStorage:edited:range:changeInLength:invalidatedRange: 中执行此操作。这意味着通过提供您自己的文本容器并将其布局管理器设置为您的子类的实例来替换 textview 的布局管理器。这样您就可以提交用户从 textview 所做的更改,检查 rect 是否仍然可以接受,如果不能接受则撤消。

【讨论】:

  • 我不知道如何实现这个
  • @troop231 processEditingForTextStorage:edited:range:changeInLength:invalidatedRange: 在将更改提交到文本存储时调用。您要实施的是检查这些更改是否可以接受,如果不可以撤消它们。要捕获processEditingForTextStorage:edited:range:changeInLength:invalidatedRange:,您需要继承NSLayoutManagerUITextView 有一个新的初始化方法:initWithFrame:textContainer:,你需要使用它。
  • 我创建了一个 NSLayoutManager 的子类,但不知道如何设置我的 CustomTextView 类来使用它。
  • @troop231 在这里查看如何使用自定义布局管理器:stackoverflow.com/a/20916091/983912
  • @troop231 不,只是您自己的实例,因此您可以将其布局管理器设置为您的自定义实例。
【解决方案3】:

您需要自己做这件事。基本上它会像这样工作:

  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];

【讨论】:

  • 你能举个例子吗?我相信 sizeWithFont 方法已被弃用。这还必须考虑换行符和文本视图的内置文本换行。
  • 在上面的问题中查看我的更新,看看我是否走在正确的轨道上?谢谢
  • boundingRectWithSize:... 在 textviews 中是个坏主意。
【解决方案4】:

我创建了一个测试 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

【讨论】:

  • 还不能让它工作。您能否确认如果用户键入 2 行,然后将光标移动到第一行的开头,然后按回车键,则前 2 行将一直到文本视图的底部,并且然后在第二行到达文本视图的底部后停止?
  • 我没有跑遍所有可能的场景。我的理解是,当达到一定数量的行时,你需要一些东西来提醒你。
  • 我有几个场景要解决,这是一个令人头疼的问题,我可以通过禁止用户使用返回键来轻松解决,但这根本不是一个好的体验。
  • 仔细检查您是否在 .h 文件中设置了
  • 好吧,我不知道该说什么......每次使用新行时,代码都会准确计数。我用换行和返回键对其进行了测试。自己试试吧。
【解决方案5】:

您可以检查边界矩形的大小,如果它太大,请调用撤消管理器来撤消最后一个操作。可以是粘贴操作,也可以是输入文本或换行符。

这是一个检查文本高度是否太接近 textView 高度的快速技巧。还检查 textView rect 是否包含文本 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;
}

【讨论】:

  • 嗨,Duncan,你能告诉我 self.contentView 是什么吗?
  • @troop231 我假设视图包含文本视图?
  • 是的@LeoNatan 是正确的,只需使用textView.superView 或self.view。我使用可滚动的表单,因此 contentView 是 UI 控件所在的视图,它本身位于 scrollView 中。
  • 您将不得不摆弄一下才能弄清楚什么是有效的。将其设置为 0 并查看。我认为您需要注意 a 和 p 等字母的差异,其中 p 有下降 - 因此,如果用户在最后一行键入 a a 它可能适合,但如果他们随后键入 a p 它不会。因此,最好获取您正在使用的特定字体和字体大小的最大高度,而不是硬编码值。
  • 问题是您只能在编辑完成后确定大小,然后您必须撤消。不幸的是,撤消管理器似乎不会一次撤消一个字符。另一种选择是拥有一个隐藏的 textView,您将编辑从 shouldChangeTextInRange 传递到,然后使用它来计算大小。如果太大,则返回 NO。
【解决方案6】:

IOS 7 中有一个新类与 UITextviews 协同工作,即 NSTextContainer 类

它通过 Textviews 文本容器属性与 UITextview 一起工作

它有一个叫做 size 的属性 ...

尺寸 控制接收器边界矩形的大小。默认值:CGSizeZero。

@property(nonatomic) CGSize 大小 讨论 此属性定义从 lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect: 返回的布局区域的最大大小。 0.0 或更小的值表示没有限制。

我仍在了解并尝试它,但我相信它应该可以解决您的问题。

【讨论】:

    【解决方案7】:

    无需查找行数。 我们可以通过从textview计算光标位置来得到所有这些东西,据此我们可以根据UITextView的高度最小化UITextView的UIFont。

    这里是下面的链接。请参考这个。 https://github.com/jayaprada-behera/CustomTextView

    【讨论】:

      猜你喜欢
      • 2012-04-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-31
      • 2023-03-14
      • 1970-01-01
      • 2014-05-31
      • 1970-01-01
      相关资源
      最近更新 更多