0

I have a UITableViewCell containing a UITextView and a UIImageView. The text view has scrolling disabled. The UITableViewCell does use auto layout to calculate its height to fit the content.

view layout screenshot

If there is more text than in the screenshot I want the text to flow around the image and use the space below it too. Therefore I use exclusion path on the text view. The exclusion Path gets set correctly and the text flows around the image.

Exclusion paths must be set after auto layout has calculated the frames. As the exclusion path blocks some view space on the text view the containing text does not fit any more in the height auto layout calculated for the text view.

How can I make this work together with auto layout?

I tried to set a height constraint on the text view after setting exclusion paths:

    let size = textView.sizeThatFits(CGSize(width: textView.frame.size.width, height: CGFloat(MAXFLOAT)))
    textView.heightAnchor.constraint(equalToConstant: size.height).isActive = true

This does not help. I need to call layoutSubviews() on the UITableViewCell to force it to recalculate its height. However this will create an endless loop as I set my exclusion paths in viewDidLayoutSubviews().

funkenstrahlen
  • 3,032
  • 2
  • 28
  • 40
  • There is a special function of UIView which updates constraints. Maybe try overriding it like this: `override func updateConstraints() { super.updateConstraints() let size = textView.sizeThatFits(CGSize(width: textView.frame.size.width, height: CGFloat(MAXFLOAT))) textView.heightAnchor.constraint(equalToConstant: size.height).isActive = true }` – Eugene Dudnyk Jun 14 '17 at 21:57

2 Answers2

0

I found that UITextView does NOT recalculate intrinsicContentSize when isScrollEnabled is false and textContainer.exclusionPaths changes.

The solution was simple:

class FixedTextView : UITextView {
    
    func setExclusionPaths(_ paths: [UIBezierPath]) {
        textContainer.exclusionPaths = paths
        
        if !isScrollEnabled {
            invalidateIntrinsicContentSize()
        }
    }
}
ryanholden8
  • 536
  • 4
  • 13
-1

I had a similar issue where I couldn't feasibly access viewDidLayoutSubviews() without a lot of excessive coupling, so I feel your pain.

Suppose you have view A and the subviews include a UITextView.

In view A, override layoutSubviews()

override public func layoutSubviews() {
  super.layoutSubviews()
  // At this point, UITextView is laid out and has a valid frame.
  // Set exclusion paths here, now that you have enough info to set coordinates.
  super.layoutSubviews() // do it again.
}

This is similar to the approach taken with UILabel and maxPreferredWidth on prior iOS versions. iOS: Multi-line UILabel in Auto Layout

One caveat: I don't know that setting a constraint in here will help you, as the layout cycles don't really work that way, but according to this Update Constraints of Cell Subview , you might be able to get away with doing layoutIfNeeded().

  • https://developer.apple.com/documentation/uikit/uiview/1622482-layoutsubviews `You should not call this method directly.` You should use `layoutIfNeeded` instead of the second call – Vyachaslav Gerchicov Aug 19 '21 at 13:52