I am trying to add a subview using layout constraint. The subview is not getting displayed. Its displaying only when the frame is specified. However I want to achieve the it using layout constraints
import UIKit
class PhotoViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .lightGray
configurePhotoBarButton()
buildSubView()
}
#objc func closeClicked() {
dismiss(animated: true, completion: nil)
}
func configurePhotoBarButton() {
navigationController?.navigationBar.barStyle = .black
navigationController?.navigationBar.barTintColor = .lightGray
navigationItem.title = "Take a Photo"
let titleAttributes = [NSAttributedString.Key.font:UIFont(name: "DIN Condensed", size: 30),NSAttributedString.Key.foregroundColor: UIColor.black]
self.navigationController?.navigationBar.titleTextAttributes = titleAttributes
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Close", style: .plain, target: self, action: #selector(closeClicked))
let attributes = [NSAttributedString.Key.font:UIFont(name: "DIN Condensed", size: 20),NSAttributedString.Key.foregroundColor: UIColor.black]
navigationItem.leftBarButtonItem?.setTitleTextAttributes(attributes, for: .normal)
}
func buildSubView() {
//Add a Image View
let testview = UIView()
testview.backgroundColor = .orange
self.view.addSubview(testview)
testview.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
testview.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20).isActive = true
testview.topAnchor.constraint(equalTo: view.topAnchor, constant: 20).isActive = true
testview.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20).isActive = true
}
}
You are missing set translatesAutoresizingMaskIntoConstraints.
Please set it equal false when you want to add constraint programmatically.
testview.translatesAutoresizingMaskIntoConstraints = false
Related
I have an iOS application where i have a textfield and a button and on tap of button i have to hide the textfield.
I am setting heightAnchor to 0 on tap of button. Everything is working fine on iOS 14(14.5) but does not work(does not hide the text field) on iOS 15. Also, I have tried setting up the isHidden property on UITextField but it does not work.
Can you please help tell if something changed or i am doing something wrong. Thank you.
Code reference:
import UIKit
class ViewController: UIViewController {
private lazy var mytextFeild: UITextField = {
let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.text = "Hello world"
textField.backgroundColor = .green
return textField
}()
private lazy var testView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .systemPink
return view
}()
private lazy var button: UIButton = {
let view = UIButton()
view.backgroundColor = .blue
view.setTitle("hide it", for: .normal)
view.translatesAutoresizingMaskIntoConstraints = false
view.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
return view
}()
var heightConstraint: NSLayoutConstraint?
#objc func buttonTapped() {
heightConstraint?.isActive = false
heightConstraint = mytextFeild.heightAnchor.constraint(equalToConstant: 0)
heightConstraint?.isActive = true
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(mytextFeild)
view.addSubview(testView)
view.addSubview(button)
mytextFeild.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 32).isActive = true
mytextFeild.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -32.0).isActive = true
mytextFeild.topAnchor.constraint(equalTo: view.topAnchor, constant: 64).isActive = true
heightConstraint = mytextFeild.heightAnchor.constraint(equalToConstant: 32.0)
heightConstraint?.isActive = true
button.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 32.0).isActive = true
button.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -32.0).isActive = true
button.heightAnchor.constraint(equalToConstant: 32.0).isActive = true
button.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -64.0).isActive = true
testView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
testView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
testView.topAnchor.constraint(equalTo: mytextFeild.bottomAnchor).isActive = true
testView.bottomAnchor.constraint(equalTo: button.topAnchor).isActive = true
}
}
Add them to a stack then add stack to the viewController. at the end try to hide it easily without changing the height.
class ViewController: UIViewController {
private lazy var stackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.distribution = .fill
stackView.alignment = .center
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
private lazy var myTextField: UITextField = {
let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.text = "Hello world"
textField.backgroundColor = .green
return textField
}()
private lazy var testView: UIView = {
let view = UIView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .systemPink
return view
}()
private lazy var button: UIButton = {
let view = UIButton()
view.backgroundColor = .blue
view.setTitle("hide it", for: .normal)
view.translatesAutoresizingMaskIntoConstraints = false
view.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
return view
}()
#objc func buttonTapped() {
myTextField.isHidden = !myTextField.isHidden
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
stackView.addArrangedSubview(myTextField)
stackView.addArrangedSubview(testView)
stackView.addArrangedSubview(button)
view.addSubview(stackView)
stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -64.0).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
myTextField.heightAnchor.constraint(equalToConstant: 32.0).isActive = true
myTextField.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -64.0).isActive = true
testView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
button.heightAnchor.constraint(equalToConstant: 32.0).isActive = true
button.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -64.0).isActive = true
}
}
I have custom view with some labels, a text field and a button. When I add it to a view in a VC, the custom view appears nicely, but I can't tap on the text field or on the button, they are not responding. Can someone tell my what is the problem with the code? Here is the simplified version:
import UIKit
class CustomView: UIView {
let title: UILabel = {
let title = UILabel()
title.font = UIFont.systemFont(ofSize: 24)
title.text = "Title"
title.numberOfLines = 0
title.textAlignment = .center
title.translatesAutoresizingMaskIntoConstraints = false
return title
}()
let textView: UITextField = {
let textView = UITextField()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.placeholder = "Placeholder text"
textView.backgroundColor = UIColor.lightGray
textView.layer.cornerRadius = 10
return textView
}()
let searchButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = UIColor.blue
button.setTitle("Tap me", for: UIControl.State.normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
return button
}()
private func setupView() {
addSubview(title)
addSubview(textView)
addSubview(searchButton)
NSLayoutConstraint.activate([
title.topAnchor.constraint(equalTo: self.topAnchor, constant: 100),
title.rightAnchor.constraint(equalTo: self.rightAnchor),
title.leftAnchor.constraint(equalTo: self.leftAnchor),
])
NSLayoutConstraint.activate([
textView.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 20),
textView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
textView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
textView.heightAnchor.constraint(equalToConstant: 60)
])
NSLayoutConstraint.activate([
searchButton.topAnchor.constraint(equalTo: textView.bottomAnchor, constant: 20),
searchButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
searchButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
searchButton.heightAnchor.constraint(equalToConstant: 60)
])
}
#objc func buttonAction(_ sender:UIButton!)
{
print("Button tapped")
}
override init(frame: CGRect) {
super.init(frame: frame)
self.setupView()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
import UIKit
class ViewController: UIViewController {
private let customView = CustomView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(customView)
customView.translatesAutoresizingMaskIntoConstraints = false
customView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
customView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
customView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
}
Basically I want to have a seperate file with the view that has all the design and I just want to drop that into the VC without doing anything special with it. Is this even a good approach? Where should I set up the button action? I mean once its tappable...
Thanks!
Try this first - at the end of viewDidLoad(), add this line:
customView.backgroundColor = .red
When you run the app, you'll notice there is no red box.
Now, add this line after that one:
customView.clipsToBounds = true
Run it again, and... we see nothing!
The problem is, you haven't given your customView any height.
To fix it, constrain the bottom of searchButton in your custom view class:
NSLayoutConstraint.activate([
searchButton.topAnchor.constraint(equalTo: textView.bottomAnchor, constant: 20),
searchButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
searchButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
searchButton.heightAnchor.constraint(equalToConstant: 60),
// add this line!
searchButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20),
])
You need to set userInteractionEnabled to true on your CustomView instance so that it passes through taps.
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(customView)
customView.translatesAutoresizingMaskIntoConstraints = false
customView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
customView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
customView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
customView.userInteractionEnabled = true
}
You can use a delegation pattern or a closure property to pass the button tap event back to the containing view controller.
For example,
class CustomView: UIView {
var searchTappedHandler: ((CustomView)->Void)?
#objc func buttonAction(_ sender:UIButton!)
{
print("Button tapped")
self.searchTappedHandler?(self)
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(customView)
customView.translatesAutoresizingMaskIntoConstraints = false
customView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
customView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
customView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
customView.userInteractionEnabled = true
customView.searchTappedHandler = { _ in
print("Search button was tapped")
}
}
Or, if you want to use a function rather than an inline closure
func handleTap(_ customView: CustomView) {
print("tap")
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(customView)
customView.translatesAutoresizingMaskIntoConstraints = false
customView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
customView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
customView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
customView.userInteractionEnabled = true
customView.searchTappedHandler = handleTap
}
I have a ViewController and another UIView called MySubview which contains a simple button.
If I add MySubview into ViewController the button is unclickable. However if I put the button directly inside ViewController everything works as expected.
Example:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = MySubview()
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
button.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
}
}
class MySubview: UIView {
let button: UIButton = {
let button = UIButton()
button.setTitle("MyButton", for: .normal)
button.setTitleColor(.label, for: .normal)
button.addTarget(self, action: #selector(myfunc), for: .touchUpInside)
return button
}()
#objc func myfunc() {
print("clicked")
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .red
addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.topAnchor.constraint(equalTo: topAnchor, constant: 0).isActive = true
button.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
When I click on the button I do not see printed message.
Your code is confused, if I understand well take a look to my code below, declare your button and your view under your controller class:
class ViewController: UIViewController {
let button: UIButton = {
let button = UIButton()
button.setTitle("MyButton", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .black
button.addTarget(self, action: #selector(myfunc), for: .touchUpInside)
return button
}()
let mysubview = UIView()
...
now in viewDidLoad set constraints like this:
override func viewDidLoad() {
super.viewDidLoad()
mysubview.backgroundColor = .red
view.addSubview(mysubview)
mysubview.translatesAutoresizingMaskIntoConstraints = false
mysubview.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
mysubview.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
mysubview.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
mysubview.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
mysubview.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.topAnchor.constraint(equalTo: mysubview.topAnchor, constant: 20).isActive = true
button.leadingAnchor.constraint(equalTo: mysubview.leadingAnchor, constant: 20).isActive = true
button.trailingAnchor.constraint(equalTo: mysubview.trailingAnchor, constant: -20).isActive = true
button.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
now add your func
#objc func myfunc() {
print("clicked")
}
complete code:
import UIKit
class ViewController: UIViewController {
let button: UIButton = {
let button = UIButton()
button.setTitle("MyButton", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .black
button.addTarget(self, action: #selector(myfunc), for: .touchUpInside)
return button
}()
let mysubview = UIView()
override func viewDidLoad() {
super.viewDidLoad()
mysubview.backgroundColor = .red
view.addSubview(mysubview)
mysubview.translatesAutoresizingMaskIntoConstraints = false
mysubview.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
mysubview.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
mysubview.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
mysubview.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
mysubview.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.topAnchor.constraint(equalTo: mysubview.topAnchor, constant: 20).isActive = true
button.leadingAnchor.constraint(equalTo: mysubview.leadingAnchor, constant: 20).isActive = true
button.trailingAnchor.constraint(equalTo: mysubview.trailingAnchor, constant: -20).isActive = true
button.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
#objc func myfunc() {
print("clicked")
}
}
If your subview has a frame of size zero, you won't be able to tap the button even if you can see it. Using a view debugger from Xcode will show you if this is the issue.
Also, check if the subview has user interaction enabled when adding it.
I think , the problem is in layers (I mean, subviews). Try this instead of view.addSubview(button) :
view.insertSubview(your_button_view, at: view.subviews.count)
As suggested by some experts like #DonMag , i design the view in a separate controller, while trying to do so , every thing works, but when i use a navigation controller and there is a navigation bar at top, if i try and design in separate controller and then add its view to another controller the safeAreaLayoutGuide does not work and the view is attached to top of screen ignoring the safearea
SOLUTION as per #Mohammad Azam, DonMag solutions works as well , thanks
import UIKit
class NotesDesign: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func commonInit(){
let notesTitle = UITextField()
let notesContent = UITextView()
let font = UIFont(name: "CourierNewPS-ItalicMT", size: 20)
let fontM = UIFontMetrics(forTextStyle: .body)
notesTitle.font = fontM.scaledFont(for: font!)
notesContent.font = fontM.scaledFont(for: font!)
notesTitle.widthAnchor.constraint(greaterThanOrEqualToConstant:250).isActive = true
notesTitle.heightAnchor.constraint(equalToConstant: 30).isActive = true
notesTitle.borderStyle = .line
notesContent.widthAnchor.constraint(greaterThanOrEqualToConstant:250).isActive = true
notesContent.heightAnchor.constraint(greaterThanOrEqualToConstant: 100).isActive = true
notesContent.layer.borderWidth = 1
let notesStack = UIStackView()
notesStack.axis = .vertical
notesStack.spacing = 20
notesStack.alignment = .top
notesStack.distribution = .fill
notesStack.addArrangedSubview(notesTitle)
notesStack.addArrangedSubview(notesContent)
// Do any additional setup after loading the view.
notesStack.translatesAutoresizingMaskIntoConstraints = false
addSubview(notesStack)
notesStack.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 10).isActive = true
notesStack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
notesStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10).isActive = true
notesStack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -40).isActive = true
}
}
And where i call it
import UIKit
class AddNotesViewController: UIViewController {
var design = NotesDesign()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
view.addSubview(design)
design.translatesAutoresizingMaskIntoConstraints = false
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .plain, target: self, action: #selector(saveData))
}
#objc func saveData() {
}
}
And this is what i get
If you are taking a view from its ViewController and adding it as a subview to another view, you need to constrain it just like you would with any other added subview:
class AddNotesViewController: UIViewController {
var design = NotesViewDesign()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
view.addSubview(design.view)
// a view loaded from a UIViewController has .translatesAutoresizingMaskIntoConstraints = true
design.view.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain the loaded view
design.view.topAnchor.constraint(equalTo: g.topAnchor),
design.view.leadingAnchor.constraint(equalTo: g.leadingAnchor),
design.view.trailingAnchor.constraint(equalTo: g.trailingAnchor),
design.view.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .plain, target: self, action: #selector(saveData))
}
#objc func saveData() {
}
}
I have a problem when adding uiview under navigation controller. why my uiview is on top of uinavigationcontroller, I want to add my uiview under navigationController. this is my code.
let slideView = UIView()
view.backgroundColor = .white
navigationItem.title = "Absensi"
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationBar.barTintColor = .white
navigationController?.navigationBar.shadowImage = UIImage()
navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedString.Key.font: UIFont(name: "NunitoSans-SemiBold", size: 20)]
navigationItem.leftBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "ic-back-line").withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector(handleBack))
view.addSubview(slideView)
slideView.backgroundColor = .red
slideView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
slideView.topAnchor.constraint(equalTo: view.topAnchor),
slideView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
slideView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
slideView.heightAnchor.constraint(equalToConstant: 80)
])
Set your constraints with respect to safe area.
You should be add the topAnchor to safe area -> view.safeAreaLayoutGuide.topAnchor
NSLayoutConstraint.activate([
slideView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
slideView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
slideView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
slideView.heightAnchor.constraint(equalToConstant: 80)
])
just try using frame instead of constraint..this code should work
override func viewDidAppear(_ animated: Bool) {
let slideView = UIView(frame: CGRect(x: 0, y:
navigationController?.navigationBar.frame.height ?? 0 + 20 ,width: UIScreen.main.bounds.width, height: 80))
slideView.backgroundColor = .red
slideView.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
navigationItem.title = "Absensi"
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationBar.barTintColor = .white
navigationController?.navigationBar.shadowImage = UIImage()
navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedString.Key.font: UIFont(name: "NunitoSans-SemiBold", size: 20)]
navigationItem.leftBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "ic-back-line").withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector(handleBack))
view.addSubview(slideView)
}