iOS CustomView With AutoLayout in navigationItem not receiving clicks - ios

I created a custom view for navigationItem but somehow it is not receiving any click events:
The code for customView is below
class CustomNavigationView: UIView {
let backButton: UIButton = {
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "icon_back", in: Bundle.main, compatibleWith: nil), for: .normal)
button.isUserInteractionEnabled = true
return button
}()
var profileImage: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "icon_back", in: Bundle.main, compatibleWith: nil)
return imageView
}()
var profileName: UILabel = {
let label = UILabel()
label.text = "No Name"
label.font = UIFont(name: "HelveticaNeue", size: 16) ?? UIFont.systemFont(ofSize: 16)
label.textColor = UIColor(red: 96, green: 94, blue: 94)
return label
}()
var onlineStatusIcon: UIView = {
let view = UIView()
view.backgroundColor = UIColor(28, green: 222, blue: 20)
return view
}()
var onlineStatusText: UILabel = {
let label = UILabel()
label.text = "Online"
label.font = UIFont(name: "HelveticaNeue", size: 12) ?? UIFont.systemFont(ofSize: 12)
label.textColor = UIColor(red: 113, green: 110, blue: 110)
return label
}()
lazy var profileView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [self.profileName, self.onlineStatusText])
stackView.alignment = .fill
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.spacing = 2
return stackView
}()
#objc func backButtonClicked(_ sender: UIButton) {
print("Back Button click successfully")
}
private func setupConstraints() {
self.addViewsForAutolayout(views: [backButton, profileImage, onlineStatusIcon, profileView])
//Setup constraints
backButton.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 5).isActive = true
backButton.topAnchor.constraint(equalTo: self.topAnchor, constant: 10).isActive = true
backButton.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -10).isActive = true
backButton.widthAnchor.constraint(equalToConstant: 20).isActive = true
profileImage.leadingAnchor.constraint(equalTo: backButton.trailingAnchor, constant: 20).isActive = true
profileImage.topAnchor.constraint(equalTo: self.topAnchor, constant: 5).isActive = true
profileImage.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -5).isActive = true
profileImage.widthAnchor.constraint(equalToConstant: 35).isActive = true
profileImage.layer.cornerRadius = 18
profileImage.clipsToBounds = true
onlineStatusIcon.bottomAnchor.constraint(equalTo: profileImage.bottomAnchor, constant: 0).isActive = true
onlineStatusIcon.leadingAnchor.constraint(equalTo: profileImage.trailingAnchor, constant: -8).isActive = true
onlineStatusIcon.widthAnchor.constraint(equalToConstant: 10).isActive = true
onlineStatusIcon.heightAnchor.constraint(equalToConstant: 10).isActive = true
onlineStatusIcon.layer.cornerRadius = 5
onlineStatusIcon.clipsToBounds = true
profileView.leadingAnchor.constraint(equalTo: profileImage.trailingAnchor, constant: 5).isActive = true
profileView.topAnchor.constraint(equalTo: profileImage.topAnchor).isActive = true
profileView.bottomAnchor.constraint(equalTo: profileImage.bottomAnchor).isActive = true
}
required init() {
super.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 40))
setupConstraints()
addButtonTarget()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addButtonTarget() {
// Setup button callback
backButton.addTarget(self, action: #selector(backButtonClicked(_:)), for: .touchUpInside)
print("Target added")
}
}
And I am setting this view as NavigationbarLeft button Item in my view Controller:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let customView = CustomNavigationView()
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: customView)
}
}
The view is displaying correctly but the clicks are not working at all.
I used view debugging to check if some other layer is on top of this which might be causing problem but nothing of that sort is present.
I also checked backButton frame when adding the target using debug points.
Is there any solution for this problem. Does autolayout not work with custom view in navigation item? Or is there something that I am missing.
You can run the above piece of code and see that the clicks are not working.
This somehow appears to be related to auto layout. If I hardcode the frame position then clicks are working.
class CustomNavigationView: UIView {
let backButton: UIButton = {
let button = UIButton(frame: CGRect(x: 5, y: 5, width: 30, height: 30))
button.setImage(UIImage(named: "icon_back", in: Bundle.kommunicate, compatibleWith: nil), for: .normal)
button.isUserInteractionEnabled = true
return button
}()
var profileImage: UIImageView = {
let imageView = UIImageView(frame: CGRect(x: 40, y: 5, width: 30, height: 30))
imageView.image = UIImage(named: "icon_back", in: Bundle.kommunicate, compatibleWith: nil)
return imageView
}()
var profileName: UILabel = {
let label = UILabel(frame: CGRect(x: 80, y: 5, width: 50, height: 15))
label.text = "No Name"
label.font = UIFont(name: "HelveticaNeue", size: 16) ?? UIFont.systemFont(ofSize: 16)
label.textColor = UIColor(red: 96, green: 94, blue: 94)
return label
}()
var onlineStatusIcon: UIView = {
let view = UIView(frame: CGRect(x: 65, y: 30, width: 10, height: 10))
view.backgroundColor = UIColor(28, green: 222, blue: 20)
return view
}()
var onlineStatusText: UILabel = {
let label = UILabel(frame: CGRect(x: 80, y: 25, width: 50, height: 10))
label.text = "Online"
label.font = UIFont(name: "HelveticaNeue", size: 12) ?? UIFont.systemFont(ofSize: 12)
label.textColor = UIColor(red: 113, green: 110, blue: 110)
return label
}()
lazy var profileView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [self.profileName, self.onlineStatusText])
stackView.alignment = .fill
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.spacing = 2
return stackView
}()
#objc func backButtonClicked(_ sender: UIButton) {
print("Back button is successfully called")
}
private func setupConstraints() {
self.addSubview(backButton)
self.addSubview(profileImage)
self.addSubview(onlineStatusIcon)
self.addSubview(profileView)
}
required init() {
super.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 40))
setupConstraints()
addButtonTarget()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addButtonTarget() {
// Setup button callback
backButton.addTarget(self, action: #selector(backButtonClicked(_:)), for: .touchUpInside)
print("Target added")
}
}

