UITextView that expands to text using auto layout

前端 未结 16 1916
面向向阳花
面向向阳花 2020-11-28 01:25

I have a view that is laid out completely using auto layout programmatically. I have a UITextView in the middle of the view with items above and below it. Everything works f

相关标签:
16条回答
  • 2020-11-28 01:37

    Plug and Play Solution - Xcode 9

    Autolayout just like UILabel, with the link detection, text selection, editing and scrolling of UITextView.

    Automatically handles

    • Safe area
    • Content insets
    • Line fragment padding
    • Text container insets
    • Constraints
    • Stack views
    • Attributed strings
    • Whatever.

    A lot of these answers got me 90% there, but none were fool-proof.

    Drop in this UITextView subclass and you're good.


    #pragma mark - Init
    
    - (instancetype)initWithFrame:(CGRect)frame textContainer:(nullable NSTextContainer *)textContainer
    {
        self = [super initWithFrame:frame textContainer:textContainer];
        if (self) {
            [self commonInit];
        }
        return self;
    }
    
    - (instancetype)initWithCoder:(NSCoder *)aDecoder
    {
        self = [super initWithCoder:aDecoder];
        if (self) {
            [self commonInit];
        }
        return self;
    }
    
    - (void)commonInit
    {
        // Try to use max width, like UILabel
        [self setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
        
        // Optional -- Enable / disable scroll & edit ability
        self.editable = YES;
        self.scrollEnabled = YES;
        
        // Optional -- match padding of UILabel
        self.textContainer.lineFragmentPadding = 0.0;
        self.textContainerInset = UIEdgeInsetsZero;
        
        // Optional -- for selecting text and links
        self.selectable = YES;
        self.dataDetectorTypes = UIDataDetectorTypeLink | UIDataDetectorTypePhoneNumber | UIDataDetectorTypeAddress;
    }
    
    #pragma mark - Layout
    
    - (CGFloat)widthPadding
    {
        CGFloat extraWidth = self.textContainer.lineFragmentPadding * 2.0;
        extraWidth +=  self.textContainerInset.left + self.textContainerInset.right;
        if (@available(iOS 11.0, *)) {
            extraWidth += self.adjustedContentInset.left + self.adjustedContentInset.right;
        } else {
            extraWidth += self.contentInset.left + self.contentInset.right;
        }
        return extraWidth;
    }
    
    - (CGFloat)heightPadding
    {
        CGFloat extraHeight = self.textContainerInset.top + self.textContainerInset.bottom;
        if (@available(iOS 11.0, *)) {
            extraHeight += self.adjustedContentInset.top + self.adjustedContentInset.bottom;
        } else {
            extraHeight += self.contentInset.top + self.contentInset.bottom;
        }
        return extraHeight;
    }
    
    - (void)layoutSubviews
    {
        [super layoutSubviews];
        
        // Prevents flashing of frame change
        if (CGSizeEqualToSize(self.bounds.size, self.intrinsicContentSize) == NO) {
            [self invalidateIntrinsicContentSize];
        }
        
        // Fix offset error from insets & safe area
        
        CGFloat textWidth = self.bounds.size.width - [self widthPadding];
        CGFloat textHeight = self.bounds.size.height - [self heightPadding];
        if (self.contentSize.width <= textWidth && self.contentSize.height <= textHeight) {
            
            CGPoint offset = CGPointMake(-self.contentInset.left, -self.contentInset.top);
            if (@available(iOS 11.0, *)) {
                offset = CGPointMake(-self.adjustedContentInset.left, -self.adjustedContentInset.top);
            }
            if (CGPointEqualToPoint(self.contentOffset, offset) == NO) {
                self.contentOffset = offset;
            }
        }
    }
    
    - (CGSize)intrinsicContentSize
    {
        if (self.attributedText.length == 0) {
            return CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric);
        }
        
        CGRect rect = [self.attributedText boundingRectWithSize:CGSizeMake(self.bounds.size.width - [self widthPadding], CGFLOAT_MAX)
                                                        options:NSStringDrawingUsesLineFragmentOrigin
                                                        context:nil];
        
        return CGSizeMake(ceil(rect.size.width + [self widthPadding]),
                          ceil(rect.size.height + [self heightPadding]));
    }
    
    0 讨论(0)
  • 2020-11-28 01:39

    Place hidden UILabel underneath your textview. Label lines = 0. Set constraints of UITextView to be equal to the UILabel (centerX, centerY, width, height). Works even if you leave scroll behaviour of textView.

    0 讨论(0)
  • 2020-11-28 01:42

    UITextView doesn't provide an intrinsicContentSize, so you need to subclass it and provide one. To make it grow automatically, invalidate the intrinsicContentSize in layoutSubviews. If you use anything other than the default contentInset (which I do not recommend), you may need to adjust the intrinsicContentSize calculation.

    @interface AutoTextView : UITextView
    
    @end
    

    #import "AutoTextView.h"
    
    @implementation AutoTextView
    
    - (void) layoutSubviews
    {
        [super layoutSubviews];
    
        if (!CGSizeEqualToSize(self.bounds.size, [self intrinsicContentSize])) {
            [self invalidateIntrinsicContentSize];
        }
    }
    
    - (CGSize)intrinsicContentSize
    {
        CGSize intrinsicContentSize = self.contentSize;
    
        // iOS 7.0+
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0f) {
            intrinsicContentSize.width += (self.textContainerInset.left + self.textContainerInset.right ) / 2.0f;
            intrinsicContentSize.height += (self.textContainerInset.top + self.textContainerInset.bottom) / 2.0f;
        }
    
        return intrinsicContentSize;
    }
    
    @end
    
    0 讨论(0)
  • 2020-11-28 01:42

    You can also do it without subclassing UITextView. Have a look at my answer to How do I size a UITextView to its content on iOS 7?

    Use the value of this expression:

    [textView sizeThatFits:CGSizeMake(textView.frame.size.width, CGFLOAT_MAX)].height
    

    to update the constant of the textView's height UILayoutConstraint.

    0 讨论(0)
  • 2020-11-28 01:43

    Here's a solution for people who prefer to do it all by auto layout:

    In Size Inspector:

    1. Set content compression resistance priority vertical to 1000.

    2. Lower the priority of constraint height by click "Edit" in Constraints. Just make it less than 1000.

    In Attributes Inspector:

    1. Uncheck "Scrolling Enabled"
    0 讨论(0)
  • 2020-11-28 01:44

    I needed a text view that would automatically grow up until a certain maximum height, then become scrollable. Michael Link's answer worked great but I wanted to see if I could come up with something a bit simpler. Here's what I came up with:

    Swift 5.3, Xcode 12

    class AutoExpandingTextView: UITextView {
    
        private var heightConstraint: NSLayoutConstraint!
    
        var maxHeight: CGFloat = 100 {
            didSet {
                heightConstraint?.constant = maxHeight
            }
        }
    
        private var observer: NSObjectProtocol?
    
        override init(frame: CGRect, textContainer: NSTextContainer?) {
            super.init(frame: frame, textContainer: textContainer)
            commonInit()
        }
    
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
    
        private func commonInit() {
            heightConstraint = heightAnchor.constraint(equalToConstant: maxHeight)
    
            observer = NotificationCenter.default.addObserver(forName: UITextView.textDidChangeNotification, object: nil, queue: .main) { [weak self] _ in
                guard let self = self else { return }
                self.heightConstraint.isActive = self.contentSize.height > self.maxHeight
                self.isScrollEnabled = self.contentSize.height > self.maxHeight
                self.invalidateIntrinsicContentSize()
            }
        }
    }
    
    
    
    0 讨论(0)
提交回复
热议问题