skip to Main Content

I have a UILabel with 3 lines set to word wrap. I also set the label to have a width of 180. While this works in almost all circumstances, some languages with very long words are starting to character wrap because their length is greater than 180. Ideally, I would have the label keep the width of 180 in all cases, except if a word is too long to fit. Then, I would like to expand the width to be the minimum size to keep the longest word in tact. Any advice on how to do that?

let myString = "thisisaveryveryveryveryverylongstring"
let myLabel = UILabel()
myLabel.text = myString
mylabel.numberOfLines = 3
myLabel.lineBreakMode = .byWordWrapping
myLabel.widthAnchor.constraint(equalToConstant: 180).isActive = true

I’ve tried setting a preferredWidth as well as setting the width constraint to have a priority of less than 1000, but in both circumstances the longer word still character wraps.

2

Answers


  1. To achieve the desired behavior of having a UILabel with a maximum of three lines, auto-shrinking the text to fit within those lines, and dynamically adjusting the number of lines, you can follow these steps:

    •   Create your UILabel and set its properties as follows:
      

    swift

    let myString = "thisisaveryveryveryveryverylongstring" let myLabel = UILabel() myLabel.text = myString myLabel.numberOfLines = 0 // Allow dynamic number of lines myLabel.lineBreakMode = .byWordWrapping myLabel.translatesAutoresizingMaskIntoConstraints = false // Ensure Auto Layout is enabled

    •   Set up the width constraint to limit the label's width to a maximum of 180:
      

    swift

    let widthConstraint = myLabel.widthAnchor.constraint(lessThanOrEqualToConstant: 180) widthConstraint.priority = .required widthConstraint.isActive = true

    •   Define the minimum font size for auto-shrinking by using the minimumScaleFactor property. Adjust this value as needed based on your design requirements:
      

    swift

    myLabel.minimumScaleFactor = 0.5 // Adjust as needed myLabel.adjustsFontSizeToFitWidth = true

    •   Add your label to the view hierarchy and make sure you have appropriate constraints to position it within its superview.
      

    With these settings, the label will automatically adjust its font size to fit within the specified width (180) and ensure the text remains within a maximum of three lines. If the text is too long to fit, it will shrink the font size while keeping it within three lines. Setting numberOfLines to 0 allows the label to dynamically adjust the number of lines based on the content.
    You can fine-tune the minimumScaleFactor value to control the extent to which the font size can shrink while auto-fitting the text within the label’s frame. This approach provides a flexible and responsive solution for your UILabel requirements.

    Login or Signup to reply.
  2. It’s not entirely clear what you are trying to do, but I’m going to guess it’s like this…

    Consider the different word lengths here:

    English: Some Occupation
    German:  Irgendeine Beschäftigung
    
    
    English: Occupational Therapist 
    German:  Beschäftigungstherapeutin
    

    Using the first example, if the string is: "Some Occupation Irgendeine Beschäftigung" and the label width is constrained to 180-points, it will look like this:

    enter image description here

    Which, I’m guessing, is the first part of your goal — 180-point label frame width.

    However, using the second example, if the string is: "Occupational Therapist Beschäftigungstherapeutin" and the label width is constrained to 180-points, it will look like this:

    enter image description here

    The single word "Beschäftigungstherapeutin" by itself is too wide, and we get character wrapping.

    So, we need to get to here:

    enter image description here

    The label width is now 206-points.

    To accomplish that, we can split the string into individual words and find the widest single word:

    func calcMaxWordWidth(_ str: String, forLabel: UILabel) -> CGFloat {
        var maxWidth: CGFloat = 0
    
        let words = str.components(separatedBy: " ")
    
        words.forEach { s in
            forLabel.text = s
            forLabel.sizeToFit()
            maxWidth = max(maxWidth, forLabel.frame.width)
        }
    
        return maxWidth
    }
    

    Here’s an example you can try:

    class ExampleVC: UIViewController {
        
        override func viewDidLoad() {
            super.viewDidLoad()
    
            var myString: String = ""
            
            var myLabelA: UILabel = UILabel()
            var myLabelB: UILabel = UILabel()
            
            var sizingLabel: UILabel = UILabel()
            
            // all labels have the same font
            [myLabelA, myLabelB, sizingLabel].forEach { v in
                v.font = .systemFont(ofSize: 17.0)
            }
    
            myLabelA.numberOfLines = 3
            myLabelA.lineBreakMode = .byWordWrapping
            
            myLabelB.numberOfLines = 3
            myLabelB.lineBreakMode = .byWordWrapping
            
            // so we can see the label frames
            myLabelA.backgroundColor = .yellow
            myLabelB.backgroundColor = .green
    
            myString = "Occupational Therapist Beschäftigungstherapeutin"
            //myString = "Some Occupation Irgendeine Beschäftigung"
    
            myLabelA.text = myString
            myLabelB.text = myString
    
            myLabelA.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(myLabelA)
            
            myLabelB.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(myLabelB)
            
            // for sizingLabel
            //  do NOT set .translatesAutoresizingMaskIntoConstraints = false
            //  do NOT add it as a subview
            //  set number of lines to 1
            sizingLabel.translatesAutoresizingMaskIntoConstraints = true
            sizingLabel.numberOfLines = 1
            
            let maxWordWidth: CGFloat = calcMaxWordWidth(myString, forLabel: sizingLabel)
            let actualWidth: CGFloat = max(180.0, maxWordWidth)
    
            print("mx:", maxWordWidth, "act:", actualWidth)
            
            // set myLabelA width to 180.0
            myLabelA.widthAnchor.constraint(equalToConstant: 180.0).isActive = true
            
            // set myLabelB width to MAX of 180.0 or widest single word
            myLabelB.widthAnchor.constraint(equalToConstant: actualWidth).isActive = true
    
            let g = view.safeAreaLayoutGuide
            
            NSLayoutConstraint.activate([
                
                myLabelA.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
                myLabelA.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                
                myLabelB.topAnchor.constraint(equalTo: myLabelA.bottomAnchor, constant: 20.0),
                myLabelB.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                
            ])
            
        }
        
        func calcMaxWordWidth(_ str: String, forLabel: UILabel) -> CGFloat {
            var maxWidth: CGFloat = 0
    
            let words = str.components(separatedBy: " ")
    
            words.forEach { s in
                forLabel.text = s
                forLabel.sizeToFit()
                maxWidth = max(maxWidth, forLabel.frame.width)
            }
    
            return maxWidth
        }
    }
    

    Please note: this is meant to be a starting point. You would probably want to implement a "max width" so your label doesn’t extend wider than the screen (or into another view). Also, I did very little testing of this… so it should not be considered "Production Ready"

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search