Image View Play Button Constraints
ElrB opened this issue · comments
Hi,
I hope you are well.
In the example app's ImageView class, I try to create and add a play button to represent video messages, but I am struggling to constrain the play button image view in the centre of the bubble. Sometimes it appears in the centre, sometimes it doesn't (appears misplaced). I have copied the class' code below. Please assist in constraining the play button correctly. Thanks.
final class ImageView: UIView, ContainerCollectionViewCellDelegate {
private lazy var stackView = UIStackView(frame: bounds)
private lazy var loadingIndicator = UIActivityIndicatorView(style: .gray)
private lazy var imageView = UIImageView(frame: bounds)
private lazy var playButtonView = UIImageView()
private var controller: ImageController!
private var imageWidthConstraint: NSLayoutConstraint?
private var imageHeightConstraint: NSLayoutConstraint?
private var viewPortWidth: CGFloat = 300
override init(frame: CGRect) {
super.init(frame: frame)
setupSubviews()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupSubviews()
}
func prepareForReuse() {
imageView.image = nil
playButtonView.isHidden = true
}
func apply(_ layoutAttributes: ChatLayoutAttributes) {
viewPortWidth = layoutAttributes.layoutFrame.width
setupSize()
}
// Uncomment to demonstrate the manual cell size calculation.
// NB: Keep in mind that the cell itself is still using autolayout to layout it content. If you really want to speed up the
// performance, You must layout the entire!!! `UICollectionCell` manually or using the tools
// like [LayoutKit](https://github.com/linkedin/LayoutKit)
// func preferredLayoutAttributesFitting(_ layoutAttributes: ChatLayoutAttributes) -> ChatLayoutAttributes? {
// viewPortWidth = layoutAttributes.layoutFrame.width
// switch controller.state {
// case .loading:
// layoutAttributes.frame.size.height = 100
// return layoutAttributes
// case let .image(image):
// let maxWidth = min(viewPortWidth * Constants.maxWidth, image.size.width)
// layoutAttributes.frame.size.height = image.size.height * maxWidth / image.size.width
// return layoutAttributes
// }
// }
func setup(with controller: ImageController) {
self.controller = controller
}
func reloadData() {
UIView.performWithoutAnimation {
switch controller.state {
case .loading:
loadingIndicator.isHidden = false
imageView.isHidden = true
imageView.image = nil
stackView.removeArrangedSubview(imageView)
stackView.addArrangedSubview(loadingIndicator)
if !loadingIndicator.isAnimating {
loadingIndicator.startAnimating()
}
if #available(iOS 13.0, *) {
backgroundColor = .systemGray5
} else {
backgroundColor = UIColor(red: 200 / 255, green: 200 / 255, blue: 200 / 255, alpha: 1)
}
setupSize()
case let .image(image):
loadingIndicator.isHidden = true
loadingIndicator.stopAnimating()
imageView.isHidden = false
imageView.image = image
stackView.removeArrangedSubview(loadingIndicator)
stackView.addArrangedSubview(imageView)
setupSize()
backgroundColor = .clear
}
switch controller.imageType {
case .videoThumb:
playButtonView.isHidden = false
case .image:
playButtonView.isHidden = true
}
}
}
private func setupSubviews() {
layoutMargins = .zero
translatesAutoresizingMaskIntoConstraints = false
insetsLayoutMarginsFromSafeArea = false
addSubview(stackView)
addSubview(playButtonView)
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
stackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor),
stackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor)
])
if #available(iOS 13.0, *) {
playButtonView.image = UIImage(systemName: "play.circle.fill")
}
playButtonView.tintColor = .darkGray
playButtonView.backgroundColor = .black
playButtonView.contentMode = .scaleAspectFill
playButtonView.layer.cornerRadius = 25
playButtonView.bounds.size = CGSize(width: 50, height: 50)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.isHidden = true
loadingIndicator.translatesAutoresizingMaskIntoConstraints = false
loadingIndicator.isHidden = true
let loadingWidthConstraint = loadingIndicator.widthAnchor.constraint(equalToConstant: 100)
loadingWidthConstraint.priority = UILayoutPriority(999)
loadingWidthConstraint.isActive = true
let loadingHeightConstraint = loadingIndicator.heightAnchor.constraint(equalToConstant: 100)
loadingHeightConstraint.priority = UILayoutPriority(999)
loadingHeightConstraint.isActive = true
imageWidthConstraint = imageView.widthAnchor.constraint(equalToConstant: 310)
imageWidthConstraint?.priority = UILayoutPriority(999)
imageHeightConstraint = imageView.heightAnchor.constraint(equalToConstant: 40)
imageHeightConstraint?.priority = UILayoutPriority(999)
playButtonView.center = center
}
private func setupSize() {
UIView.performWithoutAnimation {
switch controller.state {
case .loading:
imageWidthConstraint?.isActive = false
imageHeightConstraint?.isActive = false
playButtonView.center = center
setNeedsLayout()
case let .image(image):
imageWidthConstraint?.isActive = true
imageHeightConstraint?.isActive = true
let maxWidth = min(viewPortWidth * Constants.maxWidth, image.size.width)
imageWidthConstraint?.constant = maxWidth
imageHeightConstraint?.constant = image.size.height * maxWidth / image.size.width
playButtonView.center = center
setNeedsLayout()
}
}
}
}
@ElrB Hi. You need to use constraints based layout as the rest of the view relies on constraints. At the moments you set playButtonView.center = center
the correct view size has not yet been calculated.
Thank you. The below worked.
playButtonView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
playButtonView.centerXAnchor.constraint(equalTo: centerXAnchor),
playButtonView.centerYAnchor.constraint(equalTo: centerYAnchor),
playButtonView.widthAnchor.constraint(equalToConstant: 50),
playButtonView.heightAnchor.constraint(equalToConstant: 50)
])