1

I have a label. If the text count in that label is 30, then after the 20th character there should be a line break. How can we achieve that?

I have the label setup as below.

        let label = MyLabelText()
        label.numberOfLines = 2
        label.lineBreakMode = .byTruncatingTail
        label.setContentHuggingPriority(.defaultHigh, for: .horizontal)
        
    

Both line break and number of lines are defined. But I'm not sure how to go to the second line after the 20th character.

kfo dof
  • 21
  • 4
  • 2
    You have to find the 20th character in the string and insert a line-break character `\n` after it and the label will render it for you. https://stackoverflow.com/q/24092884/9086770 – trndjc Sep 23 '22 at 18:12
  • You could even subclass this label and implement this logic internally for a clean abstraction. – trndjc Sep 23 '22 at 18:15

2 Answers2

0

You can use a String extension:

extension String {

    mutating func insert(sourceString: String, string: String, indx: Int) {

        do {

            if indx > sourceString.count {
              
            } else {

                try insert(contentsOf: string, at: index(startIndex, offsetBy: indx))
            }

        // This do-catch is to make sure nothing happens if is an issue

        } catch Your.Execption {
          
        }
    }
}

And then use it like this:

myString.insert(sourceString: myString, string: "\n", indx: 20)

Ars_Codicis
  • 440
  • 1
  • 4
  • 16
0

A clean way to achieve this is to extend String so it can provide a 'wrapped' version of itself, and then use this in a subclass of UILabel to keep things clean at the point of use.

So extend String to wrap itself into a multi-line string at a certain character width:

extension String {
    func wrap(at width: Int) -> String {
        return self
            .indices
            .enumerated()
            .reduce(""){
                let charAsString = String(self[$1.1])
                let position = $1.0
                guard position != 0 else {return charAsString}
                if position.isMultiple(of: width) {
                    return $0 + "\n" + charAsString
                } else {
                    return $0 + charAsString
                }
            }
    }
}

A couple of things of note:

  • you need to use the original indices, not the just the length of the produced string or it's indices as adding the line break affects the number of characters
  • you actually want to wrap the original string, i.e. insert the line break, every width + 1 characters. Helpfully sequence enumerations are 0-indexed so you get the +1 for free :)
  • you could put the whole reduce closure into a single line ternary operation. I did initially and it was hideous to read, so an if...else is far more maintainable.

Once this is in place, creating the custom UILAbel is pretty straightforward. Create a subclass and override the text property:

class WrappedLabel: UILabel {
    let width: Int
    init(withWidth width: Int, frame: CGRect = .zero) {
        self.width = width
        super.init(frame: frame)
        numberOfLines = 0  //to allow auto-sizing of the label
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override var text: String? {
        set {
            guard let newValue = newValue else {return}
            super.text = newValue.wrap(at: width)
        }
        get {
            super.text
        }
    }
}

Implementation is then as simple as

let label = WrappedLabel(withWidth: 20)
flanker
  • 3,840
  • 1
  • 12
  • 20