The problem is with the manually added constraints that you added.
Using the view debugger the width of CustomNavigationView after it is added to the bar is 0.
In order to force the container to expand, add the following constraint in setupConstraints():
profileView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
Now that the container expands to match it's contents, the touch events should be propagated to the button as expected.

Related

context over screen while transitioning between view controllers

When its clicked on some cell, DetailViewController will be opened. Problem is that context from DetailViewController is shown while transitioning between controllers.
Picture below presents problem:
image
DetailViewController is called in function:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let singleMovie = movieList[indexPath.row]
getSingleMovie(movieId: singleMovie.id, completion: {})
getDirector(movieId: singleMovie.id, completion: { [weak self] in
guard let self = self else { return }
var directorName = ""
self.creditResponse?.crew.forEach({ singleCredit in
if singleCredit.knownForDepartment == .directing {
directorName = singleCredit.name
}
})
guard let safeMovie = self.detailMovie else {return}
let detailVc = DetailViewController(movie: safeMovie, groups: self.checkGroups(groups: singleMovie.genreIds), director: directorName, movieIndex: indexPath.row)
self.navigationController?.pushViewController(detailVc, animated: true)
})
}
DetailViewController:
import UIKit
class DetailViewController: UIViewController {
let movie: Details?
let groupsValue: String?
let directorValue: String?
let movieIndex: Int?
var checkButton: Bool = false
var favoriteButton: Bool = false
let backButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setImage(UIImage(named: "Icon"), for: .normal)
button.addTarget(self, action: #selector(backButtonTapped), for: .touchUpInside)
return button
}()
var movieTitleLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "Quicksand-Bold", size: 40)
label.textColor = .white
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
return label
}()
var groupsLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "Quicksand-Regular", size: 20)
label.textColor = .white
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var directorLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "Quicksand-Bold", size: 20)
label.textColor = .white
label.text = "Director: "
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var directorNameLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "Quicksand-Regular", size: 20)
label.textColor = .white
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var descriptionLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "Quicksand-Regular", size: 20)
label.textColor = .white
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
return label
}()
var movieImageView: UIImageView = {
let iv = UIImageView()
iv.layer.cornerRadius = 15
iv.layer.masksToBounds = true
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
let buttonChecked: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.tintColor = UIColor(red: 0.475, green: 0.729, blue: 0.757, alpha: 1)
button.imageView?.contentMode = .scaleAspectFit
button.imageEdgeInsets = UIEdgeInsets(top: 35, left: 35, bottom: 35, right: 35)
button.tintColor = UIColor(red: 0.475, green: 0.729, blue: 0.757, alpha: 1)
button.addTarget(self, action: #selector(checkButtonTapped), for: .touchUpInside)
return button
}()
let buttonFavorite: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.tintColor = UIColor(red: 0.475, green: 0.729, blue: 0.757, alpha: 1)
button.imageView?.contentMode = .scaleAspectFit
button.imageEdgeInsets = UIEdgeInsets(top: 35, left: 35, bottom: 35, right: 35)
button.tintColor = UIColor(red: 0.475, green: 0.729, blue: 0.757, alpha: 1)
button.addTarget(self, action: #selector(favoriteButtonTapped), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
self.navigationItem.hidesBackButton = true
setupUI()
}
init(movie:Details, groups: String, director: String, movieIndex: Int) {
self.movie = movie
self.groupsValue = groups
self.directorValue = director
self.movieIndex = movieIndex
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension DetailViewController{
#objc func backButtonTapped() {
navigationController?.popToRootViewController(animated: true)
}
}
extension DetailViewController {
func setupUI(){
view.addSubview(movieTitleLabel)
view.addSubview(groupsLabel)
view.addSubview(directorLabel)
view.addSubview(descriptionLabel)
view.addSubview(directorNameLabel)
view.addSubview(movieImageView)
view.addSubview(backButton)
view.addSubview(buttonChecked)
view.addSubview(buttonFavorite)
setupValues()
setupConstraints()
setupButtons()
}
}
extension DetailViewController {
func setupConstraints(){
let bottomImageConstraint = movieImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
bottomImageConstraint.priority = .defaultLow
NSLayoutConstraint.activate([
movieImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
movieImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
movieImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
bottomImageConstraint,
movieImageView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.3),
movieTitleLabel.topAnchor.constraint(equalTo: movieImageView.bottomAnchor, constant: 10),
movieTitleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
movieTitleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor),
groupsLabel.topAnchor.constraint(equalTo: movieTitleLabel.bottomAnchor, constant: 10),
groupsLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
directorLabel.topAnchor.constraint(equalTo: groupsLabel.bottomAnchor, constant: 10),
directorLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
directorLabel.topAnchor.constraint(equalTo: groupsLabel.bottomAnchor, constant: 10),
directorLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
directorNameLabel.topAnchor.constraint(equalTo: groupsLabel.bottomAnchor, constant: 10),
directorNameLabel.leadingAnchor.constraint(equalTo: directorLabel.trailingAnchor, constant: 10),
descriptionLabel.topAnchor.constraint(equalTo: directorLabel.bottomAnchor, constant: 10),
descriptionLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
descriptionLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor),
backButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10),
backButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
backButton.heightAnchor.constraint(equalToConstant: 40),
backButton.widthAnchor.constraint(equalToConstant: 40),
buttonFavorite.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
buttonFavorite.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
buttonChecked.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
buttonChecked.trailingAnchor.constraint(equalTo: buttonFavorite.trailingAnchor, constant: -50),
])
}
func setupValues(){
movieTitleLabel.text = movie?.title
guard let safeUrl = movie?.posterPath else {return}
movieImageView.downloadImage(from: safeUrl)
descriptionLabel.text = movie?.overview
groupsLabel.text = groupsValue
directorNameLabel.text = directorValue
}
func setupButtons(){
let checkValue = UserDefaults.standard.bool(forKey: "checkButton\(movieIndex ?? 0)")
let favoriteValue = UserDefaults.standard.bool(forKey: "favoriteButton\(movieIndex ?? 0)")
if checkValue == true {
buttonChecked.setImage(UIImage(systemName: "checkmark.seal.fill"), for: .normal)
}
else {
buttonChecked.setImage(UIImage(systemName: "checkmark.seal"), for: .normal)
}
if favoriteValue == true {
buttonFavorite.setImage(UIImage(systemName: "star.fill"), for: .normal)
}
else {
buttonFavorite.setImage(UIImage(systemName: "star"), for: .normal)
}
}
}
extension DetailViewController{
#objc func checkButtonTapped() {
checkButton = !checkButton
if checkButton == true {
DispatchQueue.main.async {
self.buttonChecked.setImage(UIImage(systemName: "checkmark.seal.fill"), for: .normal)
}
}
else {
DispatchQueue.main.async {
self.buttonChecked.setImage(UIImage(systemName: "checkmark.seal"), for: .normal)
}
}
UserDefaults.standard.set(checkButton, forKey: "checkButton\(movieIndex ?? 0)")
}
#objc func favoriteButtonTapped() {
favoriteButton = !favoriteButton
if favoriteButton == true {
DispatchQueue.main.async {
self.buttonFavorite.setImage(UIImage(systemName: "star.fill"), for: .normal)
}
}
else {
DispatchQueue.main.async {
self.buttonFavorite.setImage(UIImage(systemName: "star"), for: .normal)
}
}
UserDefaults.standard.set(favoriteButton, forKey: "favoriteButton\(movieIndex ?? 0)")
}
}
How to setup animations so it transits normally?
Transition GIF
It looks like your DetailViewController.view has no backgroundColor set (OR it is .clear).
Please assign a backgroundColor to your view like this.
override func viewDidLoad() {
super.viewDidLoad()
// For testing, you may want to set this to something else like `.red` / `.yellow`
// This will help you in identifying where the issue is
self.view.backgroundColor = .black
}

