UITextView's text going beyond bounds

后端 未结 7 1447
深忆病人
深忆病人 2020-12-30 01:24

I have a non-scrollable UITextView with it\'s layoutManager maximumNumberOfLines set to 9, which works fine, but, I cannot seem to find a method in NSLayoutManager that rest

相关标签:
7条回答
  • 2020-12-30 02:03

    You can check the size of the bounding rectangle and if it is too big call the undo manager to undo the last action. Could be a paste operation or enter in text or new line character.

    Here is a quick hack that checks if the height of the text is too close to the height of the textView. Also checks that the textView rect contains the text rect. You might need to fiddle with this some more to suit your needs.

    -(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;
    }
    

    ANOTHER OPTION - here we check the size and if its too big prevent any new characters being typed in except if its a deletion. Not pretty as this also prevents filling a line at the top if the height is exceeded.

    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;
    }
    
    0 讨论(0)
  • 2020-12-30 02:05

    No need to find number of lines. We can get all these things by calculating the cursor position from the textview and according to that we can minimize the UIFont of UITextView according to the height of UITextView.

    Here is below link.Please refer this. https://github.com/jayaprada-behera/CustomTextView

    0 讨论(0)
  • 2020-12-30 02:18

    I created a test VC. It increases a line counter every time a new line is reached in the UITextView. As I understand you want to limit your text input to no more than 9 lines. I hope this answers your question.

    #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
    
    0 讨论(0)
  • 2020-12-30 02:20

    There is a new Class in IOS 7 that works hand in hand with UITextviews which is the NSTextContainer Class

    It works with UITextview through the Textviews text container property

    it has this property called size ...

    size Controls the size of the receiver’s bounding rectangle. Default value: CGSizeZero.

    @property(nonatomic) CGSize size Discussion This property defines the maximum size for the layout area returned from lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect:. A value of 0.0 or less means no limitation.

    I am still in the process of understanding it and trying it out but I believe it should resolve your issue.

    0 讨论(0)
  • 2020-12-30 02:24

    You will need to do this yourself. Basically it would work like this:

    1. In your UITextViewDelegate's textView:shouldChangeTextInRange:replacementText: method find the size of your current text (NSString sizeWithFont:constrainedToSize: for example).
    2. If the size is larger than you allow return FALSE, otherwise return TRUE.
    3. Provide your own feedback to the user if they type something larger than you allow.

    EDIT: Since sizeWithFont: is deprecated use boundingRectWithSize:options:attributes:context:

    Example:

    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];
    
    0 讨论(0)
  • 2020-12-30 02:25

    Here is a better answer I think. Whenever the shouldChangeTextInRange delegate method is called we call our doesFit:string:range function to see whether the resulting text height exceeds the view height. If it does we return NO to prevent the change from taking place.

    -(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;
    }
    

    EDIT OK you will also need to add some checks if you change the format of the text. In my case the user can change the font or make it bold, change paragraph style, etc.. So now any of these changes could also cause the text to exceed the textView borders.

    So first you need to make sure you are registering these changes with the textViews undoManager. See below for an example (I just copy the whole attributedString so I can put it back if undo is called).

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

    Now check the size in the delegate and undo if it does not fit.

    -(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);
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题