Pictures disappear when I scroll down in CollectionView - ios

I have a collectionView for display posts, I have 3 different post types (Text,Image and Video). I`ve added an imageview to cell and Im using if else codes in cellForItemAt function for display imageview for Image and Video posts or hide it with heightAnchor = 0 for text posts.
its loading correct at beginning, but when I scroll down and scroll up again images heightAnchor resetting to "0" for every posts. How can I solve this issue ?
When the posts are loaded
When I Scroll down and scroll up again
TimelinePosts CollectionViewCell
class TimelinePosts: UICollectionViewCell {
let avatar: UIImageView = {
let avatar = UIImageView()
avatar.contentMode = .scaleAspectFill
avatar.clipsToBounds = true
avatar.layer.cornerRadius = 24
avatar.translatesAutoresizingMaskIntoConstraints = false
return avatar
}()
let name: UILabel = {
let name = UILabel()
name.numberOfLines = 1
name.translatesAutoresizingMaskIntoConstraints = false
return name
}()
let content: ActiveLabel = {
let content = ActiveLabel()
content.numberOfLines = 0
content.font = UIFont.systemFont(ofSize: 15)
content.translatesAutoresizingMaskIntoConstraints = false
return content
}()
let image: UIImageView = {
let image = UIImageView()
image.contentMode = .scaleAspectFill
image.clipsToBounds = true
image.layer.cornerRadius = 12
image.translatesAutoresizingMaskIntoConstraints = false
return image
}()
let time: UILabel = {
let time = UILabel()
time.numberOfLines = 1
time.textColor = .gray
time.font = UIFont.systemFont(ofSize: 14)
time.textAlignment = .center
time.translatesAutoresizingMaskIntoConstraints = false
return time
}()
let moreButton: UIButton = {
let button = UIButton()
let image = UIImage(named: "arrow")
button.setImage(image, for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
let favoriteButton: FaveButton = {
let button = FaveButton(frame: CGRect(x:0, y:0, width: 28, height: 28), faveIconNormal: UIImage(named: "favorite"))
button.normalColor = UIColor(hexString: "#CBCBCB")
button.selectedColor = UIColor(hexString: "#FFBE00")
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
let boostButton: FaveButton = {
let button = FaveButton(frame: CGRect(x:0, y:0, width: 28, height: 28), faveIconNormal: UIImage(named: "boost-pressed"))
button.normalColor = UIColor(hexString: "#CBCBCB")
button.selectedColor = UIColor(hexString: "#6e00ff")
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
let actions: UILabel = {
let view = UILabel()
view.numberOfLines = 1
view.textAlignment = .left
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
addViews()
setupViews()
}
func addViews(){
addSubview(avatar)
addSubview(moreButton)
addSubview(time)
addSubview(name)
addSubview(content)
addSubview(image)
addSubview(favoriteButton)
addSubview(boostButton)
addSubview(actions)
}
func setupViews(){
avatar.leftAnchor.constraint(equalTo: leftAnchor, constant: 15).isActive = true
avatar.topAnchor.constraint(equalTo: topAnchor, constant: 15).isActive = true
avatar.widthAnchor.constraint(equalToConstant: 48).isActive = true
avatar.heightAnchor.constraint(equalToConstant: 48).isActive = true
moreButton.rightAnchor.constraint(equalTo: rightAnchor, constant: -18).isActive = true
moreButton.topAnchor.constraint(equalTo: topAnchor, constant: 15).isActive = true
moreButton.widthAnchor.constraint(equalToConstant: 14).isActive = true
moreButton.heightAnchor.constraint(equalToConstant: 14).isActive = true
time.leftAnchor.constraint(equalTo: leftAnchor, constant: 15).isActive = true
time.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10).isActive = true
time.widthAnchor.constraint(equalToConstant: 48).isActive = true
time.heightAnchor.constraint(equalToConstant: 20).isActive = true
name.leftAnchor.constraint(equalTo: avatar.rightAnchor, constant: 10).isActive = true
name.rightAnchor.constraint(equalTo: moreButton.leftAnchor, constant: -14).isActive = true
name.topAnchor.constraint(equalTo: topAnchor, constant: 15).isActive = true
name.heightAnchor.constraint(equalToConstant: 20).isActive = true
content.leftAnchor.constraint(equalTo: leftAnchor, constant: 73).isActive = true
content.rightAnchor.constraint(equalTo: rightAnchor, constant: -46).isActive = true
content.topAnchor.constraint(equalTo: name.bottomAnchor, constant: 5).isActive = true
image.leftAnchor.constraint(equalTo: leftAnchor, constant: 73).isActive = true
image.rightAnchor.constraint(equalTo: rightAnchor, constant: -46).isActive = true
image.topAnchor.constraint(equalTo: content.bottomAnchor, constant: 5).isActive = true
image.heightAnchor.constraint(equalToConstant: ((UIScreen.main.bounds.width - 120) * 2) / 3).isActive = true
actions.leftAnchor.constraint(equalTo: avatar.rightAnchor, constant: 10).isActive = true
actions.rightAnchor.constraint(equalTo: moreButton.leftAnchor, constant: -14).isActive = true
actions.topAnchor.constraint(equalTo: image.bottomAnchor, constant: 10).isActive = true
actions.heightAnchor.constraint(equalToConstant: 20).isActive = true
actions.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10).isActive = true
favoriteButton.rightAnchor.constraint(equalTo: rightAnchor, constant: -14).isActive = true
favoriteButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10).isActive = true
favoriteButton.widthAnchor.constraint(equalToConstant: 20).isActive = true
favoriteButton.heightAnchor.constraint(equalToConstant: 20).isActive = true
boostButton.rightAnchor.constraint(equalTo: rightAnchor, constant: -14).isActive = true
boostButton.bottomAnchor.constraint(equalTo: favoriteButton.topAnchor, constant: -12).isActive = true
boostButton.widthAnchor.constraint(equalToConstant: 20).isActive = true
boostButton.heightAnchor.constraint(equalToConstant: 20).isActive = true
/* name.backgroundColor = .yellow
content.backgroundColor = .red
image.backgroundColor = .green */
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
And cellForItemAt function
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TimelinePosts", for: indexPath) as! TimelinePosts
if indexPath.row < id.count{
cell.avatar.sd_setImage(with: URL(string: avatars[indexPath.row]))
cell.time.text = hours[indexPath.row]
cell.favoriteButton.isSelected = isLike[indexPath.row]
cell.favoriteButton.isUserInteractionEnabled = true
cell.favoriteButton.tag = indexPath.row
cell.boostButton.isSelected = isBoost[indexPath.row]
cell.boostButton.isUserInteractionEnabled = true
cell.boostButton.tag = indexPath.row
let selectedArrowTap = UITapGestureRecognizer(target: self, action: #selector(self.selectedArrow))
selectedArrowTap.numberOfTapsRequired = 1
cell.moreButton.isUserInteractionEnabled = true
cell.moreButton.tag = indexPath.row
cell.moreButton.addGestureRecognizer(selectedArrowTap)
cell.content.customize { label in
label.text = content[indexPath.row]
label.hashtagColor = UIColor(hexString: "#6e00ff")
label.mentionColor = UIColor(hexString: "#6e00ff")
label.URLColor = UIColor(hexString: "#0366d6")
}
cell.content.handleHashtagTap { hashtag in
print("Success. You just tapped the \(hashtag) hashtag")
}
cell.content.handleURLTap { url in
let urlString = url.absoluteString
if urlString.hasPrefix("http://")
{
let openURL = URL(string: urlString)!
let svc = SFSafariViewController(url: openURL)
self.present(svc, animated: true, completion: nil)
}
else if urlString.hasPrefix("https://")
{
let openURL = URL(string: urlString)!
let svc = SFSafariViewController(url: openURL)
self.present(svc, animated: true, completion: nil)
}
else
{
let openURL = URL(string: "https://" + urlString)!
let svc = SFSafariViewController(url: openURL)
self.present(svc, animated: true, completion: nil)
}
}
if types[indexPath.row] == 2{
cell.image.sd_setImage(with: URL(string: images[indexPath.row]))
let selectedImageTap = UITapGestureRecognizer(target: self, action: #selector(self.photoZoom))
selectedImageTap.numberOfTapsRequired = 1
cell.image.isUserInteractionEnabled = true
cell.image.tag = indexPath.row
cell.image.addGestureRecognizer(selectedImageTap)
}else if types[indexPath.row] == 3{
cell.image.sd_setImage(with: URL(string: "https://i.ytimg.com/vi/\(self.extractYoutubeIdFromLink(link: videos[indexPath.row])!)/mqdefault.jpg"))
let playBtn = UIImageView()
playBtn.image = UIImage(named: "youtube-play")
playBtn.tag = indexPath.row
playBtn.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.videoPlay)))
playBtn.isUserInteractionEnabled = true
playBtn.translatesAutoresizingMaskIntoConstraints = false
cell.image.isUserInteractionEnabled = true
cell.image.addSubview(playBtn)
playBtn.centerXAnchor.constraint(equalTo: cell.image.centerXAnchor).isActive = true
playBtn.centerYAnchor.constraint(equalTo: cell.image.centerYAnchor).isActive = true
playBtn.widthAnchor.constraint(equalToConstant: 74).isActive = true
playBtn.heightAnchor.constraint(equalToConstant: 52).isActive = true
}else{
cell.image.heightAnchor.constraint(equalToConstant: 0).isActive = true
}
}
return cell
}

you should not modify image height by
cell.image.heightAnchor.constraint(equalToConstant: 0).isActive = true
create a new variable imageHeightConstraint in you TimelinesPosts,
set this variable in setupViews()
imageHeightConstraint = image.heightAnchor.constraint(equalToConstant: ((UIScreen.main.bounds.width - 120) * 2) / 3).isActive = true
then change the height by this
// don't miss this code.
imageHeightConstraint.constant = ((UIScreen.main.bounds.width - 120) * 2) / 3
if types[indexPath.row] == 2 {
//***************
} else if types[indexPath.row] == 3 {
//***************
} else {
imageHeightConstraint.constant = 0
}

UIScrollView is parent UICollectionView. You use UIScrollView with UIScrollViewDelegate for setup images with va scrollOffset: CGFloat follow .y (referent code)
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
var scrollOffset : CGFloat = scrollView.contentOffset.y
// process with scrollOffset
}
}

Related

How to add UIView Gesture - Programmatically UIView

I have searched a lot about adding a gesture on UIView when it is tapped. There are many solutions but none of them worked for me.
I have made UIView class programmatically and using the view in different classes.
Here is my class of UIView:
class PaymentServiceView: UIView {
private let views: UIView = {
let view = UIView()
view.backgroundColor = .white
// view.layer.maskedCorners = [.layerMinXMinYCorner,.layerMaxXMinYCorner]
view.layer.borderColor = UIColor.gray.cgColor
view.layer.shadowOpacity = 0.3
view.layer.shadowColor = UIColor.gray.cgColor
view.layer.shadowRadius = 10
view.layer.borderWidth = 0.1
view.layer.cornerRadius = 20
view.isUserInteractionEnabled = true
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let titleLbl: UILabel = {
var label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.textColor = UIColor.black
label.font = UIFont(name: AppFontName.circularStdRegular, size: 18)
label.clipsToBounds = true
return label
}()
private let subLbl: UILabel = {
var label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.textColor = UIColor.gray
label.numberOfLines = 0
label.textAlignment = .left
label.font = UIFont(name: AppFontName.robotoRegular, size: 15)
label.clipsToBounds = true
return label
}()
private let image: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.clipsToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
private let btnImage: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.clipsToBounds = true
imageView.image = UIImage(named: IconName.chevron_down)?.transform(withNewColor: UIColor.btnGray)
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
init(titleLabel: String, subTitleLabel: String, imageName: String) {
super.init(frame: CGRect.zero)
self.addSubview(views)
self.views.addSubview(btnImage)
self.views.addSubview(titleLbl)
self.views.addSubview(image)
self.views.addSubview(subLbl)
titleLbl.text = titleLabel
image.image = UIImage(named: imageName)
subLbl.text = subTitleLabel
setupConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupConstraints() {
self.views.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
self.views.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
self.views.heightAnchor.constraint(equalToConstant: 150).isActive = true
self.views.widthAnchor.constraint(equalToConstant: 320).isActive = true
self.image.centerYAnchor.constraint(equalTo: self.views.centerYAnchor).isActive = true
self.image.leadingAnchor.constraint(equalTo: self.views.leadingAnchor, constant: 15).isActive = true
self.image.widthAnchor.constraint(equalToConstant: 30).isActive = true
self.image.heightAnchor.constraint(equalToConstant: 30).isActive = true
self.titleLbl.centerYAnchor.constraint(equalTo: self.views.centerYAnchor, constant: -35).isActive = true
self.titleLbl.leadingAnchor.constraint(equalTo: image.trailingAnchor, constant: 15).isActive = true
self.subLbl.topAnchor.constraint(equalTo: titleLbl.bottomAnchor, constant: 5).isActive = true
self.subLbl.leadingAnchor.constraint(equalTo: image.trailingAnchor, constant: 15).isActive = true
self.subLbl.trailingAnchor.constraint(equalTo: btnImage.leadingAnchor, constant: -15).isActive = true
btnImage.topAnchor.constraint(equalTo: views.topAnchor, constant: 55).isActive = true
btnImage.rightAnchor.constraint(equalTo: views.rightAnchor, constant: -10).isActive = true
btnImage.heightAnchor.constraint(equalToConstant: 10).isActive = true
}
}
Now Im using this UIView class in PaymentServicesViewController
class PaymentServicesViewController: UIViewController {
private (set) lazy var headerView: HeaderView = { [unowned self] in
let view = HeaderView.init(titleLbl: Headings.paymentService, closeAction: {
self.navigationController?.popViewController(animated: true)
}, nextAction: {
print("next")
}, previousAction: {
self.navigationController?.popViewController(animated: true)
})
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let fundTransferView: PaymentServiceView = {
let view = PaymentServiceView(titleLabel: Headings.fundTransfer, subTitleLabel: Description.fundTransferDecription , imageName: IconName.fundImage )
view.isUserInteractionEnabled = true
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let subscriptionView: PaymentServiceView = {
let view = PaymentServiceView(titleLabel: Headings.subscribe, subTitleLabel: Description.subscriptionDescription, imageName: IconName.subImage )
view.isUserInteractionEnabled = true
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let billPaymentView: PaymentServiceView = {
let view = PaymentServiceView(titleLabel: Headings.billPayment, subTitleLabel: Description.billPaymentDescription , imageName: IconName.billImage )
view.isUserInteractionEnabled = true
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .gray2
let fundtrasnferGesture = UITapGestureRecognizer(target: self, action: #selector(fundTranferTapped))
fundTransferView.isUserInteractionEnabled = true
self.fundTransferView.addGestureRecognizer(fundtrasnferGesture)
let subscribeGesture = UITapGestureRecognizer(target: self, action: #selector(subscribeTapped))
subscriptionView.isUserInteractionEnabled = true
self.subscriptionView.addGestureRecognizer(subscribeGesture)
let billPaymentGesture = UITapGestureRecognizer(target: self, action: #selector(billPaymentTapped))
fundTransferView.isUserInteractionEnabled = true
self.billPaymentView.addGestureRecognizer(billPaymentGesture)
view.addSubview(headerView)
view.addSubview(subscriptionView)
view.addSubview(fundTransferView)
view.addSubview(billPaymentView)
setupConstraint()
}
#objc func fundTranferTapped(sender: UITapGestureRecognizer) {
print("FundTransfer Tapped.")
}
#objc func subscribeTapped(sender: UITapGestureRecognizer) {
print("Subscribe Tapped.")
}
#objc func billPaymentTapped(sender: UITapGestureRecognizer) {
print("BillPayment Tapped.")
}
private func setupConstraint() {
headerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
headerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
headerView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
headerView.heightAnchor.constraint(equalToConstant: 100).isActive = true
subscriptionView.trailingAnchor.constraint(equalTo: fundTransferView.trailingAnchor, constant: 0).isActive = true
subscriptionView.leadingAnchor.constraint(equalTo: fundTransferView.leadingAnchor, constant: 0).isActive = true
subscriptionView.bottomAnchor.constraint(equalTo: fundTransferView.topAnchor, constant: -130).isActive = true
fundTransferView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0).isActive = true
fundTransferView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0).isActive = true
billPaymentView.leadingAnchor.constraint(equalTo: fundTransferView.leadingAnchor, constant: 0).isActive = true
billPaymentView.trailingAnchor.constraint(equalTo: fundTransferView.trailingAnchor).isActive = true
billPaymentView.topAnchor.constraint(equalTo: fundTransferView.bottomAnchor, constant: 130).isActive = true
}
}
I know there is a small mistake I'm doing but not sure. Help would be appreciated, Thank you.
You are missing a couple constraints...
If you add this at the end of viewDidLoad():
subscriptionView.clipsToBounds = true
fundTransferView.clipsToBounds = true
billPaymentView.clipsToBounds = true
You'll see that the views "disappear":
because they have no Width or Height constraints (so their size is .zero).
In setupConstraints() in your PaymentServiceView class, add these two lines:
self.widthAnchor.constraint(equalTo: self.views.widthAnchor).isActive = true
self.heightAnchor.constraint(equalTo: self.views.heightAnchor).isActive = true
Now, the PaymentServiceView will be the same Width and Height as self.views view.
However, now that the views have Heights:
your layout needs to be adjusted.
Change the .clipsToBounds back to false (remove those added lines) so the shadows won't be clipped, and change these constraints in your controller (adjust the 15 and -15 to your liking):
//subscriptionView.bottomAnchor.constraint(equalTo: fundTransferView.topAnchor, constant: -130).isActive = true
subscriptionView.bottomAnchor.constraint(equalTo: fundTransferView.topAnchor, constant: 15).isActive = true
//billPaymentView.topAnchor.constraint(equalTo: fundTransferView.bottomAnchor, constant: 130).isActive = true
billPaymentView.topAnchor.constraint(equalTo: fundTransferView.bottomAnchor, constant: -15).isActive = true
and we get:
Now, because the views have a size, the Tap Gestures will work.

How do I make my collectionView UICollectionViewCell a dynamic height?

I have a collectionview and the cells in the collectionview are stretching their height. I used UICollectionViewCompositionalLayout so similar questions asked on this site did not seem to apply.
Here is where I create my UICollectionViewCompositionalLayout
private func configureLayout() -> UICollectionViewCompositionalLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(400))
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
return UICollectionViewCompositionalLayout(section: section)
}
Here is the View I am trying to display in the cell.
class PostCell: UICollectionViewCell {
static let reuseIdentifier = String(describing: PostCell.self)
private lazy var textPost : TextPostView = {
let textPost = TextPostView()
textPost.translatesAutoresizingMaskIntoConstraints = false
addSubview(textPost)
textPost.topAnchor.constraint(equalTo: topAnchor).isActive = true
textPost.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
textPost.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
textPost.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
return textPost
}()
private lazy var mediaPost : MediaPostView = {
let mediaPost = MediaPostView()
mediaPost.translatesAutoresizingMaskIntoConstraints = false
addSubview(mediaPost)
mediaPost.topAnchor.constraint(equalTo: topAnchor).isActive = true
mediaPost.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
mediaPost.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
mediaPost.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
return mediaPost
}()
private lazy var eventPost : EventPostView = {
let eventPost = EventPostView()
eventPost.translatesAutoresizingMaskIntoConstraints = false
addSubview(eventPost)
eventPost.topAnchor.constraint(equalTo: topAnchor).isActive = true
eventPost.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
eventPost.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
eventPost.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
return eventPost
}()
func setData(_ presentable: PostPresentable) {
switch presentable.type {
case .text:
eventPost.isHidden = true
mediaPost.isHidden = true
textPost.isHidden = false
textPost.setData(presentable)
case .media:
eventPost.isHidden = true
mediaPost.isHidden = false
textPost.isHidden = true
mediaPost.setData(presentable)
case .event:
eventPost.isHidden = false
mediaPost.isHidden = true
textPost.isHidden = true
eventPost.setData(presentable)
}
}
}
Here is the TextPostView that I display in the cell
import Foundation
import UIKit
class TextPostView: UIView {
lazy var headerView: PostHeaderView = {
let headerView = PostHeaderView()
headerView.translatesAutoresizingMaskIntoConstraints = false
addSubview(headerView)
let margin: CGFloat = 2
headerView.topAnchor.constraint(equalTo: topAnchor, constant: margin).isActive = true
headerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: margin).isActive = true
headerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: margin).isActive = true
return headerView
}()
lazy var descriptionView: UILabel = {
let description = UILabel()
description.translatesAutoresizingMaskIntoConstraints = false
description.font = UIFont.preferredFont(forTextStyle: .body)
description.numberOfLines = 0
let headerViewLayoutGuide = headerView.layoutGuide(self)
addSubview(description)
let margin: CGFloat = 8
description.topAnchor.constraint(equalTo: headerViewLayoutGuide.bottomAnchor, constant: margin).isActive = true
description.leadingAnchor.constraint(equalTo: leadingAnchor,constant: margin).isActive = true
description.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -margin).isActive = true
return description
}()
lazy var footerView: PostFooterView = {
let footer = PostFooterView()
footer.translatesAutoresizingMaskIntoConstraints = false
addSubview(footer)
let descriptionViewLayoutGuide = descriptionView.layoutGuide(self)
footer.topAnchor.constraint(equalTo: descriptionViewLayoutGuide.bottomAnchor, constant: 8).isActive = true
footer.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
footer.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
footer.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
return footer
}()
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
initialize()
}
private func initialize() {
layer.borderColor = UIColor.gray.cgColor
layer.borderWidth = 1
layer.cornerRadius = 16
}
func setData(_ presentable: PostPresentable) {
headerView.setData(presentable.header)
footerView.setData(presentable.footer)
descriptionView.text = presentable.description
}
}
The PostHeaderView that the post uses
import Foundation
import UIKit
import SDWebImage
class PostHeaderView: UIView {
private static let horizontalMargin: CGFloat = 8
private static let iconHeight: CGFloat = 20
private lazy var date: UILabel = {
let label = UILabel()
label.font = UIFont.preferredFont(forTextStyle: .caption2)
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: PostHeaderView.horizontalMargin).isActive = true
label.topAnchor.constraint(equalTo: topAnchor).isActive = true
return label
}()
private lazy var flairsContainer: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.distribution = .fill
stackView.alignment = .trailing
stackView.translatesAutoresizingMaskIntoConstraints = false
let dateLayoutGuide = UILayoutGuide()
let shareLayoutGuide = UILayoutGuide()
addLayoutGuide(dateLayoutGuide)
addLayoutGuide(shareLayoutGuide)
addSubview(stackView)
dateLayoutGuide.trailingAnchor.constraint(equalTo: date.trailingAnchor).isActive = true
shareLayoutGuide.leadingAnchor.constraint(equalTo: shareButton.leadingAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: topAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: dateLayoutGuide.trailingAnchor, constant: 16).isActive = true
stackView.trailingAnchor.constraint(equalTo: shareLayoutGuide.leadingAnchor, constant: -16).isActive = true
stackView.heightAnchor.constraint(equalToConstant: PostHeaderView.iconHeight).isActive = true
return stackView
}()
private lazy var title: UILabel = {
let title = UILabel()
title.translatesAutoresizingMaskIntoConstraints = false
title.font = UIFont.preferredFont(forTextStyle: .title1)
let avatarLayoutGuide = avatar.layoutGuide(self)
addSubview(title)
title.leadingAnchor.constraint(equalTo: avatarLayoutGuide.trailingAnchor, constant: 4).isActive = true
title.trailingAnchor.constraint(equalTo: trailingAnchor, constant: PostHeaderView.horizontalMargin).isActive = true
title.centerYAnchor.constraint(equalTo: avatarLayoutGuide.centerYAnchor).isActive = true
return title
}()
private lazy var subHeader: UILabel = {
let subHeader = UILabel()
subHeader.translatesAutoresizingMaskIntoConstraints = false
subHeader.font = UIFont.preferredFont(forTextStyle: .caption1)
let titleLayoutGuide = title.layoutGuide(self)
addSubview(subHeader)
subHeader.topAnchor.constraint(equalTo: titleLayoutGuide.bottomAnchor, constant: 0).isActive = true
subHeader.leadingAnchor.constraint(equalTo: titleLayoutGuide.leadingAnchor).isActive = true
return subHeader
}()
private lazy var checkMark: UIImageView = {
let checkMark = UIImageView()
checkMark.translatesAutoresizingMaskIntoConstraints = false
let image = UIImage(named: "VerifiedBadge")
checkMark.image = image
let subHeaderLayoutGuide = subHeader.layoutGuide(self)
addSubview(checkMark)
checkMark.leadingAnchor.constraint(equalTo: subHeaderLayoutGuide.trailingAnchor).isActive = true
checkMark.centerYAnchor.constraint(equalTo: subHeaderLayoutGuide.centerYAnchor).isActive = true
return checkMark
}()
private lazy var menuButton: UIButton = {
let button = UIButton(type: .custom)
button.translatesAutoresizingMaskIntoConstraints = false
button.setImage(UIImage(named: "menu"), for: .normal)
addSubview(button)
button.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -PostHeaderView.horizontalMargin).isActive = true
button.topAnchor.constraint(equalTo: topAnchor).isActive = true
button.heightAnchor.constraint(equalToConstant: PostHeaderView.iconHeight).isActive = true
return button
}()
private lazy var shareButton: UIButton = {
let shareButton = UIButton()
shareButton.translatesAutoresizingMaskIntoConstraints = false
let buttonImage = UIImage(systemName: "square.and.arrow.up")?.withRenderingMode(.alwaysTemplate)
shareButton.setImage(buttonImage, for: .normal)
shareButton.tintColor = UIColor.black
let menuLayoutGuide = UILayoutGuide()
addLayoutGuide(menuLayoutGuide)
addSubview(shareButton)
menuLayoutGuide.leadingAnchor.constraint(equalTo: menuButton.leadingAnchor).isActive = true
shareButton.trailingAnchor.constraint(equalTo: menuLayoutGuide.leadingAnchor, constant: -16).isActive = true
shareButton.topAnchor.constraint(equalTo: topAnchor).isActive = true
shareButton.heightAnchor.constraint(equalToConstant: 20).isActive = true
return shareButton
}()
private lazy var avatar: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
let imageViewSize: CGFloat = 52
imageView.layer.cornerRadius = imageViewSize / 2
imageView.clipsToBounds = true
let dateLayoutGuide = UILayoutGuide()
addLayoutGuide(dateLayoutGuide)
addSubview(imageView)
dateLayoutGuide.bottomAnchor.constraint(equalTo: date.bottomAnchor).isActive = true
imageView.heightAnchor.constraint(equalToConstant: imageViewSize).isActive = true
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true
imageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: PostHeaderView.horizontalMargin).isActive = true
imageView.topAnchor.constraint(equalTo: dateLayoutGuide.bottomAnchor, constant: 2).isActive = true
imageView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
func setData(_ presentable: PostHeaderPresentable) {
date.text = presentable.date
avatar.sd_setImage(with: presentable.avatarImageUrl)
title.text = presentable.title
addFlairs(presentable.flairs)
menuButton.isHidden = false
shareButton.isHidden = false
if let subheadline = presentable.subheadline {
subHeader.isHidden = subheadline.text.isEmpty
subHeader.text = subheadline.text
checkMark.isHidden = !subheadline.verified
} else {
checkMark.isHidden = true
subHeader.isHidden = true
}
}
private func addFlairs(_ flairImages: [UIImage]) {
flairsContainer.removeAllArrangedSubviews()
for flairImage in flairImages {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.image = flairImage
imageView.contentMode = .scaleAspectFill
flairsContainer.addArrangedSubview(imageView)
imageView.heightAnchor.constraint(equalToConstant: PostHeaderView.iconHeight).isActive = true
imageView.widthAnchor.constraint(equalToConstant: PostHeaderView.iconHeight).isActive = true
}
}
}
The PostFooterView
class PostFooterView: UIView {
private static let horizontalMargin: CGFloat = 8
private static let verticalMargin: CGFloat = 16
private lazy var location: UILabel = {
let label = UILabel()
label.font = UIFont.preferredFont(forTextStyle: .subheadline)
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -PostFooterView.horizontalMargin).isActive = true
label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -PostFooterView.verticalMargin).isActive = true
return label
}()
private lazy var likeButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
let likeImageSelected = UIImage(named: "Like")?.withRenderingMode(.alwaysTemplate)
let likeImage = UIImage(named: "LikeStroke")?.withRenderingMode(.alwaysTemplate)
button.setImage(likeImageSelected, for: .selected)
button.setImage(likeImage, for: .normal)
button.tintColor = UIColor.black
addSubview(button)
button.leadingAnchor.constraint(equalTo: leadingAnchor, constant: PostFooterView.horizontalMargin).isActive = true
button.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -PostFooterView.verticalMargin).isActive = true
return button
}()
private lazy var dislikeButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
let dislikeImageSelected = UIImage(named: "Dislike")?.withRenderingMode(.alwaysTemplate)
let dislikeImage = UIImage(named: "DislikeStroke")?.withRenderingMode(.alwaysTemplate)
button.setImage(dislikeImageSelected, for: .selected)
button.setImage(dislikeImage, for: .normal)
button.tintColor = UIColor.black
addSubview(button)
let likeButtonLayoutGuide = likeButton.layoutGuide(self)
button.leadingAnchor.constraint(equalTo: likeButtonLayoutGuide.trailingAnchor, constant: 8).isActive = true
button.bottomAnchor.constraint(equalTo: likeButtonLayoutGuide.bottomAnchor).isActive = true
return button
}()
private lazy var navButton : UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
let navigateImage = UIImage(named: "Navigate")?.withRenderingMode(.alwaysTemplate)
button.setImage(navigateImage, for: .normal)
button.setTitle("Navigate", for: .normal)
button.tintColor = UIColor.black
button.setTitleColor(UIColor.black, for: .normal)
addSubview(button)
let likeButtonLayoutGuide = likeButton.layoutGuide(self)
topAnchor.constraint(equalTo: button.topAnchor).isActive = true
button.leadingAnchor.constraint(equalTo: leadingAnchor, constant: PostFooterView.horizontalMargin).isActive = true
button.bottomAnchor.constraint(equalTo: likeButtonLayoutGuide.topAnchor, constant: -20).isActive = true
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
func setData(_ presentable: PostFooterPresentable) {
location.text = presentable.location
navButton.isHidden = !presentable.showNavigation
likeButton.isSelected = presentable.reaction == .like
dislikeButton.isSelected = presentable.reaction == .dislike
}
}
Here is a screenshot of how it looks

Can't Tap UIView in Nested ScrollView

I have a vertical UIScrollView, allScrollView, and a horizontal UIScrollView, hourlyScrollView, nested inside the vertical UIScrollView. In each of the scroll views I have 10 other UIViews that will show data. Each of the views are assigned a UITapGestureRecognizer. I'm able to tap the views that are only in the vertical scroll, but none of the views in the nested horizontal scroll do anything when tapped. If anyone can help me it would be greatly appreciated as I've tried a lot of suggestions on here with no luck.
my view hierarchy:
-allScrollView (vertical)
-allContentView
-hourlyScrollView (horizontal)
-hourlyContentView
-10 UIViews
-dailyContentView
-10 UIViews
viewDidLoad()
let dailyContentView = UIView()
let hourlyContentView = UIView()
let hourlyScrollView = UIScrollView()
let allContentView = UIView()
let allScrollView = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad()
hourlyScrollView.translatesAutoresizingMaskIntoConstraints = false
hourlyContentView.translatesAutoresizingMaskIntoConstraints = false
allContentView.translatesAutoresizingMaskIntoConstraints = false
allScrollView.translatesAutoresizingMaskIntoConstraints = false
dailyContentView.translatesAutoresizingMaskIntoConstraints = false
layoutHourlyViews()
layoutDailyViews()
finishLayout()
}
create horizontal scroll content
func layoutHourlyViews() {
for subview in hourlyScrollView.subviews {
subview.removeFromSuperview()
}
for subView in hourlyContentView.subviews {
subView.removeFromSuperview()
}
var previousHour : UIView? = nil
for count in 1...10 {
let hourlyUIView = UIView()
hourlyUIView.backgroundColor = .blue
hourlyUIView.isUserInteractionEnabled = true
hourlyUIView.tag = count
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(hourlyTap(_:)))
hourlyUIView.addGestureRecognizer(tapGesture)
let descriptionLabel = UILabel()
let borderView = UIView()
let borderViewTop = UIView()
let borderViewBottom = UIView()
borderViewTop.backgroundColor = .black
borderViewBottom.backgroundColor = .black
borderViewTop.translatesAutoresizingMaskIntoConstraints = false
borderViewBottom.translatesAutoresizingMaskIntoConstraints = false
borderView.translatesAutoresizingMaskIntoConstraints = false
hourlyUIView.translatesAutoresizingMaskIntoConstraints = false
descriptionLabel.translatesAutoresizingMaskIntoConstraints = false
descriptionLabel.text = "test"
borderView.backgroundColor = .black
hourlyUIView.addSubview(borderViewBottom)
hourlyUIView.addSubview(borderViewTop)
hourlyUIView.addSubview(descriptionLabel)
hourlyUIView.addSubview(borderView)
borderViewBottom.leadingAnchor.constraint(equalTo: hourlyUIView.leadingAnchor).isActive = true
borderViewBottom.trailingAnchor.constraint(equalTo: hourlyUIView.trailingAnchor).isActive = true
borderViewBottom.bottomAnchor.constraint(equalTo: hourlyUIView.bottomAnchor).isActive = true
borderViewBottom.heightAnchor.constraint(equalToConstant: 2).isActive = true
borderViewTop.leadingAnchor.constraint(equalTo: hourlyUIView.leadingAnchor).isActive = true
borderViewTop.trailingAnchor.constraint(equalTo: hourlyUIView.trailingAnchor).isActive = true
borderViewTop.topAnchor.constraint(equalTo: hourlyUIView.topAnchor).isActive = true
borderViewTop.heightAnchor.constraint(equalToConstant: 2).isActive = true
hourlyUIView.widthAnchor.constraint(equalToConstant: 160).isActive = true
hourlyUIView.heightAnchor.constraint(equalToConstant: 240).isActive = true
descriptionLabel.topAnchor.constraint(equalTo: hourlyUIView.topAnchor, constant: 16).isActive = true
descriptionLabel.centerXAnchor.constraint(equalTo: hourlyUIView.centerXAnchor, constant: 0).isActive = true
hourlyContentView.addSubview(hourlyUIView)
if previousHour == nil {
hourlyUIView.topAnchor.constraint(equalTo: hourlyContentView.topAnchor, constant: 0).isActive = true
hourlyUIView.leadingAnchor.constraint(equalTo: hourlyContentView.leadingAnchor, constant: 2).isActive = true
}
else {
hourlyUIView.topAnchor.constraint(equalTo: hourlyContentView.topAnchor, constant: 0).isActive = true
hourlyUIView.leadingAnchor.constraint(equalTo: previousHour!.trailingAnchor, constant: 0).isActive = true
borderView.bottomAnchor.constraint(equalTo: hourlyUIView.bottomAnchor).isActive = true
borderView.topAnchor.constraint(equalTo: hourlyUIView.topAnchor).isActive = true
borderView.widthAnchor.constraint(equalToConstant: 2).isActive = true
borderView.leadingAnchor.constraint(equalTo: hourlyUIView.leadingAnchor).isActive = true
}
previousHour = hourlyUIView
}
let borderViewTop = UIView()
let borderViewBottom = UIView()
borderViewTop.backgroundColor = .black
borderViewBottom.backgroundColor = .black
borderViewTop.translatesAutoresizingMaskIntoConstraints = false
borderViewBottom.translatesAutoresizingMaskIntoConstraints = false
hourlyScrollView.addSubview(hourlyContentView)
hourlyContentView.leadingAnchor.constraint(equalTo: hourlyScrollView.leadingAnchor, constant: 0).isActive = true
hourlyContentView.topAnchor.constraint(equalTo: hourlyScrollView.topAnchor, constant: 0).isActive = true
hourlyContentView.trailingAnchor.constraint(equalTo: hourlyScrollView.trailingAnchor, constant: 0).isActive = true
hourlyContentView.bottomAnchor.constraint(equalTo: hourlyScrollView.bottomAnchor, constant: 0).isActive = true
allContentView.addSubview(hourlyScrollView)
hourlyScrollView.isScrollEnabled = true
hourlyScrollView.topAnchor.constraint(equalTo: allContentView.topAnchor, constant: 20).isActive = true
hourlyScrollView.leadingAnchor.constraint(equalTo: allContentView.leadingAnchor, constant: 0).isActive = true
hourlyScrollView.trailingAnchor.constraint(equalTo: allContentView.trailingAnchor, constant: 0).isActive = true
}
create vertical scroll content
func layoutDailyViews() {
for subview in dailyContentView.subviews {
subview.removeFromSuperview()
}
var previousDay : UIView? = nil
for count in 1...10 {
let dailyUIView = UIView()
//dailyUIView.isUserInteractionEnabled = true
dailyUIView.tag = count
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dailyTap(_:)))
dailyUIView.addGestureRecognizer(tapGesture)
//hourlyUIView.frame.size = CGSize(width: 500, height: 50)
let descriptionLabel = UILabel()
dailyUIView.translatesAutoresizingMaskIntoConstraints = false
descriptionLabel.translatesAutoresizingMaskIntoConstraints = false
descriptionLabel.text = "daily test"
let borderView = UIView()
borderView.translatesAutoresizingMaskIntoConstraints = false
borderView.backgroundColor = .black
dailyUIView.addSubview(descriptionLabel)
dailyUIView.addSubview(borderView)
borderView.bottomAnchor.constraint(equalTo: dailyUIView.bottomAnchor).isActive = true
borderView.heightAnchor.constraint(equalToConstant: 2).isActive = true
borderView.leadingAnchor.constraint(equalTo: dailyUIView.leadingAnchor).isActive = true
borderView.trailingAnchor.constraint(equalTo: dailyUIView.trailingAnchor).isActive = true
dailyUIView.heightAnchor.constraint(equalToConstant: 100).isActive = true
descriptionLabel.leadingAnchor.constraint(equalTo: dailyUIView.leadingAnchor, constant: 16).isActive = true
descriptionLabel.centerYAnchor.constraint(equalTo: dailyUIView.centerYAnchor, constant: 0).isActive = true
dailyContentView.addSubview(dailyUIView)
if previousDay == nil {
dailyUIView.topAnchor.constraint(equalTo: dailyContentView.topAnchor, constant: 0).isActive = true
}
else {
dailyUIView.topAnchor.constraint(equalTo: previousDay!.bottomAnchor, constant: 0).isActive = true
}
dailyUIView.widthAnchor.constraint(equalToConstant: view.frame.width - 4).isActive = true
dailyUIView.centerXAnchor.constraint(equalTo: dailyContentView.centerXAnchor).isActive = true
previousDay = dailyUIView
}
allContentView.addSubview(dailyContentView)
}
func finishLayout() {
hourlyScrollView.bottomAnchor.constraint(equalTo: dailyContentView.bottomAnchor, constant: 0).isActive = true
dailyContentView.topAnchor.constraint(equalTo: allContentView.topAnchor, constant: 260).isActive = true
dailyContentView.centerXAnchor.constraint(equalTo: allContentView.centerXAnchor, constant: 0).isActive = true
dailyContentView.leadingAnchor.constraint(equalTo: allContentView.leadingAnchor).isActive = true
dailyContentView.trailingAnchor.constraint(equalTo: allContentView.trailingAnchor, constant: 0).isActive = true
dailyContentView.bottomAnchor.constraint(equalTo: allContentView.bottomAnchor).isActive = true
allScrollView.addSubview(allContentView)
allContentView.topAnchor.constraint(equalTo: allScrollView.topAnchor).isActive = true
allContentView.leadingAnchor.constraint(equalTo: allScrollView.leadingAnchor).isActive = true
allContentView.trailingAnchor.constraint(equalTo: allScrollView.trailingAnchor).isActive = true
allContentView.bottomAnchor.constraint(equalTo: allScrollView.bottomAnchor).isActive = true
allContentView.centerXAnchor.constraint(equalTo: allScrollView.centerXAnchor).isActive = true
allContentView.widthAnchor.constraint(equalToConstant: view.frame.width).isActive = true
allContentView.heightAnchor.constraint(equalToConstant: 1500).isActive = true
view.addSubview(allScrollView)
allScrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
allScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
allScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
allScrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
tapped functions
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
hourlyScrollView.contentSize = CGSize(width:160 * 10 + 2, height:240)
allScrollView.contentSize = CGSize(width: view.frame.width, height:1500)
}
#objc func hourlyTap(_ sender: UITapGestureRecognizer) {
let tappedView = sender.view
print("hourly: \(tappedView!.tag)")
}
#objc func dailyTap(_ sender: UITapGestureRecognizer) {
let tappedView = sender.view
print("daily: \(tappedView!.tag)")
}
You haven't set all the required constraints on your hourlyContentView so the horizontal scrollView's size is (0,0) and as such it can't be scrolled or tapped. You can use Debug View Hierarchy in Xcode to see this.
The constraints you need to add are:
Between your last hourlyUIView's trailingAnchor and hourlyContentView's trailing anchor:
...
previousHour = hourlyUIView
}
previousHour?.trailingAnchor.constraint(equalTo: hourlyContentView.trailingAnchor).isActive = true
let borderViewTop = UIView()
let borderViewBottom = UIView()
...
Setting your hourlyContentView heightAnchor equal to the hourlyScrollView height anchor:
hourlyContentView.heightAnchor.constraint(equalTo: hourlyScrollView.heightAnchor).isActive = true

Setting up UI constraint (Image and stackView inside a UIView) programmatically in Swift

I'm trying to build a custom AD when the app opens it pop up some UIViews and image and two buttons then control it from my Firebase, for now I have problem adding the adImage and buttonsStack(contains 2 buttons) inside my backView programmatically and so far nothing works ..
I need the image takes ~ %75 of the backView up and the buttonsStack ~ %25 of the rest
here some code and I have upload it to my GitHub
import UIKit
class ViewController: UIViewController {
let backroundView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .black
view.alpha = 0.5
return view
}()
let backView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
view.layer.cornerRadius = 15
return view
}()
let adImage: UIImageView = {
var image = UIImageView()
image.translatesAutoresizingMaskIntoConstraints = false
image.contentMode = .scaleAspectFill
return image
}()
let buttonsStack: UIStackView = {
let stack = UIStackView()
stack.translatesAutoresizingMaskIntoConstraints = false
stack.alignment = UIStackViewAlignment.fill
stack.axis = UILayoutConstraintAxis.vertical
stack.distribution = .equalSpacing
stack.spacing = 8
stack.backgroundColor = UIColor.red
return stack
}()
let actionButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Open", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = UIColor(red: 0, green: 0.60, blue: 1, alpha: 1)
button.layer.cornerRadius = 8
button.titleLabel?.adjustsFontSizeToFitWidth = true
button.titleLabel?.textAlignment = .center
return button
}()
let dismessButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Exit", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .lightGray
button.layer.cornerRadius = 8
button.titleLabel?.adjustsFontSizeToFitWidth = true
button.titleLabel?.textAlignment = .center
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
func setupUI(){
// backroundView
view.addSubview(backroundView)
backroundView.frame = view.frame
// backView
view.addSubview(backView)
backView.topAnchor.constraint(equalTo: view.topAnchor, constant: 80).isActive = true
backView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -50).isActive = true
backView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -25).isActive = true
backView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 25).isActive = true
// adImage
backView.addSubview(adImage)
adImage.image = UIImage(named: "testImage")
adImage.topAnchor.constraint(equalTo: backView.topAnchor).isActive = true
adImage.rightAnchor.constraint(equalTo: backView.rightAnchor).isActive = true
adImage.leftAnchor.constraint(equalTo: backView.leftAnchor).isActive = true
adImage.heightAnchor.constraint(equalTo: backView.heightAnchor, multiplier: 0.50).isActive = true
// buttonsStack
buttonsStack.addArrangedSubview(actionButton)
buttonsStack.addArrangedSubview(dismessButton)
backView.addSubview(buttonsStack)
buttonsStack.topAnchor.constraint(equalTo: backView.topAnchor, constant: 15).isActive = true
buttonsStack.bottomAnchor.constraint(equalTo: backView.bottomAnchor, constant: -15).isActive = true
buttonsStack.rightAnchor.constraint(equalTo: backView.rightAnchor, constant: -15).isActive = true
buttonsStack.leftAnchor.constraint(equalTo: backView.leftAnchor, constant: 15).isActive = true
}
}
For the image to take 0.75 change this
adImage.heightAnchor.constraint(equalTo: backView.heightAnchor, multiplier: 0.50).isActive = true
to
adImage.heightAnchor.constraint(equalTo: backView.heightAnchor, multiplier: 0.75).isActive = true
//
then the buttonStack should goes under it so change this
buttonsStack.topAnchor.constraint(equalTo: backView.topAnchor, constant: 15).isActive = true
to
buttonsStack.topAnchor.constraint(equalTo: adImage.bottomAnchor, constant: 15).isActive = true

Swift 4: Add View on top of all controllers

Conditions:
Swift 4, Xcode 9.3
Target: iOS 11.3
UI Done Programatically
Using Constraints
My Root View Controller is a Navigation
Situation:
I wanted to float an audio player that will be visible throughout the app.
I did an AudioPlayer.swift class that contains the user interface of the audio player.
AudioPlayer.swift
import Foundation
import UIKit
import FRadioPlayer
class AudioPlayer: UIView {
let screenSize: CGRect = UIScreen.main.bounds
let playerImage: UIImageView = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
iv.contentMode = .scaleAspectFill
iv.layer.masksToBounds = true
return iv
}()
let playerTitle: UILabel = {
let l = UILabel()
l.textColor = .darkGray
l.font = UIFont.boldSystemFont(ofSize: 13)
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
let playerSeriesTitle: UILabel = {
let l = UILabel()
l.textColor = .darkGray
l.font = UIFont.boldSystemFont(ofSize: 12)
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
override init(frame: CGRect) {
super.init(frame: frame)
translatesAutoresizingMaskIntoConstraints = false
setupAudioControls()
}
private func setupAudioControls(){
let appDelegate = AppDelegate.sharedInstance
self.backgroundColor = UIColor.init(hex: "#EBE4D3")
self.addSubview(playerImage)
self.addSubview(playerTitle)
self.addSubview(playerSeriesTitle)
self.heightAnchor.constraint(equalToConstant: 150).isActive = true
self.bottomAnchor.constraint(equalTo: appDelegate().rootView ).isActive = true
self.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor).isActive = true
self.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor).isActive = true
playerImage.topAnchor.constraint(equalTo: self.topAnchor, constant: 10).isActive = true
playerImage.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
playerImage.widthAnchor.constraint(equalToConstant: 55).isActive = true
playerImage.heightAnchor.constraint(equalToConstant: 55).isActive = true
playerTitle.topAnchor.constraint(equalTo: self.topAnchor, constant: 5).isActive = true
playerTitle.leadingAnchor.constraint(equalTo: playerImage.trailingAnchor, constant: 10).isActive = true
playerTitle.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 10).isActive = true
playerTitle.heightAnchor.constraint(equalToConstant: 25).isActive = true
playerSeriesTitle.topAnchor.constraint(equalTo: playerTitle.topAnchor, constant: 20).isActive = true
playerSeriesTitle.leadingAnchor.constraint(equalTo: playerImage.trailingAnchor, constant: 10).isActive = true
playerSeriesTitle.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 10).isActive = true
playerSeriesTitle.heightAnchor.constraint(equalToConstant: 20).isActive = true
UIView.animate(withDuration: 0.5, animations: {
self.frame.origin.y -= 150
self.playerImage.frame.origin.y -= 150
self.playerTitle.frame.origin.y -= 150
self.playerSeriesTitle.frame.origin.y -= 150
}, completion: nil)
self.setNeedsLayout()
self.reloadInputViews()
}
}
Problem:
How can I add this to the Root View Controller to stay on top in all view controllers that I have in my app? Wherever I navigate, the player must stay on the bottom part of every controller. As you can see, I need a reference to the rootviewcontroller to set the contraints for the AudioPlayer but I failed in so many attempts (like calling the rootviewcontroller using AppDelegate)
I update it for you
add singleton static let shared = AudioPlayer()
add public func showAudioPlayer () --> to display Audio player
add as subview to UIApplication.shared.keyWindow?
TODO- add HideAudioPlayer()
Use like this
AudioPlayer.shared.showAudioPlayer()
Here is updated code
import Foundation
import UIKit
class AudioPlayer: UIView {
static let shared = AudioPlayer()
let screenSize: CGRect = UIScreen.main.bounds
let playerImage: UIImageView = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
iv.contentMode = .scaleAspectFill
iv.layer.masksToBounds = true
return iv
}()
let playerTitle: UILabel = {
let l = UILabel()
l.textColor = .darkGray
l.font = UIFont.boldSystemFont(ofSize: 13)
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
let playerSeriesTitle: UILabel = {
let l = UILabel()
l.textColor = .darkGray
l.font = UIFont.boldSystemFont(ofSize: 12)
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
override init(frame: CGRect) {
super.init(frame: frame)
translatesAutoresizingMaskIntoConstraints = false
// setupAudioControls()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func showAudioPlayer (){
self.setupAudioControls()
}
private func setupAudioControls(){
self.backgroundColor = .red
self.addSubview(playerImage)
self.addSubview(playerTitle)
self.addSubview(playerSeriesTitle)
UIApplication.shared.keyWindow?.addSubview(self)
if let layoutGuide = UIApplication.shared.keyWindow?.layoutMarginsGuide {
self.heightAnchor.constraint(equalToConstant: 150).isActive = true
self.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor ).isActive = true
self.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor).isActive = true
self.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor).isActive = true
}
playerImage.topAnchor.constraint(equalTo: self.topAnchor, constant: 10).isActive = true
playerImage.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
playerImage.widthAnchor.constraint(equalToConstant: 55).isActive = true
playerImage.heightAnchor.constraint(equalToConstant: 55).isActive = true
playerTitle.topAnchor.constraint(equalTo: self.topAnchor, constant: 5).isActive = true
playerTitle.leadingAnchor.constraint(equalTo: playerImage.trailingAnchor, constant: 10).isActive = true
playerTitle.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 10).isActive = true
playerTitle.heightAnchor.constraint(equalToConstant: 25).isActive = true
playerSeriesTitle.topAnchor.constraint(equalTo: playerTitle.topAnchor, constant: 20).isActive = true
playerSeriesTitle.leadingAnchor.constraint(equalTo: playerImage.trailingAnchor, constant: 10).isActive = true
playerSeriesTitle.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 10).isActive = true
playerSeriesTitle.heightAnchor.constraint(equalToConstant: 20).isActive = true
UIView.animate(withDuration: 0.5, animations: {
self.frame.origin.y -= 150
self.playerImage.frame.origin.y -= 150
self.playerTitle.frame.origin.y -= 150
self.playerSeriesTitle.frame.origin.y -= 150
}, completion: nil)
self.setNeedsLayout()
self.reloadInputViews()
}
}
If you want to show view in each view controller then as per view hierarchy you must have to add in UIWindow. UIWindow is base of all screen.
AppDelegate.shared.window?.addSubview(AudioPlayer)
You can add your view to UIWindow.
I am doing the same thing with below method in AppDelegate.
var window: UIWindow?
func addPlayerViewAtBottom() {
var bottomView : PlayerBottomView!
bottomView = PlayerBottomView(frame: CGRect(x: 0, y: UIScreen.main.bounds.size.height - 60, width: UIScreen.main.bounds.width, height: 60))
self.window?.addSubview(bottomView)
self.window?.bringSubview(toFront: bottomView)
}

Resources