Transparent gradient not working in UIView Class

I'm trying to add a transparent gradient to UIView in UIView Class but it doesn't work.
class RecipesDetailsView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
layoutUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var containerView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .white
let gradientMaskLayer = CAGradientLayer()
gradientMaskLayer.frame = containerView.bounds
gradientMaskLayer.colors = [UIColor.clear.cgColor, UIColor.white.cgColor]
gradientMaskLayer.locations = [0, 1]
containerView.layer.mask = gradientMaskLayer
containerView.fadeView(style: .bottom, percentage: 0.5)
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
}()
lazy var startCookingButton: UIButton = {
let startCookingButton = UIButton(type: .system)
startCookingButton.setTitle("Start cooking", for: .normal)
startCookingButton.setTitleColor(.white, for: .normal)
startCookingButton.backgroundColor = .CustomGreen()
startCookingButton.layer.cornerRadius = 8.0
startCookingButton.translatesAutoresizingMaskIntoConstraints = false
startCookingButton.titleLabel?.font = UIFont(name: "AvenirNext-Bold", size: 14)
return startCookingButton
}()
lazy var saveButton: UIButton = {
let saveButton = UIButton(type: .system)
saveButton.setTitleColor(.customDarkGray(), for: .normal)
saveButton.setTitle("Save", for: .normal)
saveButton.setImage(UIImage(systemName: "heart"), for: .normal)
saveButton.imageEdgeInsets = UIEdgeInsets(top: 0,left: -5,bottom: 0,right: 0)
saveButton.titleEdgeInsets = UIEdgeInsets(top: 0,left: 0,bottom: 0,right: -5)
saveButton.titleLabel?.font = UIFont(name: "AvenirNext-Bold", size: 14)
saveButton.tintColor = .customDarkGray()
saveButton.backgroundColor = .clear
saveButton.translatesAutoresizingMaskIntoConstraints = false
return saveButton
}()
func setupContainerViewConstraints() {
NSLayoutConstraint.activate([
containerView.bottomAnchor.constraint(equalTo: bottomAnchor),
containerView.leadingAnchor.constraint(equalTo: leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: trailingAnchor),
containerView.heightAnchor.constraint(equalToConstant: frame.width / 5)
])
}
func setupStartCookingButton() {
NSLayoutConstraint.activate([
startCookingButton.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
startCookingButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -32),
startCookingButton.heightAnchor.constraint(equalToConstant: 55),
startCookingButton.widthAnchor.constraint(equalToConstant: frame.width * (2.5/4))
])
}
func setupSaveButtonConstraints() {
NSLayoutConstraint.activate([
saveButton.centerYAnchor.constraint(equalTo: startCookingButton.centerYAnchor),
saveButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
saveButton.heightAnchor.constraint(equalTo: startCookingButton.heightAnchor),
saveButton.widthAnchor.constraint(equalToConstant: frame.width * (1.2/4))
])
}
func addSubviews() {
addSubview(containerView)
containerView.addSubview(startCookingButton)
containerView.addSubview(saveButton)
}
func layoutUI() {
addSubviews()
setupContainerViewConstraints()
setupStartCookingButton()
setupSaveButtonConstraints()
}
}
What I want to get:
What I get from my code:
Layers do not "auto-size" with their views, so you need to keep that gradient layer as a property and update its frame when the view layout changes.
Add this property:
private var gradientMaskLayer: CAGradientLayer!
then, in lazy var containerView: UIView = change:
let gradientMaskLayer = CAGradientLayer()
to:
gradientMaskLayer = CAGradientLayer()
then, add this func:
override func layoutSubviews() {
super.layoutSubviews()
gradientMaskLayer.frame = bounds
}
Edit
However, that will apply the gradient mask to containerView AND its subviews (the buttons), which is probably not what you want.
So, change your addSubviews() func to:
func addSubviews() {
addSubview(containerView)
// add buttons to self, not to containerView
//containerView.addSubview(startCookingButton)
//containerView.addSubview(saveButton)
addSubview(startCookingButton)
addSubview(saveButton)
}
Edit 2
Here is a complete implementation, with the view controller's background set to red:
class TestViewController: UIViewController {
var rv: RecipesDetailsView!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// with the way you are setting up the layout,
// we need to add the view here where we know the
// frame has been setup
if rv == nil {
let w = view.frame.width
let h = w / 5.0 * 2.0
let t = view.frame.height - h
rv = RecipesDetailsView(frame: CGRect(x: 0.0, y: t, width: w, height: h))
view.addSubview(rv)
}
}
}
class RecipesDetailsView: UIView {
// add this var / property
private var gradientMaskLayer: CAGradientLayer!
override init(frame: CGRect) {
super.init(frame: frame)
layoutUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
// layers do not follow frame changes, so update here
gradientMaskLayer.frame = bounds
}
lazy var containerView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .white
gradientMaskLayer = CAGradientLayer()
gradientMaskLayer.frame = containerView.bounds
gradientMaskLayer.colors = [UIColor.clear.cgColor, UIColor.white.cgColor]
gradientMaskLayer.locations = [0, 1]
containerView.layer.mask = gradientMaskLayer
//containerView.fadeView(style: .bottom, percentage: 0.5)
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
}()
lazy var startCookingButton: UIButton = {
let startCookingButton = UIButton(type: .system)
startCookingButton.setTitle("Start cooking", for: .normal)
startCookingButton.setTitleColor(.white, for: .normal)
//startCookingButton.backgroundColor = .CustomGreen()
startCookingButton.backgroundColor = .systemGreen
startCookingButton.layer.cornerRadius = 8.0
startCookingButton.translatesAutoresizingMaskIntoConstraints = false
startCookingButton.titleLabel?.font = UIFont(name: "AvenirNext-Bold", size: 14)
return startCookingButton
}()
lazy var saveButton: UIButton = {
let saveButton = UIButton(type: .system)
//saveButton.setTitleColor(.customDarkGray(), for: .normal)
saveButton.setTitleColor(.darkGray, for: .normal)
saveButton.setTitle("Save", for: .normal)
saveButton.setImage(UIImage(systemName: "heart"), for: .normal)
saveButton.imageEdgeInsets = UIEdgeInsets(top: 0,left: -5,bottom: 0,right: 0)
saveButton.titleEdgeInsets = UIEdgeInsets(top: 0,left: 0,bottom: 0,right: -5)
saveButton.titleLabel?.font = UIFont(name: "AvenirNext-Bold", size: 14)
//saveButton.tintColor = .customDarkGray()
saveButton.tintColor = .darkGray
saveButton.backgroundColor = .clear
saveButton.translatesAutoresizingMaskIntoConstraints = false
return saveButton
}()
func setupContainerViewConstraints() {
NSLayoutConstraint.activate([
containerView.bottomAnchor.constraint(equalTo: bottomAnchor),
containerView.leadingAnchor.constraint(equalTo: leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: trailingAnchor),
containerView.heightAnchor.constraint(equalToConstant: frame.width / 5)
])
}
func setupStartCookingButton() {
NSLayoutConstraint.activate([
startCookingButton.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
startCookingButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -32),
startCookingButton.heightAnchor.constraint(equalToConstant: 55),
startCookingButton.widthAnchor.constraint(equalToConstant: frame.width * (2.5/4))
])
}
func setupSaveButtonConstraints() {
NSLayoutConstraint.activate([
saveButton.centerYAnchor.constraint(equalTo: startCookingButton.centerYAnchor),
saveButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
saveButton.heightAnchor.constraint(equalTo: startCookingButton.heightAnchor),
saveButton.widthAnchor.constraint(equalToConstant: frame.width * (1.2/4))
])
}
func addSubviews() {
addSubview(containerView)
// add buttons to self, not to containerView
//containerView.addSubview(startCookingButton)
//containerView.addSubview(saveButton)
addSubview(startCookingButton)
addSubview(saveButton)
}
func layoutUI() {
addSubviews()
setupContainerViewConstraints()
setupStartCookingButton()
setupSaveButtonConstraints()
}
}
Result:

