I am trying to build an messaging interface and faced this issue. As this is messaging app, a message bubble either align leading or trailing. I thought doing this kind if thing, creating a subview called bubbleView
which will align itself using a computed variable called bubbleViewCenterXConstraintValue
.
override var frame: CGRect {
didSet {
print(frame, bubbleView.frame)
self.setNeedsLayout()
}
}
private var bubbleViewCenterXConstraintValue: CGFloat {
print(UIScreen.main.bounds.width, contentView.frame.width, bubbleView.frame.width, ((UIScreen.main.bounds.width - bubbleView.bounds.width) / 2))
return ((UIScreen.main.bounds.width - bubbleView.bounds.width) / 2)
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .clear
contentView.backgroundColor = .systemTeal
bubbleView.addSubviews(messageLabel, hourLabel)
messageLabel.leadingAnchor.constraint(equalTo: bubbleView.leadingAnchor, constant: 12).isActive = true
messageLabel.trailingAnchor.constraint(equalTo: bubbleView.trailingAnchor, constant: -12).isActive = true
messageLabel.topAnchor.constraint(equalTo: bubbleView.topAnchor, constant: 12).isActive = true
hourLabel.trailingAnchor.constraint(equalTo: messageLabel.trailingAnchor).isActive = true
hourLabel.topAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 4).isActive = true
hourLabel.bottomAnchor.constraint(equalTo: bubbleView.bottomAnchor, constant: -12).isActive = true
contentView.addSubview(bubbleView)
bubbleView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4).isActive = true
bubbleView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -4).isActive = true
bubbleView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor, constant: bubbleViewCenterXConstraintValue).isActive = true
bubbleView.widthAnchor.constraint(lessThanOrEqualToConstant: availableMaxWidth).isActive = true
}
However, whenever bubbleViewCenterXConstraintValue
is called the frame of bubbleView
is zero so it is miscalculating my logic. Logic does not include left or right alignment right now, it just tries to align to right.
When I looked into those print results I realized another weird thing. Width of contentView
is 320 which is not equal to the UIScreen.main.bounds.width(390)
.
(0.0, 0.0, 320.0, 44.0) (0.0, 0.0, 0.0, 0.0)
390.0 320.0 0.0 195.0
(0.0, 0.0, 390.0, 100.66666666666667) (0.0, 0.0, 0.0, 0.0)
(0.0, 0.0, 390.0, 100.66666412353516) (243.66666666666666, 4.0, 292.6666666666667, 92.66666666666667)
(0.0, 0.0, 390.0, 100.66666412353516) (243.66666666666666, 4.0, 292.6666666666667, 92.66666666666667)
I need a way to make those auto-layout code to update whenever the frame of bubbleView
is set. Because I know that if its width would be 292 initially this code would work. I tried the current non working solution setNeedsLayout()
and overriding layoutSubviews
but that did not work too.
I know I am missing something and the solution is just right there, however I cant see right now and need some help.
Appreciated in advance.
Note: Also looked for a solution in the site could not find anything similar. If you think this is duplicate feel free to tell me or get in action.
I'd have thought the easier way to do this would be to set leading and trailing constraints for the bubble. This would make it easy to decide which side of the screen to align to. You could also use autolayout for the width parameter, using a multiplier based on parent view width to make it cope with any size screen.
I'm not going to work it into your code as it would be too long, but hopefully the below example function will show the principles I'd recommend:
func addBubble(alignLeft: Bool, offset: CGFloat) {
let bubble = UIView()
bubble.backgroundColor = .systemBlue
let parentView = view.safeAreaLayoutGuide //your parent view may be different
parentView.addSubview(bubble)
bubble.translatesAutoresizingMaskIntoConstraints = false
//quick and dirty top and bottom constraints for the example
bubble.topAnchor.constraint(equalTo: parentView.topAnchor, constant: 10 + offset).isActive = true
bubble.bottomAnchor.constraint(equalTo: parentView.topAnchor, constant: 40 + offset).isActive = true
//set the width of the bubble proportional to the size of the parent view
bubble.widthAnchor.constraint(equalTo: parentView.widthAnchor, multiplier: 2/3).isActive = true
// implement the left or right alignment
if alignLeft {
bubble.leadingAnchor.constraint(equalTo: parentView.leadingAnchor, constant: 4).isActive = true
} else {
bubble.trailingAnchor.constraint(equalTo: parentView.trailingAnchor, constant: -4).isActive = true
}
}
This way all you have to do is anchor the bubble's sub views to the bubbles top/bottom/leading/trailing constraints and they will also resize with the bubble to cope with all screen sizes.