Reputation: 1703
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
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
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
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)
}
}
Upvotes: 1
Views: 268
Reputation: 3325
This way you can get the width of your lablels texts:
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 :
YourView.frame = CGRect( width: size1.width + size2.width + some spacing , height : ....)
Upvotes: 1