How to position a button to right most edge of UITableCell with a margin programatically?

I am trying to position a button to the right most side of a UITableCell, but it is not working properly. I am using the below code.
override func awakeFromNib() {
flagBtn = UIButton(frame: CGRect(x: self.frame.maxX, y: 0, width: 30, height: 20))
contentView.addSubview(flagBtn)
// ...
}
The maxX is not giving the full width.
You can use constraints to achieve this.
override func awakeFromNib() {
flagBtn = UIButton()
contentView.addSubview(flagBtn)
flagBtn.translatesAutoresizingMaskIntoConstraints = false
flagBtn.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
flagBtn.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
flagBtn.widthAnchor.constraint(equalToConstant: 30).isActive = true
flagBtn.heightAnchor.constraint(equalToConstant: 20).isActive = true
}
Here you can read more about NSLayoutAnchors
First declare you UIbutton as a lazy property like below
lazy var flagBtn: UIButton = {
let btn = UIButton()
btn.setTitle("foo", for: .normal)
btn.backgroundColor = #colorLiteral(red: 1, green: 0, blue: 0, alpha: 1)
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
Secondly add your constraint
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
contentView.addSubview(flagBtn)
contentView.topAnchor.constraint(equalTo: flagBtn.topAnchor, constant: 5.0).isActive = true
contentView.rightAnchor.constraint(equalTo: flagBtn.rightAnchor, constant: 5.0).isActive = true
flagBtn.heightAnchor.constraint(equalToConstant: 30).isActive = true
flagBtn.widthAnchor.constraint(equalToConstant: 30).isActive = true
}
Hope it will help you.

