Constraining Text Field to Button in Swift - swift

I have a button I created in my storyboard, but have a custom UITextField that needs to be implemented in code, I am trying to place the text field so that it's 20 pixels above the button, but I am not getting the desired result for some reason. Am I missing something or is this not how you implement it? here's my implementation and output:
#IBOutlet weak var buyPointsButton: UIButton!
lazy var cardTextField: STPPaymentCardTextField = {
let cardTextField = STPPaymentCardTextField()
return cardTextField
}()
In viewDidLoad
self.view.addSubview(cardTextField)
cardTextField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
NSLayoutConstraint(item: cardTextField, attribute: .bottom, relatedBy: .equal, toItem: buyPointsButton, attribute: .top, multiplier: 1, constant: 70),
NSLayoutConstraint(item: cardTextField, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: buyPointsButton.frame.width),
NSLayoutConstraint(item: cardTextField, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1, constant: 0),
NSLayoutConstraint(item: cardTextField, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 28)
])

The main problem is that you are setting the text field's .centerY to the view's .centerY and you're constraining the Bottom of the text field to the Top of the button...
You'll find it much easier to work with constraints if you use the more "modern" syntax, and include comments telling yourself what the constraints are - or at least, should be - doing.
For example:
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(cardTextField)
cardTextField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// Bottom of text field 20-points above Top of button
cardTextField.bottomAnchor.constraint(equalTo: buyPointsButton.topAnchor, constant: -20.0),
// text field Width equal to button Width
cardTextField.widthAnchor.constraint(equalTo: buyPointsButton.widthAnchor),
// text field centered horizontally to button
cardTextField.centerXAnchor.constraint(equalTo: buyPointsButton.centerXAnchor),
// text field Height equal to 28
cardTextField.heightAnchor.constraint(equalToConstant: 28.0),
])
}

Related

Custom View in Cocoa can't draw after setting AutoLayout Constraints

