Search code examples
iosswiftautolayoutuilabel

Dynamically adjust size of UILabel according to the number of lines programmatically


I have a View where I create a UILabel with numberOfLines equal to zero. I want the label size to be adjusted according to the content of my label (according to numberOfLines). However, I have tried many things including all this, and it still does not work for me. I use Neon library for AutoLayot.

And here's my whole for View:

import UIKit
import Neon

class AdditionalDescriptionView: UIView {

    lazy var locationLabel = UILabel()
    lazy var seasonLabel = UILabel()
    lazy var quantityLabel = UILabel()
    lazy var durationLabel = UILabel()
    lazy var requirementsLabel = UILabel()
    var height: CGFloat = 0
    var text = NSString()
    var size = CGSize()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func setup() {
        locationLabel.textAlignment = .left
        locationLabel.text = "Локация: Каркаралинск (200км от Караганды)"
        locationLabel.textColor = .black
        locationLabel.font = UIFont.avenirNextRegular(ofSize: 14)
        locationLabel.numberOfLines = 0
        seasonLabel.textAlignment = .left
        seasonLabel.textColor = .black
        seasonLabel.font = UIFont.avenirNextRegular(ofSize: 14)
        seasonLabel.text = "Сезоны: все"
        quantityLabel.textAlignment = .left
        quantityLabel.textColor = .black
        quantityLabel.text = "Количество людей: 5-25"
        quantityLabel.font = UIFont.avenirNextRegular(ofSize: 14)
        durationLabel.textAlignment = .left
        durationLabel.textColor = .black
        durationLabel.text = "Длительность тура: 3 суток"
        durationLabel.font = UIFont.avenirNextRegular(ofSize: 14)
        requirementsLabel.textAlignment = .left
        requirementsLabel.textColor = .black
        requirementsLabel.text = "Требования: удобная обувь, дополнительный груз не более 3кг, минимум 1л питьевой воды. Лицам с кардио- и дыхательными проблемами не рекомендуется участие в туре."
        requirementsLabel.font = UIFont.avenirNextRegular(ofSize: 14)
        requirementsLabel.numberOfLines = 0
        requirementsLabel.lineBreakMode = .byWordWrapping
        requirementsLabel.sizeToFit()

        self.addSubview(locationLabel)
        self.addSubview(seasonLabel)
        self.addSubview(quantityLabel)
        self.addSubview(durationLabel)
        self.addSubview(requirementsLabel)

        updateConstraints()
    }

    override func updateConstraints() {
        locationLabel.anchorToEdge(.top, padding: 0, width: self.frame.width, height: AutoHeight)
        seasonLabel.align(.underCentered, relativeTo: locationLabel, padding: 0, width: self.frame.width, height: AutoHeight)
        seasonLabel.alignAndFillWidth(align: .underCentered, relativeTo: locationLabel, padding: 0, height: AutoHeight)
        quantityLabel.alignAndFillWidth(align: .underCentered, relativeTo: seasonLabel, padding: 0, height: AutoHeight)
        durationLabel.alignAndFillWidth(align: .underCentered, relativeTo: quantityLabel, padding: 0, height: AutoHeight)
        // fix requirementsLabel height
        requirementsLabel.alignAndFillWidth(align: .underCentered, relativeTo: durationLabel, padding: 0, height: AutoHeight)
        height = locationLabel.frame.height+seasonLabel.frame.height+quantityLabel.frame.height+durationLabel.frame.height+requirementsLabel.frame.height
    }
}

using this for actually adding the view:

self.view.addSubview(additional)
additional.anchorAndFillEdge(.top, xPad: 0, yPad: 0, otherSize: additional.height)
additional.updateConstraints()

Solution

  • I gave up trying to solve this using Neon. The problem was essentially that you were trying to define a concrete height for your container view while at the same time trying to anchor the elements within it to each other and its margins. I ended up just using the standard Auto Layout API using layout anchors. Then, all you need to do is specify the width of your container and its height will be set up automatically based on the size of the labels it contains. The solution is as follows:

    import UIKit
    
    class AdditionalDescriptionView: UIView {
    
        lazy var locationLabel = UILabel()
        lazy var seasonLabel = UILabel()
        lazy var quantityLabel = UILabel()
        lazy var durationLabel = UILabel()
        lazy var requirementsLabel = UILabel()
        var text = NSString()
        var size = CGSize()
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            setup()
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        func setup() {
            self.translatesAutoresizingMaskIntoConstraints = false
            backgroundColor = UIColor.blue
            locationLabel.textAlignment = .left
            locationLabel.text = "Локация: Каркаралинск (200км от Караганды)"
            locationLabel.textColor = .black
            locationLabel.font = UIFont.avenirNextRegular(ofSize: 14)
            locationLabel.numberOfLines = 0
            seasonLabel.textAlignment = .left
            seasonLabel.textColor = .black
            seasonLabel.font = UIFont.avenirNextRegular(ofSize: 14)
            seasonLabel.text = "Сезоны: все"
            quantityLabel.textAlignment = .left
            quantityLabel.textColor = .black
            quantityLabel.text = "Количество людей: 5-25"
            quantityLabel.font = UIFont.avenirNextRegular(ofSize: 14)
            durationLabel.textAlignment = .left
            durationLabel.textColor = .black
            durationLabel.text = "Длительность тура: 3 суток"
            durationLabel.font = UIFont.avenirNextRegular(ofSize: 14)
            requirementsLabel.textAlignment = .left
            requirementsLabel.textColor = .black
            requirementsLabel.text = "Требования: удобная обувь, дополнительный груз не более 3кг, минимум 1л питьевой воды. Лицам с кардио- и дыхательными проблемами не рекомендуется участие в туре."
            requirementsLabel.font = UIFont.avenirNextRegular(ofSize: 14)
            requirementsLabel.numberOfLines = 0
            requirementsLabel.lineBreakMode = .byWordWrapping
    
            self.addSubview(locationLabel)
            locationLabel.translatesAutoresizingMaskIntoConstraints = false
            self.addSubview(seasonLabel)
            seasonLabel.translatesAutoresizingMaskIntoConstraints = false
            self.addSubview(quantityLabel)
            quantityLabel.translatesAutoresizingMaskIntoConstraints = false
            self.addSubview(durationLabel)
            durationLabel.translatesAutoresizingMaskIntoConstraints = false
            self.addSubview(requirementsLabel)
            requirementsLabel.translatesAutoresizingMaskIntoConstraints = false
    
            locationLabel.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
            seasonLabel.topAnchor.constraint(equalTo: locationLabel.bottomAnchor).isActive = true
            quantityLabel.topAnchor.constraint(equalTo: seasonLabel.bottomAnchor).isActive = true
            durationLabel.topAnchor.constraint(equalTo: quantityLabel.bottomAnchor).isActive = true
            requirementsLabel.topAnchor.constraint(equalTo: durationLabel.bottomAnchor).isActive = true
            requirementsLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
    
            locationLabel.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
            seasonLabel.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
            quantityLabel.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
            durationLabel.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
            requirementsLabel.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
    
            locationLabel.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
            seasonLabel.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
            quantityLabel.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
            durationLabel.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
            requirementsLabel.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
        }
    }
    
    
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            let additional = AdditionalDescriptionView()
            self.view.addSubview(additional)
            additional.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true
            additional.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
        }
    }