Programmatically create UIView with size based on subviews(labels)

Alex

New Member
#1
I have a scenario where I'm needed to create an UIView completely programatically. This view is displayed as an overlay over a video feed and it contains 2 UILabels as subviews.

These labels can have varying widths and heights so I don't want to create a fixed frame for the UIView or the labels.

I've done this with programatic constrains without issue, but the problem is that the views are glitching when moving the phone and the framework that I'm using that does the video feed recommends not to use autolayout because of this exact problem.

My current code:
  • I create the view
Mã:
    func ar(_ arViewController: ARViewController, viewForAnnotation: ARAnnotation) -> ARAnnotationView {
            let annotationView = AnnotationView() //*this is the view, inherits from UIView
            annotationView.annotation = viewForAnnotation
            annotationView.delegate = self

            return annotationView
        }
  • And the actual view code
Mã:
class AnnotationView: ARAnnotationView {


        var titleLabel: UILabel?
        var distanceLabel: UILabel!
        var delegate: AnnotationViewDelegate?
        override var annotation: ARAnnotation? {
            didSet {
                loadUI()
            }
        }

        override func didMoveToSuperview() {
            super.didMoveToSuperview()

            setupUI()
        }

        private func setupUI() {
            layer.cornerRadius = 5
            layer.masksToBounds = true
            backgroundColor = .transparentWhite
        }

        private func loadUI() {
            titleLabel?.removeFromSuperview()
            distanceLabel?.removeFromSuperview()

            let label = UILabel()
            label.font = UIFont(name: "AvenirNext-Medium", size: 16.0) ?? UIFont.systemFont(ofSize: 14)
            label.numberOfLines = 2
            label.textColor = UIColor.white
            self.titleLabel = label

            distanceLabel = UILabel()
            distanceLabel.textColor = .cbPPGreen
            distanceLabel.font = UIFont(name: "AvenirNext-Medium", size: 14.0) ?? UIFont.systemFont(ofSize: 12)
            distanceLabel.textAlignment = .center

            self.translatesAutoresizingMaskIntoConstraints = false
            label.translatesAutoresizingMaskIntoConstraints = false
            distanceLabel.translatesAutoresizingMaskIntoConstraints = false

            self.addSubview(label)
            self.addSubview(distanceLabel)

            if self.constraints.isEmpty {
                NSLayoutConstraint.activate([
                    NSLayoutConstraint(item: label, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1, constant: 5),
                    NSLayoutConstraint(item: label, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: -5),
                    NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0),
                    NSLayoutConstraint(item: label, attribute: .bottom, relatedBy: .equal, toItem: distanceLabel, attribute: .top, multiplier: 1, constant: 0),

                    NSLayoutConstraint(item: distanceLabel, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1, constant: 0),
                    NSLayoutConstraint(item: distanceLabel, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: 0),
                    NSLayoutConstraint(item: distanceLabel, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0),
                    NSLayoutConstraint(item: distanceLabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 15),
                    ])
    }

            if let annotation = annotation as? BikeStationAR {
                titleLabel?.text = annotation.address
                distanceLabel?.text = String(format: "%.2f km", annotation.distanceFromUser / 1000)
            }
        }

        override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
            delegate?.didTouch(annotationView: self)
        }
    }
So my problem is now: how can I achieve the same behavior, without using autolayout?

Edit: here is a video of the behavior https://streamable.com/s/4zusq/dvbgnb

Edit 2: The solution is based on Mahgol Fa's answer and using a stack view or vertical positioning in order not to set the frame for the individual labels. So the final implementation is
Mã:
private func loadUI() {
        titleLabel?.removeFromSuperview()
        distanceLabel?.removeFromSuperview()

        let label = UILabel()
        label.font = titleViewFont
        label.numberOfLines = 0
        label.textColor = UIColor.white
        label.textAlignment = .center
        self.titleLabel = label

        distanceLabel = UILabel()
        distanceLabel.textColor = .cbPPGreen
        distanceLabel.font = subtitleViewFont
        distanceLabel.textAlignment = .center

        label.translatesAutoresizingMaskIntoConstraints = false
        distanceLabel.translatesAutoresizingMaskIntoConstraints = false

        if let annotation = annotation as? BikeStationAR {
            let title = annotation.address
            let subtitle = String(format: "%.2f km", annotation.distanceFromUser / 1000)

            let fontAttributeTitle = [NSAttributedString.Key.font: titleViewFont]
            let titleSize = title.size(withAttributes: fontAttributeTitle)

            let fontAttributeSubtitle = [NSAttributedString.Key.font: subtitleViewFont]
            let subtitleSize = annotation.address.size(withAttributes: fontAttributeSubtitle)

            titleLabel?.text = title
            distanceLabel?.text = subtitle

            let stackView = UIStackView(frame: CGRect(x: 0, y: 0, width: titleSize.width + 5, height: titleSize.height + subtitleSize.height + 5))
            stackView.axis = .vertical
            stackView.distribution = .fillProportionally
            stackView.alignment = .fill
            stackView.addArrangedSubview(titleLabel ?? UIView())
            stackView.addArrangedSubview(distanceLabel)

            frame = stackView.bounds
            addSubview(stackView)
        }
    }
 

Admin

Administrator
Thành viên BQT
#2
This way you can get the width of your lablels texts:
Mã:
let fontAttributes1= [NSAttributedStringKey.font: your declared font in your code]
   let size1 = (“your string” as String).size(withAttributes: fontAttributes1)
let fontAttributes2= [NSAttributedStringKey.font: your declared font in your code]
   let size2 = (“your string” as String).size(withAttributes: fontAttributes2)
Then :
Mã:
YourView.frame = CGRect( width: size1.width + size2.width + some spacing , height : ....)
 

Từ khóa phổ biến

You are using an out of date browser. It may not display this or other websites correctly.
You should upgrade or use an alternative browser.

Top