I want to put two buttons with images on left and right edges of the screen, and insert a UITextField between them. In storyboard everything is fine, but I need to do it programmatically.
Source:
class SecondViewController: UIViewController {
override func loadView() {
super.loadView()
self.view.backgroundColor = .white
let leftButton = UIButton()
leftButton.setImage(UIImage(named: "first"), for: .normal)
leftButton.setTitle("", for: .normal)
let rightButton = UIButton()
rightButton.setImage(UIImage(named: "second"), for: .normal)
rightButton.setTitle("", for: .normal)
let textField = UITextField()
textField.placeholder = "clear"
textField.borderStyle = .roundedRect
textField.contentHorizontalAlignment = .left
textField.contentVerticalAlignment = .center
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.alignment = .fill
stackView.distribution = .fill
stackView.axis = .horizontal
stackView.spacing = 15
stackView.addArrangedSubview(leftButton)
stackView.addArrangedSubview(textField)
stackView.addArrangedSubview(rightButton)
self.view.addSubview(stackView)
NSLayoutConstraint(item: stackView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1.0, constant: 15).isActive = true
NSLayoutConstraint(item: stackView, attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailing, multiplier: 1.0, constant: -15).isActive = true
NSLayoutConstraint(item: stackView, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1.0, constant: 50).isActive = true
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
I need a result like on first image.
But at result, I have second.
What did I do wrong ?
You have an elementary constraint ambiguity. You could easily have discovered this just by switching to the View Debugger; you would see three exclamation marks warning you of the problem. The layout engine does not magically know which of the three views should be allowed to stretch horizontally. You have to tell it:
textField.setContentHuggingPriority(UILayoutPriority(249), for: .horizontal)
Set constraints like
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 50),
stackView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 15),
stackView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -15),
stackView.heightAnchor.constraint(equalToConstant: 45),
leftButton.widthAnchor.constraint(equalToConstant: 30),
rightButton.widthAnchor.constraint(equalToConstant: 30),
])
Another small change
rightButton.imageView?.contentMode = .scaleAspectFit
leftButton.imageView?.contentMode = .scaleAspectFit
Result
Related
I am new to swift and am trying to achieve a rather simple task. I am creating an iPad application wherein I want to open the keyboard programmatically.
Keyboard should have a textField bar on top to record what's typed using the keyboard.
There should be a button right next to the textField.( as shown in the image )
I tried to achieve the same but doing this :
lazy var textFieldPanel: UIView = {
let view = UIView(frame: CGRect(x: 0.0, y: self.view.bounds.height, width: self.view.bounds.width, height: 50.0))
self.view.addSubview(view)
view.backgroundColor = UIColor.white
view.borderColor = UIColor.blue
view.borderWidth = 2.0
let fieldBottomConstraint = NSLayoutConstraint(item: view, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1.0, constant: 0.0)
customTextFieldBottomConstraint = fieldBottomConstraint
view.translatesAutoresizingMaskIntoConstraints = false
self.view.addConstraint(.init(item: view, attribute: .left, relatedBy: .equal, toItem: self.view, attribute: .left, multiplier: 1.0, constant: 0.0))
self.view.addConstraint(.init(item: view, attribute: .right, relatedBy: .equal, toItem: self.view, attribute: .right, multiplier: 1.0, constant: 0.0))
self.view.addConstraint(fieldBottomConstraint)
self.view.addConstraint(.init(item: view, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 50.0))
return view
}()
lazy var customTextField: UITextField = {
let field = UITextField(frame: CGRect(x: 0.0, y: 0.0, width: self.view.bounds.width - 300 , height: 50.0))
textFieldPanel.addSubview(field)
field.translatesAutoresizingMaskIntoConstraints = false
self.view.addConstraint(.init(item: field, attribute: .left, relatedBy: .equal, toItem: textFieldPanel, attribute: .left, multiplier: 1.0, constant: 8.0))
self.view.addConstraint(.init(item: field, attribute: .right, relatedBy: .equal, toItem: textFieldPanel, attribute: .right, multiplier: 1.0, constant: 8.0))
self.view.addConstraint(.init(item: field, attribute: .top, relatedBy: .equal, toItem: textFieldPanel, attribute: .top, multiplier: 1.0, constant: 2.0))
self.view.addConstraint(.init(item: field, attribute: .bottom, relatedBy: .equal, toItem: textFieldPanel, attribute: .bottom, multiplier: 1.0, constant: 2.0))
let button = UIButton(frame: CGRect(x: self.view.bounds.width - 350, y: 0.0, width: 200 , height: 50.0))
button.backgroundColor = UIColor.clear
button.setTitle("Send", for: .normal)
button.borderWidth = 2.0
button.borderColor = UIColor.blue
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
textFieldPanel.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
return field
}()
and then calling it in the IBAction like :
customTextField.becomeFirstResponder()
customTextFieldBottomConstraint?.constant = -360.0
The issue that I am facing is the following :
The view is comopletely distorted. ( as shown )
Send button View is distorted as it can be seen in the image.
While hiding the keyboard, the textField view still stays which I want it to be removed.
The view is not shifting up when the keyboard launches.
Can anyone help me as to what am I doing wrong here. Any help would be appreciated.
I'll start with better code readability suggestions for you:
Instead of creating constraints with init, there's a much cleaner way to do it:
.init(item: view, attribute: .left, relatedBy: .equal, toItem: self.view, attribute: .left, multiplier: 1.0, constant: 0.0)
// vs
view.leftAnchor.constraint(equalTo: self.view.leftAnchor),
Instead of self.view.addConstraint(...) for each constraint, you can easily activate a list of them like this:
NSLayoutConstraint.activate([
view.leftAnchor.constraint(equalTo: self.view.leftAnchor),
view.rightAnchor.constraint(equalTo: self.view.rightAnchor),
fieldBottomConstraint,
view.heightAnchor.constraint(equalToConstant: 50),
])
Now to your mistakes.
You're creating your views with a frame. It's totally pointless when you're using constraints, as auto layout will override original frames with constraint calculated ones.
You're providing no constraints to your button, that's why it's totally misplaced
You need to attach text field right to button left instead of superview right
If you wanna your view to be hidden when there's no keyboard, you need to set fieldBottomConstraint constant to your container height
So your fixed code constraints code will look like:
var customTextFieldBottomConstraint: NSLayoutConstraint!
lazy var textFieldPanel: UIView = {
let view = UIView(frame: CGRect(x: 0.0, y: self.view.bounds.height, width: self.view.bounds.width, height: 50.0))
self.view.addSubview(view)
view.backgroundColor = UIColor.white
let fieldBottomConstraint = view.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -100)
customTextFieldBottomConstraint = fieldBottomConstraint
view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
view.leftAnchor.constraint(equalTo: self.view.leftAnchor),
view.rightAnchor.constraint(equalTo: self.view.rightAnchor),
fieldBottomConstraint,
view.heightAnchor.constraint(equalToConstant: 50),
])
return view
}()
lazy var customTextField: UITextField = {
let field = UITextField()
field.translatesAutoresizingMaskIntoConstraints = false
textFieldPanel.addSubview(field)
let button = UIButton()
button.backgroundColor = UIColor.clear
button.setTitle("Send", for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
textFieldPanel.addSubview(button)
NSLayoutConstraint.activate([
field.leftAnchor.constraint(equalTo: textFieldPanel.leftAnchor),
field.topAnchor.constraint(equalTo: textFieldPanel.topAnchor),
field.bottomAnchor.constraint(equalTo: textFieldPanel.bottomAnchor),
button.widthAnchor.constraint(equalToConstant: 200),
button.leftAnchor.constraint(equalTo: field.rightAnchor),
button.rightAnchor.constraint(equalTo: textFieldPanel.rightAnchor),
button.topAnchor.constraint(equalTo: textFieldPanel.topAnchor),
button.bottomAnchor.constraint(equalTo: textFieldPanel.bottomAnchor),
])
return field
}()
And the last thing. You're adding a constant to your constraint when expect keyboard to appear. But keyboard size is different on different devices and even with different system settings
I suggest you using my KeyboardNotifier. This helper class will update your constraint constant according to keyboard appearance/disappearance. No need to update it during becomeFirstResponder anymore. Initialize it like this:
KeyboardNotifier(
parentView: view,
constraint: customTextFieldBottomConstraint
)
I have a Scroll View, in which I have a Stack View. In the Stack View I have arranged subviews of either UITextView or UILabel elements.
All is done programmatically, without storyboard.
The Scroll View appears and I can scroll it nicely. But unfortunately it scrolls not only vertically (top to bottom) but also horizontally (to the right, out the screen) which I don't want to (this is the reason I have numberOfLines set on the UILabel too, tried to set equal width to the scroll and stack views as the stack view's left/right attributes are connected to the view).
If it's important, this function is called either in viewDidLoad or upon touching a button later.
scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
let leftConstraintScroll = NSLayoutConstraint(item: scrollView, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 0)
let rightConstraintScroll = NSLayoutConstraint(item: scrollView, attribute: .right, relatedBy: .equal, toItem: view, attribute: .right, multiplier: 1, constant: 0)
let topConstraintScroll = NSLayoutConstraint(item: scrollView, attribute: .top, relatedBy: .equal, toItem: selectedTabIndicator, attribute: .bottom, multiplier: 1, constant: 10)
let bottomConstraintScroll = NSLayoutConstraint(item: scrollView, attribute: .bottom, relatedBy: .equal, toItem: editButton, attribute: .top, multiplier: 1, constant: 0)
view.addConstraints([leftConstraintScroll, rightConstraintScroll, topConstraintScroll, bottomConstraintScroll])
stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 10
stackView.isLayoutMarginsRelativeArrangement = true
stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 5, leading: 10, bottom: 5, trailing: 10)
// Several elements are added like this (UITextView):
let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.delegate = self
textView.isScrollEnabled = false
textView.font = UIFont.systemFont(ofSize: 15)
textView.backgroundColor = Constants.COLOR_P
textView.textColor = .black
textView.text = "XXX"
stackView.addArrangedSubview(textView)
// Or UILabel:
var label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textAlignment = .justified
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 15)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .justified
paragraphStyle.hyphenationFactor = 1.0
paragraphStyle.firstLineHeadIndent = 0
paragraphStyle.headIndent = 15
let hyphenAttribute = [NSAttributedString.Key.paragraphStyle: paragraphStyle]
let attributedString = NSMutableAttributedString(string: "XXXXX", attributes: hyphenAttribute)
label.attributedText = attributedString
stackView.addArrangedSubview(label)
scrollView.addSubview(stackView)
let leftConstraint = NSLayoutConstraint(item: stackView, attribute: .left, relatedBy: .equal, toItem: scrollView, attribute: .left, multiplier: 1, constant: 0)
let rightConstraint = NSLayoutConstraint(item: stackView, attribute: .right, relatedBy: .equal, toItem: scrollView, attribute: .right, multiplier: 1, constant: 0)
let topConstraint = NSLayoutConstraint(item: stackView, attribute: .top, relatedBy: .equal, toItem: scrollView, attribute: .top, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: stackView, attribute: .bottom, relatedBy: .equal, toItem: scrollView, attribute: .bottom, multiplier: 1, constant: 0)
scrollView.addConstraints([leftConstraint, rightConstraint, topConstraint, bottomConstraint, bottomConstraint])
Note: selectedTabIndicator and editButton are above and below the scroll view respectively.
First note: when posting code, post some actual code. Your code refers to scrollView and recipeScrollView which, I assume, are the same scroll view. Also, try to post complete information - your code also refers to selectedTabIndicator and editButton, neither of which have been identified nor described in your question.
Second note: start using more modern constraint syntax. For example:
NSLayoutConstraint.activate([
recipeScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0),
recipeScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0),
recipeScrollView.topAnchor.constraint(equalTo: selectedTabIndicator.bottomAnchor, constant: 10.0),
recipeScrollView.bottomAnchor.constraint(equalTo: editButton.topAnchor, constant: 0.0),
])
is much easier to use (and to read) than:
let leftConstraintScroll = NSLayoutConstraint(item: recipeScrollView, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 0)
let rightConstraintScroll = NSLayoutConstraint(item: recipeScrollView, attribute: .right, relatedBy: .equal, toItem: view, attribute: .right, multiplier: 1, constant: 0)
let topConstraintScroll = NSLayoutConstraint(item: recipeScrollView, attribute: .top, relatedBy: .equal, toItem: selectedTabIndicator, attribute: .bottom, multiplier: 1, constant: 10)
let bottomConstraintScroll = NSLayoutConstraint(item: recipeScrollView, attribute: .bottom, relatedBy: .equal, toItem: editButton, attribute: .top, multiplier: 1, constant: 0)
view.addConstraints([leftConstraintScroll, rightConstraintScroll, topConstraintScroll, bottomConstraintScroll])
Third note: respect the Safe Area... so your leading constraint should be:
recipeScrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0.0)
and so on.
Fourth note: constrain your scroll view's content to the .contentLayoutGuide, not to the scroll view itself.
To solve your "horizontal scrolling" issue, instead of setting the label and textView widths, set the width of the stack view relative to the scroll view's .frameLayoutGuide:
stackView.widthAnchor.constraint(equalTo: recipeScrollView.frameLayoutGuide.widthAnchor, constant: 0.0)
Here is your code, edited with those tips. I put a blue view near the top to be the selectedTabIndicator and a blue button near the bottom to be the editButton:
class AnotherScrollViewController: UIViewController, UITextViewDelegate {
var recipeScrollView: UIScrollView!
var stackView: UIStackView!
var textView: UITextView!
var selectedTabIndicator: UIView!
var editButton: UIButton!
var editButtonBottom: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
selectedTabIndicator = UIView()
selectedTabIndicator.backgroundColor = .blue
selectedTabIndicator.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(selectedTabIndicator)
editButton = UIButton()
editButton.backgroundColor = .blue
editButton.setTitle("Edit", for: [])
editButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(editButton)
recipeScrollView = UIScrollView()
recipeScrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(recipeScrollView)
let g = view.safeAreaLayoutGuide
editButtonBottom = editButton.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -4.0)
NSLayoutConstraint.activate([
selectedTabIndicator.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
selectedTabIndicator.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
selectedTabIndicator.widthAnchor.constraint(equalToConstant: 200.0),
selectedTabIndicator.heightAnchor.constraint(equalToConstant: 4.0),
//editButton.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 4.0),
editButtonBottom,
editButton.centerXAnchor.constraint(equalTo: g.centerXAnchor),
recipeScrollView.topAnchor.constraint(equalTo: selectedTabIndicator.bottomAnchor, constant: 10.0),
recipeScrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
recipeScrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
recipeScrollView.bottomAnchor.constraint(equalTo: editButton.topAnchor, constant: 0.0),
])
stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 10
stackView.isLayoutMarginsRelativeArrangement = true
stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 5, leading: 10, bottom: 5, trailing: 10)
// Several elements are added like this (UITextView):
textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.delegate = self
textView.isScrollEnabled = false
textView.font = UIFont.systemFont(ofSize: 15)
textView.backgroundColor = .cyan // Constants.COLOR_P
textView.textColor = .black
textView.text = "XXX"
stackView.addArrangedSubview(textView)
// Or UILabel:
var label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textAlignment = .justified
label.backgroundColor = .green // so we can easily see the label frame
label.textColor = .black
label.font = UIFont.systemFont(ofSize: 15)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .justified
paragraphStyle.hyphenationFactor = 1.0
paragraphStyle.firstLineHeadIndent = 0
paragraphStyle.headIndent = 15
let hyphenAttribute = [NSAttributedString.Key.paragraphStyle: paragraphStyle]
let labelString = "This is the string for the label. It will wrap if it is too long to fit in the allocated width."
//let attributedString = NSMutableAttributedString(string: "XXXXX", attributes: hyphenAttribute)
let attributedString = NSMutableAttributedString(string: labelString, attributes: hyphenAttribute)
label.attributedText = attributedString
stackView.addArrangedSubview(label)
recipeScrollView.addSubview(stackView)
let contentG = recipeScrollView.contentLayoutGuide
let frameG = recipeScrollView.frameLayoutGuide
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: contentG.topAnchor, constant: 0.0),
stackView.leadingAnchor.constraint(equalTo: contentG.leadingAnchor, constant: 0.0),
stackView.trailingAnchor.constraint(equalTo: contentG.trailingAnchor, constant: 0.0),
stackView.bottomAnchor.constraint(equalTo: contentG.bottomAnchor, constant: 0.0),
stackView.widthAnchor.constraint(equalTo: frameG.widthAnchor, constant: 0.0),
])
recipeScrollView.backgroundColor = .red
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
editButton.addTarget(self, action: #selector(self.editButtonTapped), for: .touchUpInside)
}
#objc func editButtonTapped() -> Void {
if textView.isFirstResponder {
textView.resignFirstResponder()
} else {
textView.becomeFirstResponder()
}
}
#objc func adjustForKeyboard(notification: Notification) {
guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardScreenEndFrame = keyboardValue.cgRectValue
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
print(keyboardViewEndFrame.height)
var c: CGFloat = -4.0
if notification.name != UIResponder.keyboardWillHideNotification {
c -= (keyboardViewEndFrame.height - view.safeAreaInsets.bottom)
}
editButtonBottom.constant = c
editButton.setTitle(c == -4 ? "Edit" : "Done", for: [])
}
}
I could solve it, maybe not the best solution, so I leave the question still open for a while, for better solutions.
Basically, I provided a widthAnchor constraint to each UILabel and UITextView when I created them, before adding them to the stack view.
label.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width - 20).isActive = true
I have the following UIView, how can text be added in the center of this UIView programmatically?
This is the UIView code:
let newView = UIView()
newView.backgroundColor = .groupTableViewBackground
self.view.addSubview(newView)
newView.translatesAutoresizingMaskIntoConstraints = false
if #available(iOS 11.0, *) {
let guide = self.view.safeAreaLayoutGuide
newView.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
newView.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
newView.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
newView.heightAnchor.constraint(equalToConstant: 50).isActive = true
} else {
NSLayoutConstraint(item: newView,
attribute: .top,
relatedBy: .equal,
toItem: view, attribute: .top,
multiplier: 1.0, constant: 0).isActive = true
NSLayoutConstraint(item: newView,
attribute: .leading,
relatedBy: .equal, toItem: view,
attribute: .leading,
multiplier: 1.0,
constant: 0).isActive = true
NSLayoutConstraint(item: newView, attribute: .trailing,
relatedBy: .equal,
toItem: view,
attribute: .trailing,
multiplier: 1.0,
constant: 0).isActive = true
newView.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
This was my attempt at adding text to the center of the UIView programmatically, but I'm not sure why it is not working?
let lb = UILabel()
lb.centerXAnchor.constraint(equalTo: newView.centerXAnchor).isActive = true
lb.centerYAnchor.constraint(equalTo: newView.centerYAnchor).isActive = true
lb.text="anything"
newView.backgroundColor = UIColor.white
// show on screen
self.view.addSubview(newView)
newView.addSubview(lb)
lb.center = newView.center
UPDATE:
How can this button border below the UIView that's under the navigation controller be added?
When it comes to adding constraints to the view programmatically, note that you have to set translatesAutoresizingMaskIntoConstraints to false (but I get the feeling that you already know this, and just forgot to add it to the label)
let lb = UILabel()
lb.textAlignment = .center
lb.numberOfLines = 0
newView.addSubview(lb)
lb.translatesAutoresizingMaskIntoConstraints = false
lb.centerXAnchor.constraint(equalTo: newView.centerXAnchor).isActive = true
lb.centerYAnchor.constraint(equalTo: newView.centerYAnchor).isActive = true
Add border (assuming you want to add the border to the newView):
let border = UIView()
newView.addSubview(border)
border.translatesAutoresizingMaskIntoConstraints = false
border.leadingAnchor.constraint(equalTo: newView.leadingAnchor).isActive = true
border.trailingAnchor.constraint(equalTo: newView.trailingAnchor).isActive = true
border.bottomAnchor.constraint(equalTo: newView.bottomAnchor).isActive = true
border.heightAnchor.constraint(equalToConstant: 1).isActive = true
But for the border it would probably be cleaner to write an extension on UIView, so the code can be reusable in other places, if you ever need it again
the below code is the viewController that is navigated with a navigationItem navigationItem.leftBarButtonItem = UIBarButtonItem(title: from a previous tableViewController and I am trying to produce a UIView in my viewController. However nothing is showing.
import UIKit
class SecondpgController: UIViewController {
var inputContainerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor .gray
let inputContainerView = UIView()
self.view.addSubview(inputContainerView)
//inputContainerView.backgroundColor = UIColor(red: 162/255, green: 20/255, blue: 35/255, alpha: 1)
inputContainerView.backgroundColor = .white
inputContainerView.translatesAutoresizingMaskIntoConstraints = false
//inputContainerView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 300).isActive = true
//nputContainerView.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 10).isActive = true
inputContainerView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: 200).isActive = true
inputContainerView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: 200).isActive = true
The app is running well however after reaching the view controller, Nothing is shown but only a grey screen.
Why is this happening and how can I solve it?
Your problem is that you constraints are only setting x and y position for the UIView you are creating. You are missing width and height.
Try this constraints:
inputContainerView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
inputContainerView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
inputContainerView.widthAnchor.constraint(equalToConstant: 100).isActive = true
inputContainerView.heightAnchor.constraint(equalToConstant: 100).isActive = true
This code means that your UIView will be centred vertically and horizontally in your self.view and also it has a width and height of 100. It will look like this:
Hope it helps. Happy Coding
You have added constraints to place inputContainerView to the centre of the screen but did not add constraints for it's height and width. Adding the following code might solve your issue:
inputContainerView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: 0).isActive = true
inputContainerView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: 0).isActive = true
inputContainerView.addConstraint(NSLayoutConstraint(item: inputContainerView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 200))
inputContainerView.addConstraint(NSLayoutConstraint(item: inputContainerView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 200))
Check this: https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html
Ensure that the constants for CenterXAnchor and CenterYAnchor are 0, so the view aligns at the centre. And make sure you add inputContainerView only once either as an outlet or programmatically.
So I set up a facebook button in iOS as ..
main_view.addSubview(FBlogBut)
NSLayoutConstraint(item: FBlogBut, attribute: .width, relatedBy: .equal, toItem: main_view, attribute: .width, multiplier: 0.95, constant: 0 ).isActive = true
NSLayoutConstraint(item: FBlogBut, attribute: .centerX, relatedBy: .equal, toItem: loginView, attribute: .centerX, multiplier: 1, constant: 0 ).isActive = true
topFBGuide = NSLayoutConstraint(item: FBlogBut, attribute: .top, relatedBy: .equal, toItem: login_button, attribute: .bottom, multiplier: 1, constant: 10 )
NSLayoutConstraint(item: FBlogBut, attribute: .bottom, relatedBy: .greaterThanOrEqual, toItem: loginView, attribute: .bottom, multiplier: 1, constant: 10 ).isActive = true
NSLayoutConstraint.activate([topFBGuide!])
main_view.layoutIfNeeded()
after a button click on the layout I do the following..
print("flip to register")
FBSDKLoginManager().logOut()
NSLayoutConstraint.deactivate([topFBGuide!])
topFBGuide2 = NSLayoutConstraint(item: FBlogBut, attribute: .top, relatedBy: .equal, toItem: register_button, attribute: .bottom, multiplier: 1, constant: 30 )
NSLayoutConstraint.activate([topFBGuide2!])
let buttonText = NSAttributedString(string: "Register with Facebook")
FBlogBut.setAttributedTitle(buttonText, for: .normal)
main_view.layoutIfNeeded()
So as you can see it is not moving correctly to the new constraint which should be 30 under the reg button
Thanks
R
Try using a StackView
Check below Code File
Instead of Changing constraints again and again Just Use a StackView and Set Login Button Constraint to StackView as I had Used
class ConstraintsViewController: UIViewController {
var TF1 = UITextField()
var TF2 = UITextField()
var TF3 = UITextField()
var TF4 = UITextField()
var toggleButton = UIButton()
var FBLoginBtn = UIButton()
var stackView = UIStackView()
var isChecked = true
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
self.setUpView()
}
func setUpView()
{
///Top Button as Toggle
toggleButton.backgroundColor = UIColor.lightGray
toggleButton.setTitle("Let's Toggle", for: .normal)
toggleButton.setTitleColor(UIColor.black, for: .normal)
toggleButton.addTarget(self, action: #selector(ConstraintsViewController.buttonAction(sender:)), for: .touchUpInside)
self.view.addSubview(toggleButton)
toggleButton.translatesAutoresizingMaskIntoConstraints = false
toggleButton.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 100).isActive = true
toggleButton.heightAnchor.constraint(equalToConstant: self.view.frame.size.height*0.06).isActive = true
toggleButton.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
toggleButton.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
///TF That you want to add
TF1.placeholder = "Email"
TF1.widthAnchor.constraint(equalToConstant: 100).isActive = true
TF1.heightAnchor.constraint(equalToConstant: 30).isActive = true
TF2.placeholder = "Passowrd"
TF2.widthAnchor.constraint(equalToConstant: 100).isActive = true
TF2.heightAnchor.constraint(equalToConstant: 30).isActive = true
TF3.placeholder = "ZipCode"
TF3.widthAnchor.constraint(equalToConstant: 100).isActive = true
TF3.heightAnchor.constraint(equalToConstant: 30).isActive = true
TF4.placeholder = "Mobile Number"
TF4.widthAnchor.constraint(equalToConstant: 100).isActive = true
TF4.heightAnchor.constraint(equalToConstant: 30).isActive = true
///Stack View that will store all the required TF
stackView.axis = UILayoutConstraintAxis.vertical
stackView.distribution = UIStackViewDistribution.fill
stackView.alignment = UIStackViewAlignment.center
stackView.spacing = 20
///Add 2 TF in StackView
stackView.addArrangedSubview(TF1)
TF1.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(TF2)
TF2.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(stackView)
///Properties
stackView.translatesAutoresizingMaskIntoConstraints = false;
///Frame
stackView.topAnchor.constraint(equalTo: toggleButton.bottomAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: toggleButton.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: toggleButton.trailingAnchor).isActive = true
///UIButton
FBLoginBtn.backgroundColor = UIColor.lightGray
FBLoginBtn.setTitle("Login to Facebook", for: .normal)
FBLoginBtn.setTitleColor(UIColor.black, for: .normal)
self.view.addSubview(FBLoginBtn)
FBLoginBtn.translatesAutoresizingMaskIntoConstraints = false
FBLoginBtn.topAnchor.constraint(equalTo: stackView.bottomAnchor).isActive = true
FBLoginBtn.heightAnchor.constraint(equalToConstant: 50).isActive = true
FBLoginBtn.leadingAnchor.constraint(equalTo: stackView.leadingAnchor).isActive = true
FBLoginBtn.trailingAnchor.constraint(equalTo: stackView.trailingAnchor).isActive = true
}
#objc func buttonAction(sender:UIButton)
{
isChecked = !isChecked
if isChecked {
self.stackView.removeArrangedSubview(TF3)
self.stackView.removeArrangedSubview(TF4)
TF3.removeFromSuperview()
TF4.removeFromSuperview()
} else {
self.stackView.addArrangedSubview(TF3)
self.stackView.addArrangedSubview(TF4)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Screenshot
When Loaded for First Time
When Button Is toggles
Helps you not to Update Constraints again and again