Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add property to know if the text is more than numberOfLines long #16

Closed
spadafiva opened this issue Mar 22, 2019 · 7 comments
Closed

Add property to know if the text is more than numberOfLines long #16

spadafiva opened this issue Mar 22, 2019 · 7 comments

Comments

@spadafiva
Copy link
Contributor

Is your feature request related to a problem? Please describe.
In my code I want to have a button for Read more that pops a full text description into a new page. It would be nice to know if the label is currently displaying all the text so that I can determine whether or not to hide the Read more button

Describe the solution you'd like
I think it should be possible to have a variable on the label class that gets updated in the drawAttributedString(... function that tells if the text is fully displayed. I would be happy to create a class with a PR to add this functionality if this solution makes sense. I'm open to any bikeshedding about the property name. I think isTruncated, isShowingFullText, hasHiddenText could all be possibilities. Specifically, I think that we could put a flag before the for loop and then an update somewhere in here to update the status of that flag and update the label property after looping.

//  ==> var showsFullText = true
for lineIndex in 0..<lineOrigins.count {
   // ...
    if lineIndex == numberOfLines - 1 &&
        truncateLastLine &&
        !(lastLineRange.length == 0 && lastLineRange.location == 0) &&
        lastLineRange.location + lastLineRange.length < textRange.location + textRange.length {
        let truncationDrawingContext = TruncationDrawingContext(attributedString: attributedString, context: context, descent: descent, lastLineRange: lastLineRange, lineOrigin: lineOrigin, numberOfLines: numberOfLines, rect: rect)
//  ==> showsFullText = false
        drawTruncation(truncationDrawingContext)
    } else { // otherwise normal drawing here
        let penOffset = CGFloat(CTLineGetPenOffsetForFlush(line, flushFactor, Double(rect.size.width)))
        let yOffset = lineOrigin.y - descent - font.descender
        context.textPosition = CGPoint(x: penOffset, y: yOffset)
        CTLineDraw(line, context)
    }
}
//  ==> self.showsFullText = showsFullText

Describe alternatives you've considered
It is possible to use a helper function to get the height of a view with a font size / attributed string, but it seems like there is a performance overhead and extra surface area that could potentially be removed.

@chansen22
Copy link
Contributor

Hi @spadafiva,

Your feature request makes sense to me, it's cumbersome to compare expected height vs actual over and over, and why not have Nantes report it, since it's already done the work to figure out if it's truncated or not.

That being said, I wonder if this is the best spot for it to live. We won't know if we're truncated until after we start drawing the label, so if you set the text on the label and then right away ask if it's truncated, I'm not sure this flag will always be up to date. I don't think we can guarantee that drawText will run directly after setting the label's text property.

Alternatively:
We have a class function that sounds similar to what you're asking already, sizeThatFitsAttributedString. Will something like that work for what you're trying to accomplish?

Maybe something like:

let recommendedSize = NantesLabel.sizeThatFitsAttributedString(displayString, withConstraints: sizeOfAvailableSpace, limitedToNumberOfLines: 4)

if sizeOfAvailableSpace.height > recommendedSize {
  // not truncated
} else {
  // truncated
}

@spadafiva
Copy link
Contributor Author

Okay, I see. What do you think about adding a delegate method for when it updates then, if not an exposed property?

@spadafiva
Copy link
Contributor Author

@chansen22 ^

@spadafiva
Copy link
Contributor Author

Hey Chris, I submitted a PR with a solution that solves my problem and also works through the delegate so that it will return the correct info. Let me know if this is a suitable solution or if you have other feedback that could help to improve this kind of feature.

@chansen22
Copy link
Contributor

Thanks for the PR! I'll take a look at it today. (Things have been busy for me the last couple days, sorry about that)

@chansen22
Copy link
Contributor

What do you think of having a computed property on the label instead? Something like:

    public var showsFullText: Bool {
        let attributedText = self.attributedText ?? NSAttributedString(string: self.text ?? "", attributes: NSAttributedString.attributes(from: self))

        let size = CGSize(width: frame.size.width, height: .greatestFiniteMagnitude)
        let framesetter = CTFramesetterCreateWithAttributedString(attributedText)
        let suggestedSize = NantesLabel.suggestFrameSize(for: attributedText, framesetter: framesetter, withSize: size, numberOfLines: 0)
        return frame.height >= suggestedSize.height
    }

You have to query it after the frame is set, someplace like viewDidLayoutSubviews or after layoutSubviews inside a view. It doesn't handle animations well in this case, but I'm sure there's an easy way to handle that. You can check out my example here: https://github.com/instacart/Nantes/tree/truncation-testing

I think I like something more like this, because it takes all the timing out of Nantes and puts it on the user. Whenever you call it, it'll give you an accurate reading of its current state. If you call it before your frames are all the way laid out, it'll be truncated (probably, depending on your text you put in the label).

Let me know what you think. Is this easier? Harder? More or less clear?

@spadafiva
Copy link
Contributor Author

Wouldn't this be the same to a client as just updating a boolean in the drawText method, except that it would be less performant? In both cases the onus for using it at the correct time relies on the user.

Alternatively, the other thing that could fix the issue that I'm having would be allowing something like a \n token as part of the attributed truncation token so that I can put a new line beneath for that token to be displayed and tapped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants