Reputation: 6976
There's many many questions on the internet complaining about how UITextView
cuts off text prematurely, but I can't find any about the reverse situation where you actually want it to cut off the text before the end - but part way through a line.
It appears that UITextView
will only ever render discreet lines of text - i.e. if it's not big enough, it will not render beyond the last line that fully fits inside it. I am seeking a way to make it render a partial line of text right up to the bottom boundary.
i.e. it currently does what's on the left here, whereas I am looking for a way to do what's on the right:
My UITextView
has a fixed height and scrolling is disabled. I am assigning attributedText
that could have bold, italics, etc rather than a simple string.
Upvotes: 0
Views: 374
Reputation: 6976
OK, so it got ugly pretty fast, but I did manage to do this...
Rather than using a standard UITextView
created through Interface Builder, I had to manually build one using a custom NSTextContainer
:
let frame = containerView.frame
let textStorage = NSTextStorage(string: "")
let layoutManager = NSLayoutManager()
let textContainer = CardTextContainer(size: CGSize(width: frame.size.width, height: CGFloat.greatestFiniteMagnitude))
textContainer.widthTracksTextView = true
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
let textView = UITextView(frame: frame, textContainer: textContainer)
textView.isScrollEnabled = false
containerView.addSubview(textView)
I then defined CardTextContainer
as a subclass of NSTextContainer
that delegates its behaviour to the base class, except in the scenario where the line being rendered overlaps with the bottom of the NSTextView
:
class CardTextContainer : NSTextContainer {
override func lineFragmentRect(forProposedRect proposedRect: CGRect, at characterIndex: Int, writingDirection baseWritingDirection: NSWritingDirection, remaining remainingRect: UnsafeMutablePointer<CGRect>?) -> CGRect {
let fragmentRect = super.lineFragmentRect(forProposedRect: proposedRect, at: characterIndex, writingDirection: baseWritingDirection, remaining: remainingRect)
if ((fragmentRect.size.width > 0) || (fragmentRect.size.height > 0)) {
// Just rely on the base implementation when it's working well
return fragmentRect
} else {
// See if we could do slightly better than they did
if (proposedRect.origin.y < self.size.height) {
return CGRect(origin: proposedRect.origin, size: CGSize(width: self.size.width, height: proposedRect.size.height))
} else {
// Fall back to the 'I give up' solution
return fragmentRect
}
}
}
}
Upvotes: 0
Reputation: 77462
I don't think you can accomplish this with properties of UITextView
.
One approach, though:
UIView
Clips to Bounds
of the container view to TRUEThis will allow the text view to grow vertically to fit its text, but the container view will clip the portion you don't want visible.
Container view background is yellow, text view background is cyan:
Using Debug View Hierarchy - Show Clipped Content
:
and here's the 3D view:
EDIT
Here is another approach -- may or may not work for your requirements...
UITextView
- set position and width and height constraintsNow, add a UIPanGestureRecognizer
to that text view. It doesn't have to do anything... but it will (at least it appears to) prevent the text view from receiving the pan gesture, and thus prevents scrolling.
One noticeable issue though... if you have enough text that you get multiple lines, when you start selecting you can drag the selection handle down to the next line(s) and the text will scroll up... could probably prevent that in code, or maybe that would be desirable and then reset the scroll after the user has copied the selected text.
Upvotes: 1