On a MacOS Project.
I'm trying to draw a line under a text field (so that by making the text field transparent I can create a "please fill in the blank" style of view that asks the user to input the data). I created a simple subclass of NSView called lineDrawer just to draw the line, then I tried to add an instance of a lineDrawer as a subview to the view where I let the user enter his name. The line should be drawn right below the transparent text field. The text field is an IBOutlet of the view and already in correct position, and I want the line to have the exact same frame as the text field, so that if I draw a path from the lineDrawer's (minX, minY)to (maxX, minY), the path is exactly below the transparent text field.
I can't make it work because after I set
lineView.translatesAutoresizingMaskIntoConstraints = false
and added my own constraints, the custom view wouldn't draw. I think it has something to do with me not giving the program enough information about where exactly to draw the view, but I can't figure out what's lacking here. BTW, if I don't set the constraints, it would just draw the view at its frame's place. If I set the constraints, nothing shows.
The NickName: NSViewController
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
greetingLine.font = .labelFont(ofSize: 50)
nameField.font = .labelFont(ofSize: 50)
nameField.isBezeled = false
let lineView = lineDrawer.init()
lineView.setFrameSize(nameField.frame.size)
self.view.addSubview(lineView)
lineView.translatesAutoresizingMaskIntoConstraints = false
let lineConstraintX = NSLayoutConstraint.init(item: lineView, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1, constant: 0)
let lineConstraintY = NSLayoutConstraint.init(item: lineView, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1.3, constant: 0)
self.view.addConstraints([lineConstraintY, lineConstraintX])
}
#IBOutlet weak var nameField: NSTextField!
#IBOutlet weak var greetingLine: NSTextField!
and the lineDrawer: NSView:
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
let context = NSGraphicsContext.current?.cgContext
context!.beginPath()
context!.move(to: CGPoint(x: self.bounds.minX, y: self.bounds.minY))
let endPoint = CGPoint.init(x: self.bounds.maxX, y: self.bounds.minY)
context!.addLine(to: endPoint)
context!.move(to: CGPoint(x: 0, y: 0))
context!.addLine(to: CGPoint(x: 10, y: 10))
let loadedColor = ColorGetter.getCurrentThemeColor()
context!.setStrokeColor(loadedColor.cgColor)
context!.setLineWidth(5)
context!.strokePath()
print("drawing")
}
Thanks very much in advance to anyone who would offer help on this!
Can you, please, add behind your layout constraint the variable isActive and set it to be true. Then ist should work.
So, the code you have:
let lineConstraintX = NSLayoutConstraint.init(item: lineView, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1, constant: 0)
let lineConstraintY = NSLayoutConstraint.init(item: lineView, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1.3, constant: 0)
Must be changed in:
NSLayoutConstraint.init(item: lineView, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint.init(item: lineView, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1.3, constant: 0).isActive = true
Then the auto layout constraint is activated and works. Currently, you did set the auto layout constraint, but you didn't activate it.
Answering my own question:
I added 2 constraints to the custom view's width and height by
NSLayoutConstraint.init(item: lineView, attribute: .width, relatedBy: .equal, toItem: self.nameField, attribute: .width, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint.init(item: lineView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 100).isActive = true
I think by making
lineView.translatesAutoresizingMaskIntoConstraints = false
I was removing the program's knowledge about both the custom view's position and size, and so I had to re-specify them both manually?
Weird that the program doesn't imply what the size of the custom view should be from its frame's size, which was set.

A layout issue (NSLayoutConstraint) in an iOS app

In an iOS app I have an autolayout issue.
The 2 following screenshots show the problem.
The switch (UISwitch object) on the right is displaced horizontally, when it should be fixed. Can anyone see what is happening?
It is true that the string on the left is changing length, but I think (according to the way I have set the constraints up) the font should be resized or the string split in 2 lines; but not the switch displaced.
Here is the relevant swift code:
import UIKit
class My_ViewController: UIViewController {
let xPanel = UILabel(), yPanel = UILabel(),
khToggle = UISwitch(), khLabel = UILabel()
....
override func viewDidLoad() {
super.viewDidLoad()
layOutUI()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
.....
toggleKeepHide(khToggle)
}
func layOutUI() {
for component in [xPanel,yPanel,khLabel,khToggle] {
component.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(component)
}
...........
khLabel.numberOfLines = 0
khLabel.adjustsFontSizeToFitWidth = true
khToggle.addTarget(self,
action: #selector(toggleKeepHide(_:)),
for: .valueChanged)
view.addConstraints([
.........
NSLayoutConstraint(item: khToggle, attribute: .right, relatedBy: .equal,
toItem: view, attribute: .right, multiplier: 1.0, constant: -30.0),
NSLayoutConstraint(item: khToggle, attribute: .top, relatedBy: .equal,
toItem: yPanel, attribute: .bottom, multiplier: 1.0,
constant: 50.0),
NSLayoutConstraint(item: khLabel, attribute: .right, relatedBy: .equal,
toItem: khToggle, attribute: .left, multiplier: 1.0, constant: -23.0),
NSLayoutConstraint(item: khLabel, attribute: .centerY, relatedBy: .equal,
toItem: khToggle, attribute: .centerY, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: khLabel, attribute: .left, relatedBy: .equal,
toItem: view, attribute: .left, multiplier: 1.0, constant: 30.0)])
}
#objc func toggleKeepHide(_ sender: UISwitch) {
if sender.isOn {khLabel.text = "Hide this object from the wyxoug list."}
else {khLabel.text = "Keep this object in the wyxoug list."}
}
}
You haven't provided sufficient information to reproduce the problem. Here's a reduction of your code, in my view controller's viewDidLoad (I eliminated everything but the label and the switch, fixed your left and right (you should never use those), and changed the alignment between the two views to top instead of center):
khToggle = UISwitch()
khToggle.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(khToggle)
khLabel = UILabel()
khLabel.text = String(repeating: "word ", count: 40)
khLabel.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(khLabel)
// Do any additional setup after loading the view.
khLabel.numberOfLines = 0
view.addConstraints([
NSLayoutConstraint(item: khToggle!, attribute: .trailing, relatedBy: .equal,
toItem: view, attribute: .trailing, multiplier: 1.0, constant: -30.0),
NSLayoutConstraint(item: khToggle!, attribute: .top, relatedBy: .equal,
toItem: view, attribute: .top, multiplier: 1.0,
constant: 50.0),
NSLayoutConstraint(item: khLabel!, attribute: .trailing, relatedBy: .equal,
toItem: khToggle, attribute: .leading, multiplier: 1.0, constant: -23.0),
NSLayoutConstraint(item: khLabel!, attribute: .top, relatedBy: .equal,
toItem: khToggle, attribute: .top, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: khLabel!, attribute: .leading, relatedBy: .equal,
toItem: view, attribute: .leading, multiplier: 1.0, constant: 30.0)])
The result displays fine; there are no constraint conflicts or ambiguities, and it looks as one would expect:
The horizontal constraints for the label and the toggle are competing against each other.
(If you step in to Xcode's visual debugger, you'll find that there is a warning: the UISwitch instance has an ambiguous width and horizontal position.)
Solution
You have provided absolute values to AutoLayout, and it cannot resolve the requirements. To fix this, introduce some flexibility in to the requirements by setting the compression resistance of the label to a lower value:
khLabel.setCompressionResistance(.defaultLow, for: .horizontal)

How to put a uiview back in a stackview after I took it out?

I have a viewcontroller that holds multiple stackviews. There is a button that when pressed, the corresponding uiview wil become fullscreen inside the original view. There is a different button that is supposed to make the uiview go back to its original stackview. The uiview itself contains other views. I am having some problems doing that. The uiview does end up in the stackview, but not near the same size/place it used to be. I am not sure how to solve this, and been going at it for several hours now, looking at multiple sources.
This is the code that makes the uiview go fullscreen:
private func moveToFrontOfCardView(v: UIView) {
originalView = v.superview
if let stack = originalView as? UIStackView {
stack.removeArrangedSubview(v)
}
myCardView.addSubview(v)
let topConstraint = NSLayoutConstraint(item: v, attribute: .top, relatedBy: .equal, toItem: myCardView, attribute: .top, multiplier: 1, constant: 10)
let bottomConstraint = NSLayoutConstraint(item: v, attribute: .bottom, relatedBy: .equal, toItem: myCardView, attribute: .bottom, multiplier: 1, constant: -10)
let leftConstraint = NSLayoutConstraint(item: v, attribute: .left, relatedBy: .equal, toItem: myCardView, attribute: .left, multiplier: 1, constant: 10)
let rightConstraint = NSLayoutConstraint(item: v, attribute: .right, relatedBy: .equal, toItem: myCardView, attribute: .right, multiplier: 1, constant: -10)
myCardView.addConstraints([topConstraint, bottomConstraint, leftConstraint, rightConstraint])
}
And this is the code I use when I want it to go back:
private func moveToOriginalPosition(v: UIView) {
if let stack = originalView as? UIStackView {
stack.addArrangedSubview(v)
}
}
Does anyone have a clue how I could fix this?
EDIT
I've tried Saqib and Bilals answer, but I get this as a result:
Declare a class variable for tracking view's index
var selectedIndex = 0 // Contains Current Seleceted view's index
overrie func viewDidLoad() { ...
Before removing view from stackview get the view index like this selectedIndex = stack.subviews.index(of: v)
keep reference to all the constraints.
Before adding it back disable all the constraints topConstraint.isActive = false
Now add the view at the same index using stack.insertArrangedSubview(view, at: selectedIndex)
An other option is to create a same new view and just hide/unhide the one in stackview. StackView automatically fills the space accordingly for the hidden views.
You should deActivate the constraints you added to view when removed it from stackView, at the time you want add the view to the stackView again.
For this you should make the constraints instance of your viewController class and next, write your moveToOriginalPosition(v: UIView) method like this:
private func moveToOriginalPosition(v: UIView) {
if let stack = originalView as? UIStackView {
stack.addArrangedSubview(v)
topConstraint.isActive = false
bottomConstraint.isActive = false
leftConstraint.isActive = false
rightConstraint.isActive = false
}
}
Ofcourse, you should remove, these lines of codes from moveToFrontOfCardView(v: UIView) method:
self.topConstraint = NSLayoutConstraint(item: v, attribute: .top, relatedBy: .equal, toItem: myCardView, attribute: .top, multiplier: 1, constant: 10)
self.bottomConstraint = NSLayoutConstraint(item: v, attribute: .bottom, relatedBy: .equal, toItem: myCardView, attribute: .bottom, multiplier: 1, constant: -10)
self.leftConstraint = NSLayoutConstraint(item: v, attribute: .left, relatedBy: .equal, toItem: myCardView, attribute: .left, multiplier: 1, constant: 10)
self.rightConstraint = NSLayoutConstraint(item: v, attribute: .right, relatedBy: .equal, toItem: myCardView, attribute: .right, multiplier: 1, constant: -10)
myCardView.addConstraints([topConstraint, bottomConstraint, leftConstraint, rightConstraint])
and add them where you make your view initialized. and replace below lines with above lines in moveToFrontOfCardView(v: UIView) method:
topConstraint.isActive = true
bottomConstraint.isActive = true
leftConstraint.isActive = true
rightConstraint.isActive = true
By the looks of things you don't need to remove the original view. You could make a copy of it then display the copy full screen. Then when you dismiss this copy you release the reference to it

How to add equal width and equal height constraints programmatically in swift?

Could anyone help me I have this simple view that is very simple to do if I'm using story board but all of my UILabel is based on somewhere(API) that is why I need to create my view programmatically.
this is the view I would like to achieved
what I accomplished at the moment are pinning the top label in its position and adding leading space on label 1, 2, & 3
basically what i need are:
pin the label 1 to the top label
pin the label 3 to bottom
add vertical spaces to label 1,2,3
have equal width and height constraint in label 1,2 ,3
this is my code so far.
private func addTopConstraint(from:AnyObject,to:AnyObject,cons:CGFloat){
let topConstraint = NSLayoutConstraint(item: from, attribute: .Top, relatedBy: .Equal, toItem: to, attribute: .Bottom, multiplier: 1.0, constant: cons)
self.view.addConstraint(topConstraint)
}
private func addVerticalSpace(from:AnyObject,to:AnyObject,cons:CGFloat){
let verticalSpace = NSLayoutConstraint(item: from, attribute: .Bottom, relatedBy: .Equal, toItem: to, attribute: .Bottom, multiplier: 1.0, constant: cons)
self.view.addConstraint(verticalSpace)
}
private func addLeadingSpace(from:AnyObject,to:AnyObject,cons:CGFloat){
let leadingSpace = NSLayoutConstraint(item: from, attribute: .Leading, relatedBy: .Equal, toItem: to, attribute: .Leading, multiplier: 1.0, constant: cons)
self.view.addConstraint(leadingSpace)
}
private func sameWidthAndHeight (from:AnyObject,to:AnyObject){
let width = NSLayoutConstraint(item: from, attribute: .Width, relatedBy: .Equal, toItem: to, attribute: .Width, multiplier: 1.0, constant: 0)
let height = NSLayoutConstraint(item: from, attribute: .Height, relatedBy: .Equal, toItem: to, attribute: .Height, multiplier: 1.0, constant: 0)
self.view.addConstraint(width)
self.view.addConstraint(height)
}
but the sameWidthAndHeight and addVerticalSpace function fails
maybe it something to do with content hugging and priorities too, I'm not that sure since we're used to do this in storyboard.
could anyone share a thought? Thanks in advance

Adding constraints programmatically in UIView with UITextView

I added constraints programmatically in my UITextView, but trailing and bottom constraints are not working correctly. others work fine.
I think UITextView frame size is not correct.
I just want to add margin in my UITextView programmatically.
My code is
let textView = UITextView()
self.view.addSubview(textView)
var constraints = [NSLayoutConstraint]()
constraintTop = NSLayoutConstraint(item: textView,
attribute: NSLayoutAttribute.Top,
relatedBy: NSLayoutRelation.Equal,
toItem: self.view,
attribute: NSLayoutAttribute.Top,
multiplier: 1.0,
constant: 10)
constraints.append(constraintTop)
constraintBottom = NSLayoutConstraint(item: textView,
attribute: NSLayoutAttribute.Bottom,
relatedBy: NSLayoutRelation.Equal,
toItem: self.view,
attribute: NSLayoutAttribute.Bottom,
multiplier: 1.0,
constant: 10)
constraints.append(constraintBottom)
constraintLeft = NSLayoutConstraint(item: textView,
attribute: NSLayoutAttribute.Leading,
relatedBy: NSLayoutRelation.Equal,
toItem: self.view,
attribute: NSLayoutAttribute.Leading,
multiplier: 1.0,
constant: 10)
constraints.append(constraintLeft)
constraintRight = NSLayoutConstraint(item: textView,
attribute: NSLayoutAttribute.Trailing,
relatedBy: NSLayoutRelation.Equal,
toItem: self.view,
attribute: NSLayoutAttribute.Trailing,
multiplier: 1.0,
constant: 10)
constraints.append(constraintRight)
self.view.addConstraints(constraints)
You can fix this in one of two ways:
Change the constant to -10 for your bottom and trailing constraints.
or
Switch the order of the item: and toItem: values in the bottom and trailing constraints. That is, make item: self.view and toItem: textView for the bottom and trailing constraints. This is how it is done if you set the constraints in the StoryBoard.