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
}
}
Related
I am trying to make a text acceptor view like Instagram and Snapchat.
I am trying to add a UITextview (programatically) inside a UIView thats also added programatically. Below is the code I am trying to use to add the UITextView, but it is not working when added with UIView code. Please help.
What I am trying to achieve is:
import UIKit
class TextViewViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let View1: UIView = {
let viewView = UIView()
viewView.translatesAutoresizingMaskIntoConstraints = false
viewView.contentMode = .scaleAspectFit
viewView.backgroundColor = .red
viewView.clipsToBounds = true
return viewView
}()
self.view.addSubview(View1)
View1.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
View1.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
View1.topAnchor.constraint(equalTo: view.topAnchor, constant: 250).isActive = true
View1.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -250).isActive = true
let textView = UITextView(frame: CGRect(x: 20.0, y: 90.0, width: 250.0, height: 100.0))
textView.contentInsetAdjustmentBehavior = .automatic
textView.center = self.view.center
textView.textAlignment = NSTextAlignment.justified
textView.backgroundColor = UIColor.lightGray
// Use RGB colour
textView.backgroundColor = UIColor(red: 39/255, green: 53/255, blue: 182/255, alpha: 1)
// Update UITextView font size and colour
textView.font = UIFont.systemFont(ofSize: 20)
textView.textColor = UIColor.white
textView.font = UIFont(name: "Verdana", size: 17)
// Make UITextView web links clickable
textView.isSelectable = true
textView.isEditable = false
textView.dataDetectorTypes = UIDataDetectorTypes.link
// Make UITextView corners rounded
textView.layer.cornerRadius = 10
// Make UITextView Editable
textView.isEditable = true
View1.addSubview(textView)
//
// let textView: UITextView = {
// let tv = UITextView(frame: CGRect(x: 20.0, y: 90.0, width: 100.0, height: 50.0))
// tv.contentInsetAdjustmentBehavior = .automatic
// tv.center = self.view.center
// tv.textAlignment = NSTextAlignment.justified
// tv.textColor = UIColor.blue
// tv.backgroundColor = UIColor.lightGray
// return tv
// }()
//
// View1.addSubview(textView)
// textView.topAnchor.constraint(equalTo: View1.topAnchor, constant: 20).isActive = true
// textView.bottomAnchor.constraint(equalTo: View1.bottomAnchor, constant: 20).isActive = true
// textView.leadingAnchor.constraint(equalTo: View1.leadingAnchor, constant: 0).isActive = true
// textView.trailingAnchor.constraint(equalTo: View1.trailingAnchor, constant: 0).isActive = true
}
}
The issue is in the next line of code
textView.center = self.view.center
Get rid of it and you will see the textView in the view hierarchy
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
}
Here is the screenshot if the errorI am building the ui of the app programatically and when i run the app it runs perfectly but when i turn the simulation in landscape mode the console shows some layouts errors. Is it okay to just tick the orientation to portrait only in the general app settings because my app is running perfectly in portrait mode and i don't want to run the app in any other orientations or am i making some problems in constraints? Here is my code for the ui...
`let top = UIColor(red: 217/255, green: 30/255, blue: 133/255, alpha: 1)
let bottom = UIColor(red: 242/255, green: 56/255, blue: 15/255, alpha: 1)
let colorOne = UIColor(red: 38/255, green: 38/255, blue: 38/255, alpha: 1)
let colorTwo = UIColor(red: 0/255, green: 0/255, blue: 0/255, alpha: 1)
let colorTop = UIColor.red
let colorBottom = UIColor.green
let topContainer: UIView = {
let top = UIView()
top.translatesAutoresizingMaskIntoConstraints = false
top.backgroundColor = .black
return top
}()
let model: UIImageView = {
let mymodel = UIImageView(image: #imageLiteral(resourceName: "bodybuilder"))
mymodel.translatesAutoresizingMaskIntoConstraints = false
mymodel.contentMode = .scaleToFill
return mymodel
}()
let logo: UIImageView = {
let gymble = UIImageView(image: #imageLiteral(resourceName: "Gymble"))
gymble.contentMode = .scaleAspectFill
gymble.translatesAutoresizingMaskIntoConstraints = false
return gymble
}()
let bottomContainer: UIView = {
let bottom = UIView()
bottom.translatesAutoresizingMaskIntoConstraints = false
return bottom
}()
let pinField: UITextField = {
let phone = UITextField()
phone.backgroundColor = .white
phone.text = "+91"
phone.textAlignment = .center
phone.layer.cornerRadius = 5
phone.font = UIFont(name: "Roboto-Regular", size: 20)
phone.keyboardType = UIKeyboardType.numberPad
phone.translatesAutoresizingMaskIntoConstraints = false
return phone
}()
let phoneFeild: UITextField = {
let phone = UITextField()
phone.backgroundColor = .white
phone.placeholder = "Phone number"
phone.layer.cornerRadius = 5
phone.font = UIFont(name: "Roboto-Regular", size: 20)
phone.keyboardType = UIKeyboardType.phonePad
phone.keyboardAppearance = UIKeyboardAppearance.dark
phone.translatesAutoresizingMaskIntoConstraints = false
return phone
}()
let loginButton: UIButton = {
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Login", for: .normal)
button.setTitleColor(.white, for: .normal)
button.layer.cornerRadius = 5
button.layer.masksToBounds = true
button.titleLabel?.font = UIFont(name: "Roboto-Medium", size: 22)
button.addTarget(self, action: #selector(someButtonAction), for: .touchUpInside)
return button
}()
let laterButton: UIButton = {
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("I'll login later", for: .normal)
button.setTitleColor(.gray, for: .normal)
button.titleLabel?.font = UIFont(name: "Roboto-Light", size: 16)
return button
}()
let horizontalStack: UIStackView = {
let mystack = UIStackView()
mystack.alignment = UIStackView.Alignment.center
mystack.axis = NSLayoutConstraint.Axis.horizontal
mystack.translatesAutoresizingMaskIntoConstraints = false
mystack.distribution = .fill
mystack.spacing = 15
return mystack
}()
let stack: UIStackView = {
let mystack = UIStackView()
mystack.alignment = UIStackView.Alignment.center
mystack.axis = NSLayoutConstraint.Axis.vertical
mystack.translatesAutoresizingMaskIntoConstraints = false
mystack.distribution = .equalSpacing
return mystack
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
view.addSubview(topContainer)
topContainerLayout()
topContainer.addSubview(model)
modelLayout()
view.addSubview(bottomContainer)
bottomContainerLayout()
bottomContainer.addSubview(stack)
stackLayout()
stack.addArrangedSubview(logo)
logoLayout()
stack.addArrangedSubview(horizontalStack)
horizontalStackLayout()
horizontalStack.addArrangedSubview(pinField)
pinFieldLayout()
let padding = UIView(frame: CGRect(x: 0, y: 0, width: 15, height: self.phoneFeild.frame.height))
phoneFeild.leftView = padding
phoneFeild.leftViewMode = UITextField.ViewMode.always
horizontalStack.addArrangedSubview(phoneFeild)
phoneFieldLayout()
let gradientWidth = (UIScreen.main.bounds.width - 32)
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [top.cgColor, bottom.cgColor]
gradientLayer.locations = [0.15, 1]
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 1, y: 0)
gradientLayer.frame = CGRect(x: 0, y: 0, width: gradientWidth, height: 50)
gradientLayer.cornerRadius = 5
loginButton.layer.insertSublayer(gradientLayer, at: 0)
stack.addArrangedSubview(loginButton)
loginButtonLayout()
stack.addArrangedSubview(laterButton)
LaterButtonLayout()
}
func LaterButtonLayout(){
laterButton.heightAnchor.constraint(equalToConstant: 30).isActive = true
}
func loginButtonLayout(){
loginButton.widthAnchor.constraint(equalTo: stack.widthAnchor, constant: -32).isActive = true
loginButton.heightAnchor.constraint(equalToConstant: 44).isActive = true
}
func logoLayout(){
logo.topAnchor.constraint(equalTo: stack.topAnchor).isActive = true
logo.heightAnchor.constraint(equalToConstant: 56).isActive = true
logo.widthAnchor.constraint(equalToConstant: 60).isActive = true
}
func stackLayout(){
stack.heightAnchor.constraint(equalTo: bottomContainer.heightAnchor).isActive = true
stack.widthAnchor.constraint(equalTo: bottomContainer.widthAnchor).isActive = true
}
func horizontalStackLayout(){
horizontalStack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16).isActive = true
horizontalStack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16).isActive = true
horizontalStack.heightAnchor.constraint(equalToConstant: 40).isActive = true
}
func pinFieldLayout(){
pinField.leadingAnchor.constraint(equalTo: horizontalStack.leadingAnchor).isActive = true
pinField.topAnchor.constraint(equalTo: horizontalStack.topAnchor).isActive = true
pinField.bottomAnchor.constraint(equalTo: horizontalStack.bottomAnchor).isActive = true
pinField.widthAnchor.constraint(equalToConstant: 50).isActive = true
}
func phoneFieldLayout(){
phoneFeild.leadingAnchor.constraint(equalTo: pinField.trailingAnchor, constant: 15).isActive = true
phoneFeild.topAnchor.constraint(equalTo: horizontalStack.topAnchor).isActive = true
phoneFeild.bottomAnchor.constraint(equalTo: horizontalStack.bottomAnchor).isActive = true
}
func modelLayout(){
model.topAnchor.constraint(equalTo: topContainer.topAnchor).isActive = true
model.leadingAnchor.constraint(equalTo: topContainer.leadingAnchor).isActive = true
model.trailingAnchor.constraint(equalTo: topContainer.trailingAnchor).isActive = true
model.heightAnchor.constraint(equalTo: topContainer.heightAnchor).isActive = true
}
func bottomContainerLayout(){
bottomContainer.topAnchor.constraint(equalTo: topContainer.bottomAnchor).isActive = true
bottomContainer.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
bottomContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
bottomContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
func topContainerLayout(){
topContainer.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
topContainer.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 2/3 , constant: -20).isActive = true
topContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
topContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
#objc func someButtonAction(){
print("Button is tapped!")
let opt = OTPViewController()
present(opt, animated: true, completion: nil)
}
}
`
If you want to customize app orientation do this:
Go to General setting of project
in Deployment , change device orientation .
Probably you didn't make auto layout.
Please check this out -> Auto Layout Guide
And this -> Programmatically Creating Constraints
Edit:
Use that translatesAutoresizingMaskIntoConstraints = false after addSubView.For example
func topContainerLayout(){
topContainer.translatesAutoresizingMaskIntoConstraints = false
topContainer.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
topContainer.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 2/3 , constant: -20).isActive = true
topContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
topContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
Essentially I am using this code extension below to copy a view and all its subviews. The copy is successful and I am able to view the copied view. However, the button on each copied view cannot be pressed. The button can only be pressed within the first original (not copied) view.
How to get all copied buttons to be active? Is this even possible?
I have already tried .isUserInteractionEnabled on the button and its parent view.
override func viewDidLoad(){
super.viewDidLoad()
view.isUserInteractionEnabled = true
view.addSubview(containerScrollView)
containerScrollView.addSubview(contentView)
contentView.addSubview(stackMainView)
let button = UIButton(frame: CGRect(x: 270, y: 200, width: 80, height: 40))
let partLabel1 = UILabel(frame: CGRect(x:10, y: 10, width: 300, height: 50))
let partLabel2 = UILabel(frame: CGRect(x:10, y: 50, width: 300, height: 50))
partLabel1.text = "This should sit within part use :)"
partLabel1.textColor = .white
partLabel2.text = "This should also sit within part use :)"
partLabel2.textColor = .white
contentView.addSubview(button)
contentView.addSubview(partLabel1)
contentView.addSubview(partLabel2)
part.addSubview(button)
part.addSubview(partLabel1)
part.addSubview(partLabel2)
part.bringSubviewToFront(button)
part.bringSubviewToFront(partUse3Label1)
part.layer.zPosition = -1
button.setTitle("Issue", for: .normal)
button.backgroundColor = .orange
button.leadingAnchor.constraint(equalTo: part.leadingAnchor).isActive = true
button.trailingAnchor.constraint(equalTo: part.trailingAnchor).isActive = true
button.bottomAnchor.constraint(equalTo: part.bottomAnchor).isActive = true
button.topAnchor.constraint(equalTo: part.topAnchor).isActive = true
button.heightAnchor.constraint(equalToConstant: 40).isActive = true
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
button.isUserInteractionEnabled = true
part.bringSubviewToFront(button)
partLabel1.leadingAnchor.constraint(equalTo: part.leadingAnchor).isActive = true
partLabel1.trailingAnchor.constraint(equalTo: part.trailingAnchor).isActive = true
partLabel2.leadingAnchor.constraint(equalTo: part.leadingAnchor).isActive = true
partLabel2.trailingAnchor.constraint(equalTo: part.trailingAnchor).isActive = true
part.layoutIfNeeded()
let copiedView = self.part.copyView()
stackMainView.addArrangedSubview(part)
stackMainView.addArrangedSubview(copiedView)
containerScrollView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
containerScrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true
containerScrollView.trailingAnchor.constraint(equalTo:self.view.trailingAnchor, constant: 0).isActive = true
containerScrollView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
contentView.topAnchor.constraint(equalTo: self.containerScrollView.topAnchor, constant: 0).isActive = true
contentView.leadingAnchor.constraint(equalTo: self.containerScrollView.leadingAnchor, constant: 0).isActive = true
contentView.trailingAnchor.constraint(equalTo:self.containerScrollView.trailingAnchor, constant: 0).isActive = true
contentView.bottomAnchor.constraint(equalTo: self.containerScrollView.bottomAnchor, constant: 0).isActive = true
contentView.widthAnchor.constraint(equalTo:self.view.widthAnchor, constant: 0).isActive = true
stackMainView.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 50).isActive = true
stackMainView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 8).isActive = true
stackMainView.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -8).isActive = true
stackMainView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -30).isActive = true
}
#objc func buttonAction(sender: UIButton!) {
print("Button tapped")
}
var containerScrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.backgroundColor = .white
scrollView.isScrollEnabled = true
return scrollView
}()
var contentView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.white
return view
}()
let stackMainView: UIStackView = {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.backgroundColor = .random()
return stackView
}()
let part: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 4
view.layer.masksToBounds = true
view.backgroundColor = .random()
return view
}()
Extension to copy the view.
extension UIView {
func copyView<T: UIView>() -> T {
return NSKeyedUnarchiver.unarchiveObject(with:
NSKeyedArchiver.archivedData(withRootObject: self)) as! T
}
}
I expect the output to print in the console "Button Tapped"
This happens only when I press the button on the non copied view.
Since this is all done in viewDidLoad, I assume that the view you want to copy is the same every time.
Your code does not work likely because NSKeyedArchiver does not archive the button's target and selector pairs.
You can create a method that gives a new UIView instead:
func createPart() -> UIView {
let part = UIView()
part.translatesAutoresizingMaskIntoConstraints = false
part.layer.cornerRadius = 4
part.layer.masksToBounds = true
part.backgroundColor = .random()
// The part below is copied from your viewDidLoad method
// Include only those lines that create the part view.
// I might have put more than you need. Check twice
let button = UIButton(frame: CGRect(x: 270, y: 200, width: 80, height: 40))
let partLabel1 = UILabel(frame: CGRect(x:10, y: 10, width: 300, height: 50))
let partLabel2 = UILabel(frame: CGRect(x:10, y: 50, width: 300, height: 50))
partLabel1.text = "This should sit within part use :)"
partLabel1.textColor = .white
partLabel2.text = "This should also sit within part use :)"
partLabel2.textColor = .white
part.addSubview(button)
part.addSubview(partLabel1)
part.addSubview(partLabel2)
part.bringSubviewToFront(button)
part.bringSubviewToFront(partUse3Label1)
part.layer.zPosition = -1
button.setTitle("Issue", for: .normal)
button.backgroundColor = .orange
button.leadingAnchor.constraint(equalTo: part.leadingAnchor).isActive = true
button.trailingAnchor.constraint(equalTo: part.trailingAnchor).isActive = true
button.bottomAnchor.constraint(equalTo: part.bottomAnchor).isActive = true
button.topAnchor.constraint(equalTo: part.topAnchor).isActive = true
button.heightAnchor.constraint(equalToConstant: 40).isActive = true
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
button.isUserInteractionEnabled = true
part.bringSubviewToFront(button)
partLabel1.leadingAnchor.constraint(equalTo: part.leadingAnchor).isActive = true
partLabel1.trailingAnchor.constraint(equalTo: part.trailingAnchor).isActive = true
partLabel2.leadingAnchor.constraint(equalTo: part.leadingAnchor).isActive = true
partLabel2.trailingAnchor.constraint(equalTo: part.trailingAnchor).isActive = true
part.layoutIfNeeded()
return part
}
And then in viewDidLoad, you should remove the lines of code that help create the part view, leaving only the code that creates the stack view and main content view. You should then call createPart twice, and there you have 2 copies!
let part = createPart()
let copyOfPart = createPart()
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.