How can I get a button in my subview to enable the rightbarbuttonitem in my view controller

I have a UIView with a textfield and a button, this UIView is a subview of my view controller. How can I have the button in my subview enable the view controller's navigation rightbarbuttonitem when it is pressed?
class CreateActivityView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
lazy var addButton: UIButton = {
let button = UIButton(type: UIButton.ButtonType.system)
button.setImage(UIImage(named: "add_dark.png"), for: .normal)
button.frame = CGRect(x: 0, y: 0, width: 24.0, height: 24.0)
button.addTarget(self, action: #selector(addButtonTapped), for: .touchUpInside)
button.isEnabled = false
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
lazy var activityNameTextField: DataEntryTextField = {
let textField = DataEntryTextField()
textField.frame = CGRect(x: 0, y: 0, width: 250.0, height: 30.0)
textField.placeholder = "Add an activity"
textField.font = UIFont(name: "Avenir", size: 17)
textField.keyboardType = UIKeyboardType.default
textField.returnKeyType = UIReturnKeyType.done
textField.translatesAutoresizingMaskIntoConstraints = false
return textField
}()
lazy var activityNameLabel: UILabel = {
let label = UILabel()
label.frame = CGRect(x: 0, y: 0, width: 0, height: 30.0)
label.font = UIFont(name: "Avenir", size: 17)
label.translatesAutoresizingMaskIntoConstraints = false
label.isHidden = true
return label
}()
func setupView() {
backgroundColor = UIColor.lightGray
addSubview(addButton)
addButton.rightAnchor.constraint(equalTo: self.safeAreaLayoutGuide.rightAnchor, constant: -16.0).isActive = true
addButton.centerYAnchor.constraint(equalTo: self.safeAreaLayoutGuide.centerYAnchor).isActive = true
addButton.heightAnchor.constraint(equalToConstant: 24.0).isActive = true
addButton.widthAnchor.constraint(equalToConstant: 24.0).isActive = true
addSubview(activityNameTextField)
activityNameTextField.leftAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leftAnchor, constant: 16.0).isActive = true
activityNameTextField.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 10.0).isActive = true
activityNameTextField.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: -10.0).isActive = true
activityNameTextField.rightAnchor.constraint(equalTo: addButton.safeAreaLayoutGuide.leftAnchor, constant: -20.0).isActive = true
addSubview(activityNameLabel)
activityNameLabel.leftAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leftAnchor, constant: 16.0).isActive = true
activityNameLabel.rightAnchor.constraint(equalTo: self.safeAreaLayoutGuide.rightAnchor, constant: -16.0).isActive = true
activityNameLabel.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 10.0).isActive = true
activityNameLabel.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: -10.0).isActive = true
}
#objc func addButtonTapped() {
guard let validatedText = activityNameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines), !validatedText.isEmpty else {
return
}
displayActivityNameLabel(with: validatedText)
}
func displayActivityNameLabel(with text: String) {
activityNameLabel.text = text
activityNameLabel.isHidden = false
activityNameTextField.isHidden = true
print(activityNameLabel.text)
}
}
class QuickLogViewController: UIViewController {
let screenSize = UIScreen.main.bounds.size
var createActivityView: CreateActivityView!
override func viewDidLoad() {
super.viewDidLoad()
title = "Quick Log"
setupView()
}
func setupView() {
initializeCreateActivityView()
self.view.addSubview(createActivityView)
createActivityView.leftAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leftAnchor).isActive = true
createActivityView.rightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.rightAnchor).isActive = true
createActivityView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
createActivityView.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
}
func initializeCreateActivityView() {
createActivityView = CreateActivityView()
createActivityView.frame = CGRect(x: 0, y: 0, width: screenSize.width, height: 50.0)
createActivityView.translatesAutoresizingMaskIntoConstraints = false
createActivityView.activityNameTextField.delegate = self
}
func enableCancelButton() {
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelButtonPressed))
}
}
I added my CreateActivityView to my view controller. The button target is handled by my createActvitiyView. So how can I access the rightbarbuttonitem from the subview?
You can try to use delegate
class CreateActivityView: UIView {
weak var delegate:VCName?
and
createActivityView = CreateActivityView()
createActivityView.delegate = self
after that inside any action do
delegate?.methodInsideTheVC()
Or write button action inside the vc
createActivityView = CreateActivityView()
createActivityView.button.addTarget(self, action: #selector(addButtonTapped), for: .touchUpInside)
and place
#objc func addButtonTapped() {

Creating an Input Accessory View programmatically

I'm trying to create a keyboard accessory view programmatically. I've set up a container view and inside that I'm trying to set up a textfield, post button, and an emoji.
Here's an example of what I'm trying to make.
Click here to view the image.
Here's the code that I am working with. I think the problem is when I'm setting the constraints.
Couple of questions running through my mind are:
Do I need to set up constraints for the container view?
How do I add appropriate constraints to the textfield?
override var inputAccessoryView: UIView? {
get {
//Set up the container
let containerView = UIView()
containerView.backgroundColor = #colorLiteral(red: 0.9784782529, green: 0.9650371671, blue: 0.9372026324, alpha: 1)
containerView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 60)
let textField = UITextField()
textField.placeholder = "Add a reframe..."
textField.isSecureTextEntry = false
textField.textAlignment = .left
textField.borderStyle = .none
textField.translatesAutoresizingMaskIntoConstraints = false
textField.translatesAutoresizingMaskIntoConstraints = false
textField.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
textField.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
textField.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
textField.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
textField.heightAnchor.constraint(equalToConstant: 50)
containerView.addSubview(textField)
return containerView
}
}
This is the error that I keep getting.
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to activate constraint with anchors and because they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That's illegal.'
EDIT:
View Post Controller
lazy var containerView: CommentInputAccessoryView = {
let frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 60)
let commentInputAccessoryView = CommentInputAccessoryView(frame: frame)
commentInputAccessoryView.delegate = self
return commentInputAccessoryView
}()
//Setting up the keyboard accessory view for comments.
override var inputAccessoryView: UIView? {
get {
return containerView
}
}
override var canBecomeFirstResponder: Bool {
return true
}
CommentInputAccessoryView
protocol CommentInputAccessoryViewDelegate {
func didSend(for comment: String)
}
class CommentInputAccessoryView: UIView {
var delegate: CommentInputAccessoryViewDelegate?
func clearCommentTextView() {
commentTextView.text = nil
showPlaceholderLabel()
}
let commentTextView: UITextView = {
let text = UITextView()
text.translatesAutoresizingMaskIntoConstraints = false
//text.placeholder = "Add a reframe..."
text.textAlignment = .left
text.backgroundColor = #colorLiteral(red: 0.9784782529, green: 0.9650371671, blue: 0.9372026324, alpha: 1)
text.layer.cornerRadius = 50/2
text.layer.masksToBounds = true
text.isScrollEnabled = false
text.font = UIFont.systemFont(ofSize: 16)
text.textContainerInset = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 64)
//text.borderStyle = .none
return text
}()
let placeholderLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Add a response..."
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 16)
return label
}()
func showPlaceholderLabel() {
placeholderLabel.isHidden = false
}
let sendButton: UIButton = {
let send = UIButton(type: .system)
//let sendButton = UIImageView(image: #imageLiteral(resourceName: "arrowUp"))
send.translatesAutoresizingMaskIntoConstraints = false
send.setTitle("Send", for: .normal)
send.setTitleColor(#colorLiteral(red: 0.2901960784, green: 0.3725490196, blue: 0.937254902, alpha: 1), for: .normal)
send.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 10)
send.addTarget(self, action: #selector(handlePostComment), for: .touchUpInside)
return send
}()
let hugButton: UIButton = {
let hug = UIButton()
hug.translatesAutoresizingMaskIntoConstraints = false
hug.setTitle("🤗", for: .normal)
hug.backgroundColor = #colorLiteral(red: 0.9784782529, green: 0.9650371671, blue: 0.9372026324, alpha: 1)
hug.contentEdgeInsets = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 12)
hug.layer.cornerRadius = 25
hug.layer.masksToBounds = true
return hug
}()
override init(frame: CGRect) {
super.init(frame: frame)
autoresizingMask = .flexibleHeight
addSubview(commentTextView)
addSubview(sendButton)
addSubview(hugButton)
addSubview(placeholderLabel)
if #available(iOS 11.0, *) {
commentTextView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -10).isActive = true
hugButton.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -10).isActive = true
}
else {
}
commentTextView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 8).isActive = true
commentTextView.topAnchor.constraint(equalTo: self.topAnchor, constant: 10).isActive = true
commentTextView.trailingAnchor.constraint(equalTo: hugButton.leadingAnchor, constant: 8)
placeholderLabel.leadingAnchor.constraint(equalTo: commentTextView.leadingAnchor, constant: 18).isActive = true
placeholderLabel.centerYAnchor.constraint(equalTo: self.commentTextView.centerYAnchor).isActive = true
sendButton.trailingAnchor.constraint(equalTo: self.commentTextView.trailingAnchor, constant: -10).isActive = true
sendButton.centerYAnchor.constraint(equalTo: self.commentTextView.bottomAnchor, constant: -22).isActive = true
hugButton.leadingAnchor.constraint(equalTo: self.commentTextView.trailingAnchor, constant: 10).isActive = true
hugButton.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -8).isActive = true
hugButton.widthAnchor.constraint(equalToConstant: 40)
//hugButton.centerYAnchor.constraint(equalTo: self.commentTextView.centerYAnchor).isActive = true
NotificationCenter.default.addObserver(self, selector: #selector(handleTextChanged), name: .UITextViewTextDidChange, object: nil)
}
override var intrinsicContentSize: CGSize {
return .zero
}
#objc func handleTextChanged() {
placeholderLabel.isHidden = !self.commentTextView.text.isEmpty
}
#objc func handlePostComment() {
guard let commentText = commentTextView.text else {return}
delegate?.didSend(for: commentText)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here are some photos that might help for what is happening.
InputAccessoryView working:
Click here
TextView expansion:
Click here.
Before applying constraints view should be in view hierarchy or error which you got will be raised. To get rid of error just do containerView.addSubview(textField) after let textField = UITextField().
Regarding example image you posted, initial solution could be something like this
override var inputAccessoryView: UIView? {
get {
//Set up the container
let containerView = UIView()
containerView.backgroundColor = #colorLiteral(red: 0.9784782529, green: 0.9650371671, blue: 0.9372026324, alpha: 1)
containerView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 60)
let textField = UITextField()
containerView.addSubview(textField)
textField.translatesAutoresizingMaskIntoConstraints = false
textField.placeholder = "Add a reframe..."
textField.textAlignment = .left
textField.backgroundColor = .white
textField.layer.cornerRadius = 50/2
textField.layer.masksToBounds = true
textField.borderStyle = .none
textField.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 8).isActive = true
textField.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -5).isActive = true
textField.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 10).isActive = true
textField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 15, height: textField.frame.height)) // adding left padding so it's not sticked to border
textField.leftViewMode = .always
let arrow = UIImageView(image: #imageLiteral(resourceName: "arrowUp"))
containerView.addSubview(arrow)
arrow.translatesAutoresizingMaskIntoConstraints = false
arrow.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: -10).isActive = true
arrow.centerYAnchor.constraint(equalTo: textField.centerYAnchor).isActive = true
let button = UIButton()
containerView.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("🤗", for: .normal)
button.contentEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
button.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 10).isActive = true
button.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -8).isActive = true
button.centerYAnchor.constraint(equalTo: textField.centerYAnchor).isActive = true
// Negative values for constraints can be avoided if we change order of views when applying constrains
// f.e. instead of button.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -8).isActive = true
// write containerView.trailingAnchor.constraint(equalTo: button.trailingAnchor, constant: 8).isActive = true
return containerView
}
}

Resources