I'm adding from a xib a view into my ViewController. Then I'm putting its constraints to actually fit it
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
...
...
view!.addSubview(gamePreview)
gamePreview.translatesAutoresizingMaskIntoConstraints = false
if #available(iOS 9.0, *) {
// Pin the leading edge of myView to the margin's leading edge
gamePreview.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor).active = true
//Pin the trailing edge of myView to the margin's trailing edge
gamePreview.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor).active = true
} else {
// Fallback on earlier versions
view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .TrailingMargin, relatedBy: .Equal, toItem: view, attribute: .TrailingMargin, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .LeadingMargin, relatedBy: .Equal, toItem: view, attribute: .LeadingMargin, multiplier: 1, constant: 0))
}
view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Top, relatedBy: .Equal, toItem: self.topLayoutGuide, attribute: .Bottom, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute,multiplier: 1, constant: 131))
}
What i'm trying to do: To actually fit my view constraining it to top, leading, trailing to ViewController's view, and with a prefixed height. The view I'm adding to main view has its own transparent-background view, so no need of margin (the view is meant to be device's width size, so).
I've placed 2 couples of lines that would be supposed to be equal (in my attempts), with the if, because the first 2 lines in if are actually available in iOS9> only, while I'm attempting to do the same thing in the else statement, for every device (starting from iOS 8).
This is what I get:
iOS9+ at left, iOS8+ at right. Transparent background was colored red to show what happens (don't mind at different height in the images, they're equal height in app, instead look at added margins at left and right)
I also tried AutoLayoutDSL-Swift
, but doesn't help, I'm not expert with it but every attempt made only things worse.
How can I write those constraints using classic NSLayoutConstraints methods, and how could I write all in a better way with a framework like AutoLayoutDSL or a fork of it? (or an alternative, though now mostly I'm concerned on official libs)
Add the button in the view, give it constraints and as you are using constraints, you can skip the button. frame and add widthAnchor and heightAnchor . At last activate them and keep translatesAutoresizingMaskIntoConstraints as false . Also, it will be better if you can add proper names.
In this article we will learn about the difference between leading and left, trailing and right constraints in Swift. In short, by setting the leading constraint we set the starting point of a view, while the trailing constraint sets the ending point.
You need to use the leading
and trailing
attributes, not the leadingMargin
and trailingMargin
attributes:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
...
...
view!.addSubview(gamePreview)
gamePreview.translatesAutoresizingMaskIntoConstraints = false
view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Trailing, relatedBy: .Equal, toItem: view, attribute: .Trailing, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Leading, relatedBy: .Equal, toItem: view, attribute: .Leading, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Top, relatedBy: .Equal, toItem: self.topLayoutGuide, attribute: .Bottom, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute,multiplier: 1, constant: 131))
}
Guessed it thanks to @Paulw11... this is the solution
view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Trailing, relatedBy: .Equal, toItem: view, attribute: .Trailing, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Leading, relatedBy: .Equal, toItem: view, attribute: .Leading, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Top, relatedBy: .Equal, toItem: self.topLayoutGuide, attribute: .Bottom, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute,multiplier: 1, constant: 131))
Also I managed to rewrite it as it follows using AutoLayoutDSL-Swift
for anyone interested
view => gamePreview.trailing == view.trailing
=> gamePreview.leading == view.leading
=> gamePreview.height == 131
=> gamePreview.top == view.top + self.navigationController!.navigationBar.bounds.height + UIApplication.sharedApplication().statusBarFrame.size.height
The last line is a little long because view.top refers to the really view top and doesn't consider padding added by both statusBar and navigationBar heights. They could be replaced as constants, waiting if someone comes up with a more elegant solution.
SWIFT 4 Update
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.addConstraint(NSLayoutConstraint(item: stackView, attribute: .trailing, relatedBy: .equal, toItem: stackView, attribute: .trailing, multiplier: 1, constant: 0))
stackView.addConstraint(NSLayoutConstraint(item: stackView, attribute: .leading, relatedBy: .equal, toItem: stackView, attribute: .leading, multiplier: 1, constant: 0))
stackView.addConstraint(NSLayoutConstraint(item: stackView, attribute: .top, relatedBy: .equal, toItem: self.addLayoutGuide, attribute: .bottom, multiplier: 1, constant: 0))
stackView.addConstraint(NSLayoutConstraint(item: stackView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute,multiplier: 1,
constant: 131))
Why don't you use constraint activation? You could do this:
var anchors = [NSLayoutConstraint]()
anchors.append(view.topAnchor.constraint(equalTo: gamePreview.topAnchor, constant: 0))
anchors.append(view.leadingAnchor.constraint(equalTo: gamePreview.leadingAnchor, constant: 0))
anchors.append(view.trailingAnchor.constraint(equalTo: gamePreview.trailingAnchor, constant: 0))
anchors.append(view.heightAnchor.constraint(equalTo: gamePreview.heightAnchor, multiplier: 1, constant: 131))
NSLayoutConstraint.activate(anchors)
This is another. Hope it helps. You could swap view
and gamePreview
depending on the parent / child, or you could create a general helper extension for your constraints. I found one in a tutorial provided by a YouTuber Here and slightly modified it to accept leading/trailing, left/right constraints. You could also add safeLayoutGuide
to the below as well, which makes it future-friendly. Something like this:
func anchor(_ top: UIView? = nil, left: UIView? = nil, bottom: UIView? = nil, right: UIView? = nil, width: UIView? = nil, height: UIView? = nil, topConstant: CGFloat = 0, leftConstant: CGFloat = 0, bottomConstant: CGFloat = 0, rightConstant: CGFloat = 0, widthConstant: CGFloat = 0, heightConstant: CGFloat = 0, isForLeading: Bool = false) -> [NSLayoutConstraint] {
translatesAutoresizingMaskIntoConstraints = false
var anchors = [NSLayoutConstraint]()
if #available(iOS 11.0, *) {
if let top = top {
anchors.append(topAnchor.constraint(equalTo: top.safeAreaLayoutGuide.topAnchor, constant: topConstant))
}
if let left = left {
if isForLeading {
anchors.append(leadingAnchor.constraint(equalTo: left.safeAreaLayoutGuide.leadingAnchor, constant: leftConstant))
} else {
anchors.append(leftAnchor.constraint(equalTo: left.safeAreaLayoutGuide.leftAnchor, constant: leftConstant))
}
}
if let bottom = bottom {
anchors.append(bottomAnchor.constraint(equalTo: bottom.safeAreaLayoutGuide.bottomAnchor, constant: -bottomConstant))
}
if let right = right {
if isForLeading {
anchors.append(trailingAnchor.constraint(equalTo: right.safeAreaLayoutGuide.leadingAnchor, constant: rightConstant))
} else {
anchors.append(rightAnchor.constraint(equalTo: right.safeAreaLayoutGuide.rightAnchor, constant: -rightConstant))
}
}
if let width = width {
anchors.append(widthAnchor.constraint(equalTo: width.safeAreaLayoutGuide.widthAnchor, multiplier: 1, constant: widthConstant))
} else if widthConstant > 0 {
anchors.append(widthAnchor.constraint(equalToConstant: widthConstant))
}
if let height = height {
anchors.append(heightAnchor.constraint(equalTo: height.safeAreaLayoutGuide.heightAnchor, multiplier: 1, constant: widthConstant))
} else if heightConstant > 0 {
anchors.append(widthAnchor.constraint(equalToConstant: heightConstant))
}
anchors.forEach({$0.isActive = true})
} else {
if let top = top {
anchors.append(topAnchor.constraint(equalTo: top.topAnchor, constant: topConstant))
}
if let left = left {
if isForLeading {
anchors.append(leadingAnchor.constraint(equalTo: left.leadingAnchor, constant: leftConstant))
} else {
anchors.append(leftAnchor.constraint(equalTo: left.leftAnchor, constant: leftConstant))
}
}
if let bottom = bottom {
anchors.append(bottomAnchor.constraint(equalTo: bottom.bottomAnchor, constant: -bottomConstant))
}
if let right = right {
if isForLeading {
anchors.append(trailingAnchor.constraint(equalTo: right.leadingAnchor, constant: rightConstant))
} else {
anchors.append(rightAnchor.constraint(equalTo: right.rightAnchor, constant: -rightConstant))
}
}
if let width = width {
anchors.append(widthAnchor.constraint(equalTo: width.widthAnchor, multiplier: 1, constant: widthConstant))
} else if widthConstant > 0 {
anchors.append(widthAnchor.constraint(equalToConstant: widthConstant))
}
if let height = height {
anchors.append(heightAnchor.constraint(equalTo: height.heightAnchor, multiplier: 1, constant: widthConstant))
} else if heightConstant > 0 {
anchors.append(widthAnchor.constraint(equalToConstant: heightConstant))
}
anchors.forEach({$0.isActive = true})
}
return anchors
}
And then call it like this:
_ = view.anchor(gamePreview, left: gamePreview, right: gamePreview, height: gamePreview, heightConstant: 131, isForLeading: true)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With