Pass on the part which doesn't fit into the UILabel

Hey,


I am trying to create a ViewController with a UILabel in it. The UIlabel has multiply lines and a fixed font size.

On different devices, different amount of text can fit in this Label. Now my question is if not all of the text fits into the UILabel, how can I pass this remaining text to the next page.


Thank you for your help

Yours sincerelly

Tell T.

Replies

The best wouyld probably to replace the label by a TextView, so that user can scroll content.


Otherwise, read this:

https://stackoverflow.com/questions/3077109/how-to-check-if-uilabel-is-truncated


Otherwise, try to calculate where the label is truncated and select the substring to display in next view. But what do you do if label requires 5 views ?

Use

func boundingRect(with size: CGSize, options: NSStringDrawingOptions = [], context: NSStringDrawingContext?) -> CGRect


May read this:

h ttps://forums.xamarin.com/discussion/67291/how-to-get-the-range-where-uilabel-will-truncate-a-text

Thx for your fast answer,


But isn't it possible to do it like the App "Books" from Apple. Depending on the device, different amount of text fits on one page.


your sincerelly

Tell

I looked further, using advice form h ttps://medium.com/@Bitomule/getting-text-size-on-ios-bdae7521822f

The following gives the required height.

Compare with the screenSize, and you'll find how much exceeds.

Up to you after this to repeat the computation to find where best to cut text.


          let attributes = [NSAttributedString.Key.font:self]
          let attString = NSAttributedString(string: veryLongLabel.text!, attributes: attributes)
          let framesetter = CTFramesetterCreateWithAttributedString(attString)
          let width = Double(veryLongLabel!.bounds.width)
          let x = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRange(location: 0,length: 0), nil, CGSize(width: width, height: 10000), nil)
          print(x, veryLongLabel.bounds)

isn't it possible to do it like the App "Books" from Apple

Yes, but I'm not sure they use labels.

I did not have time to test this, but that may be the direction to look at.

https://stackoverflow.com/questions/51031829/how-to-turn-a-uitextview-into-pages-like-an-e-reader

I tested this https://stackoverflow.com/questions/51031829/how-to-turn-a-uitextview-into-pages-like-an-e-reader

Is almost works fine but the string I get returned is always a bit longer than the acutally text I can see on the device. Why is that?

May be you have not defined correctly the font and there is a mismatch between the font used for computing the size and the font used for display.


But if it's a small difference, can you correct it manually and arbitrarily ?

Yes, I almost solved it. It only works with specific font sizes and is there any possible way to use Fonts like "Avenir Next" instead of "Courier". This is not my taste 🙂

Do you know what Apple uses for "Books" and how they programed this?

The way to make it work with any font is to pass the font as an attribute to func that compute size.


So you used a TextView and not a label, isn't it ?


If that's working, don't forget to close the thread.

You can build up the string and send it to

sizeWithAttributes:

https://developer.apple.com/documentation/foundation/nsstring/1531844-sizewithattributes?language=objc


You can also determine the size of the UILabel in the current view (in the current device) using something like myLabel,frame.size.width.

When the string size is the size of the label you do something with the string you are creating like adding an "\n".

Yes I use a Textfield. So my current approach looks like this but this doesn't work properly. When the button got pressed it calculates the part of the string which wasn't visible on the screen. That works fine but the string I get from the function is always a little bit longer than the acutal text I can see on my device. What am I doing wrong?

@IBAction func buttonHandler(_ sender: UIButton)
    {
        let substring = stringThatFitsOnScreen(originalString: textView.text)
        let textOfTextView = viewText
        passToNextView = String(textOfTextView.dropFirst(substring!.count))
        
        print(substring)
        print(passToNextView)
        performSegue(withIdentifier: "nextView", sender: self)

    }
    
    func stringThatFitsOnScreen(originalString: String) -> String? {
        // the visible rect area the text will fit into
        let userWidth  = textView.bounds.size.width - textView.textContainerInset.right - textView.textContainerInset.left
        let userHeight = textView.bounds.size.height - textView.textContainerInset.top - textView.textContainerInset.bottom
        let rect = CGRect(x: 0, y: 0, width: userWidth, height: userHeight)

        // we need a new UITextView object to calculate the glyphRange. This is in addition to
        // the UITextView that actually shows the text (probably a IBOutlet)
        let tempTextView = UITextView(frame: self.textView.bounds)
        tempTextView.font = textView.font
        tempTextView.text = textView.text

        // get the layout manager and use it to layout the text
        let layoutManager = tempTextView.layoutManager
        layoutManager.ensureLayout(for: tempTextView.textContainer)

        // get the range of text that fits in visible rect
        let rangeThatFits = layoutManager.glyphRange(forBoundingRect: rect, in: tempTextView.textContainer)

        // convert from NSRange to Range
        guard let stringRange = Range(rangeThatFits, in: originalString) else {
            return nil
        }

        // return the text that fits
        let subString = originalString[stringRange]
        return String(subString)
    }

So my current approach looks like this but this doesn't work properly. When the button got pressed it calculates the part of the string which wasn't visible on the screen. That works fine but the string I get from the function is always a little bit longer than the acutal text I can see on my device. What am I doing wrong?

    @IBAction func buttonHandler(_ sender: UIButton)
    {
        let substring = stringThatFitsOnScreen(originalString: textView.text)
        let textOfTextView = labelText

        passToNextView = String(textOfTextView.dropFirst(substring!.count))
        print(substring)
        print(passToNextView)
        print(textView.intrinsicContentSize.width)
        performSegue(withIdentifier: "test", sender: self)

    }
    
    func stringThatFitsOnScreen(originalString: String) -> String? {
        // the visible rect area the text will fit into
        let userHeight = textView.bounds.size.height - textView.textContainerInset.top - textView.textContainerInset.bottom
        let userWidth  = textView.bounds.size.width - textView.textContainerInset.right - textView.textContainerInset.left
        print(userWidth)
        let userHeight = textView.bounds.size.height - textView.textContainerInset.top - textView.textContainerInset.bottom
        //let userWidth = textView.intrinsicContentSize.width
        //let userHeight = textView.intrinsicContentSize.height
        let rect = CGRect(x: 0, y: 0, width: userWidth, height: userHeight)

        // we need a new UITextView object to calculate the glyphRange. This is in addition to
        // the UITextView that actually shows the text (probably a IBOutlet)
        let tempTextView = UITextView(frame: self.textView.bounds)
        tempTextView.font = textView.font
        tempTextView.text = textView.text

        // get the layout manager and use it to layout the text
        let layoutManager = tempTextView.layoutManager
        layoutManager.ensureLayout(for: tempTextView.textContainer)

        // get the range of text that fits in visible rect
        let rangeThatFits = layoutManager.glyphRange(forBoundingRect: rect, in: tempTextView.textContainer)

        // convert from NSRange to Range
        guard let stringRange = Range(rangeThatFits, in: originalString) else {
            return nil
        }

        // return the text that fits
        let subString = originalString[stringRange]
        return String(subString)
    }