I'm trying to get my head around how adding constraints programmatically works. So far I have my code like so:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//addViewStandard()
addConstraintsView()
}
func addConstraintsView() {
let someView = UIView(frame: CGRect.zero)
someView.backgroundColor = UIColor.blue
// I want to mimic a frame set of CGRect(x: 20, y: 50, width: 50, height: 50)
let widthConstraint = NSLayoutConstraint(item: someView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 50)
let heightConstraint = NSLayoutConstraint(item: someView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 50)
let leadingConstraint = NSLayoutConstraint(item: someView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 20)
someView.translatesAutoresizingMaskIntoConstraints = false
someView.addConstraints([widthConstraint, heightConstraint, leadingConstraint])
view.addSubview(someView)
}
}
Now when I run the app it crashes because of the leading constraint. The error message is "Impossible to set up layout with view hierarchy unprepared for constraint". What am I doing wrong here? Should I be adding the constraints to the object (the blue box on this case) or adding them to its superview?
EDIT:
After code changes I have:
func addConstraintsView() {
let someView = UIView(frame: CGRect.zero)
someView.backgroundColor = UIColor.blue
view.addSubview(someView)
someView.translatesAutoresizingMaskIntoConstraints = false
let widthConstraint = NSLayoutConstraint(item: someView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 50)
let heightConstraint = NSLayoutConstraint(item: someView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 50)
let leadingConstraint = NSLayoutConstraint(item: someView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1.0, constant: 20)
someView.addConstraints([widthConstraint, heightConstraint])
view.addConstraints([leadingConstraint])
}
First of all,
view.addSubview(someView)
someView.translatesAutoresizingMaskIntoConstraints = false
should come before the constraints phase; you have to apply the constraints AFTER someView is added to its superview.
Also, if you are targeting iOS 9, I'd advise you to use layout anchors like
let widthConstraint = someView.widthAnchor.constraint(equalToConstant: 50.0)
let heightConstraint = someView.heightAnchor.constraint(equalToConstant: 50.0)
let leadingConstraint = someView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0)
NSLayoutConstraint.activate([widthConstraint, heightConstraint, leadingConstraint])
This way you don't have to worry about which view to apply the constraints to.
Finally (and to clear up your doubt), if you can't use layout anchors, you should add the leading constraint to the superview, not the view.
Related
I am fairly new to iOS development and I'm using a WKWebView to render my website. I am trying to add some constraints but want to ignore the right "safe area inset" in landscape view as marked in the screenshot.
Here is the screenshot.
Here is the code.
let webConfiguration = WKWebViewConfiguration()
let customFrame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: 0.0, height: self.webViewContainer.frame.size.height))
self.webView = WKWebView (frame: customFrame , configuration: webConfiguration)
webView.translatesAutoresizingMaskIntoConstraints = false
self.webViewContainer.addSubview(webView)
addConstraints(to: webView, with: webViewContainer)
func addConstraints(to webView: UIView, with superView: UIView) {
webView.translatesAutoresizingMaskIntoConstraints = false
let leadingConstraint = NSLayoutConstraint(item: webView, attribute: .leading, relatedBy: .equal, toItem: superView, attribute: .leading, multiplier: 1, constant: 0)
let trailingConstraint = NSLayoutConstraint(item: webView, attribute: .trailing, relatedBy: .equal, toItem: superView, attribute: .trailing, multiplier: 1, constant: 0)
let topConstraint = NSLayoutConstraint(item: webView, attribute: .top, relatedBy: .equal, toItem: superView, attribute: .top, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: webView, attribute: .bottom, relatedBy: .equal, toItem: superView, attribute: .bottom, multiplier: 1, constant: 0)
superView.addConstraints([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint])
}
Any help will be appreciated.
Use size classes and change the constraint like below in the image
I'm creating my own class with inherits from UIView, class MyView: UIView {
The idea is to create a view and put some label into it. I'm achieving this by:
init() {
super.init(frame: CGRect(x: 8.0, y: 104.0, width: UIScreen.main.bounds.width - 16.0, height: 60.0))
initialize()
}
where
fileprivate func initialize() {
label = UILabel()
label.numberOfLines = 0
label = UILabel(frame: CGRect(x: 16.0, y: 8.0, width: self.frame.width - 32.0, height: self.frame.height - 16.0))
self.addSubview(label)
}
and everything works fine for now.
But now I want to make this custom view height dynamic, so the height of my view will depend on the height(text) of the label in it.
I've tried to add:
let constraints = [
NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 8.0),
NSLayoutConstraint(item: label, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1.0, constant: 8.0),
NSLayoutConstraint(item: label, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1.0, constant: 8.0),
NSLayoutConstraint(item: label, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 8.0)]
label.addConstraints(constraints)
and
self.translatesAutoresizingMaskIntoConstraints = false
self.heightAnchor.constraint(greaterThanOrEqualToConstant: 60.0).isActive = true
and eventually I get a huge error in logs with:
The view hierarchy is not prepared for the constraint
When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled.
Can someone help me with this problem?
I've googled it but could not find anything which would help me to solve my problem.
Maybe I do my layouts wrong.
I will appreciate your help!
the problem as of
label.addConstraints(constraints)
you add constraints between parent and the label to the label which is incorrect you need to add them to parent
self.addConstraints(constraints)
Another apple's recommended way is to
NSLayoutConstraint.activate([constraints])
Which will handle it for you
let mmmview = MyView()
mmmview.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mmmview)
let constraints = [
NSLayoutConstraint(item: mmmview , attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: 8.0),
NSLayoutConstraint(item: mmmview , attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 8.0),
NSLayoutConstraint(item: mmmview , attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1.0, constant: 8.0)
]
NSLayoutConstraint.activate(constraints)
I'm creating new UIViewController dynamycally using this code
#IBAction func newVCBtnPressed(_ sender: Any) {
let controller = DynamicVC()
show(controller, sender: sender)
}
In the new UIViewController I'm using this code for creation of the new UIView:
override func loadView() {
view = UIView()
view.backgroundColor = .lightGray
}
In result I have view with .lightGray backgroundcolor.
I want to add custom UIView and setup the constraints programmatically, and in result i want UIView with following constraints:
top: 0
bottom:(view.frame.height*0.9)
leading:0
trailing:(view.frame.width*0.15)
width:(view.frame.width*0.85)
height:(view.frame.height*0.1)
Example:
Here is my code:
topMenuView = UIView()
topMenuView.backgroundColor = .red
view.addSubview(topMenuView)
topMenuView.translatesAutoresizingMaskIntoConstraints = false
setupConstraints(item: topMenuView, topC: 0, topToItem: view, bottomC: (view.frame.height*0.9), bottomToItem: view, widthC: (view.frame.width*0.85), heightC: (view.frame.height*0.1), leadingCon: 0, trailingCon: (view.frame.width*0.15))
I'm using this constructed function for constraints:
func setupConstraints(item:UIView, topC:CGFloat, topToItem:UIView, bottomC:CGFloat, bottomToItem:UIView, widthC:CGFloat, heightC:CGFloat, leadingCon:CGFloat, trailingCon:CGFloat) {
let topConstraint = NSLayoutConstraint(item: item, attribute: .top, relatedBy: .equal, toItem: topToItem, attribute: .bottom, multiplier: 1, constant: topC)
let bottomConstraint = NSLayoutConstraint(item: item, attribute: .bottom, relatedBy: .equal, toItem: bottomToItem, attribute: .top, multiplier: 1, constant: bottomC)
let widthConstraint = NSLayoutConstraint(item: item, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: widthC)
let heightConstraint = NSLayoutConstraint(item: item, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: heightC)
let leading = NSLayoutConstraint(item: item,attribute: .leading,relatedBy: .equal, toItem: view, attribute: .leadingMargin, multiplier: 1.0, constant: leadingCon)
let trailing = NSLayoutConstraint(item: item,attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailingMargin,multiplier: 1.0,constant: trailingCon)
view?.addConstraints([topConstraint, bottomConstraint, widthConstraint, heightConstraint, leading, trailing])
NSLayoutConstraint.activate([topConstraint, bottomConstraint, widthConstraint, heightConstraint, leading, trailing])
}
But in the result i receive only UIView with gray background, the new UIView with red background doesn't appears.
What I'm doing wrong???
You should only specify bottom OR height and width OR trailing, otherwise you are going to get conflicts here.
see playground:
import PlaygroundSupport
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let red = UIView()
red.backgroundColor = .red
view.addSubview(red)
red.translatesAutoresizingMaskIntoConstraints = false
red.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
red.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
red.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.85).isActive = true
red.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.1).isActive = true
}
}
PlaygroundPage.current.liveView = ViewController()
I am trying to put a UIActivityIndicatorView inside each collection view cell as it downloads its image. I have it appearing in each cell, but it refuses to center itself. It stays in the top left corner. How can I get it to center itself properly?
Here's how I'm doing it:
extension UIView {
func showActivityIndicator(onView: UIView, withIndicator: UIActivityIndicatorView) {
withIndicator.frame = CGRect(x: onView.frame.midX - 20, y: onView.frame.midY - 20, width: 40, height: 40)
withIndicator.activityIndicatorViewStyle = .whiteLarge
withIndicator.center = onView.center
onView.addSubview(withIndicator)
withIndicator.startAnimating()
}
}
I call that function inside cellForItemAtIndexPath like:
showActivityIndicator(onView: cell.contentView, withIndicator: activityInd)
But nothing I do will move it from the top left corner. Any advice?
Try this
withIndicator.center = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
You need to add contraints to center it. For example use NSLayoutAnchor.
You need to set translatesAutoresizingMaskIntoConstraints to false, to set your constraints. And after adding it to the view set the constraints (hints in the code comments):
extension UIView {
func showActivityIndicator(onView: UIView, withIndicator: UIActivityIndicatorView) {
withIndicator.frame = CGRect(x: onView.frame.midX - 20, y: onView.frame.midY - 20, width: 40, height: 40)
withIndicator.activityIndicatorViewStyle = .whiteLarge
// set translatesAutoresizingMaskIntoConstraints to false to set your constraints
withIndicator.translatesAutoresizingMaskIntoConstraints = false
onView.addSubview(withIndicator)
withIndicator.startAnimating()
// add the constraints to center the indicator
withIndicator.centerXAnchor.constraint(equalTo: onView.centerXAnchor).isActive = true
withIndicator.centerYAnchor.constraint(equalTo: onView.centerYAnchor).isActive = true
}
}
I suggest using constraints (aka auto layout):
indicator.translatesAutoresizingMaskIntoConstraints = false
"your cell".addSubview(indicator)
let widthConstraint = NSLayoutConstraint(item: indicator, attribute: .Width, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 250)
let heightConstraint = NSLayoutConstraint(item: indicator, attribute: .Height, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 100)
let xConstraint = NSLayoutConstraint(item: indicator, attribute: .CenterX, relatedBy: .Equal, toItem: self.tableView, attribute: .CenterX, multiplier: 1, constant: 0)
let yConstraint = NSLayoutConstraint(item: indicator, attribute: .CenterY, relatedBy: .Equal, toItem: self.tableView, attribute: .CenterY, multiplier: 1, constant: 0)
NSLayoutConstraint.activateConstraints([widthConstraint, heightConstraint, xConstraint, yConstraint])
How to give programmatically constraints equal width and equal height with multiple views.I check google but not perfect answer for programmatically equal width and height constraints through auto layout.
my code look like below:
var countNoOfViews:Int = 3
#IBOutlet var viewForRow1: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.specialButtonViewLeft()
}
func specialButtonViewLeft(){
for i in 0..<countNoOfViews{
var customView:UIView!
customView = UIView(frame: CGRect.zero)
customView.translatesAutoresizingMaskIntoConstraints = false
viewForRow1.addSubview(customView)
let widthConstraint = NSLayoutConstraint(item: customView, attribute: .width, relatedBy: .equal,toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 20.0)
let topConstraint = NSLayoutConstraint(item: customView, attribute: .top, relatedBy: .equal,toItem: self.viewForRow1, attribute: .top, multiplier: 1.0, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: customView, attribute: .bottom, relatedBy: .equal,toItem: self.viewForRow1, attribute: .bottom, multiplier: 1.0, constant: 0)
let leadingConstraint = NSLayoutConstraint(item: customView, attribute: .leading, relatedBy: .equal, toItem: self.viewForRow1, attribute: .leading, multiplier: 1, constant: customLeadingSpaceLeft)
customLeadingSpaceLeft = customLeadingSpaceLeft + customViewWidth
arrayLeftBtnConstraints.append(widthConstraint)
if i == 0{
customView.backgroundColor = UIColor.red
}else if i == 1{
customView.backgroundColor = UIColor.green
}else if i == 2{
leftViewVal = customLeadingSpaceLeft
customView.backgroundColor = UIColor.black
}
customView.alpha = 0.50
viewForRow1.addConstraints([widthConstraint, leadingConstraint,topConstraint,bottomConstraint])
}
}
I want to add equal width constraint programmatically.
You have three possible ways to do that:
1) By using anchors:
view.widthAnchor.constraint(equalTo: otherView.widthAnchor, multiplier: 1.0).isActive = true
2) Visual format:
simple example - "H:|[otherView]-[view(==otherView)]|"
3) "Old school" constraints:
NSLayoutConstraint(item: #view, attribute: .width, relatedBy: .equal, toItem: #otherView, attribute: .width, multiplier: 1.0, constant: 0.0).isActive = true
hope it will help somebody.
Try this:
let widthConstraint = NSLayoutConstraint(item: customView, attribute: .width, relatedBy: .equal, toItem: viewForRow1, attribute: .width, multiplier: 0.25, constant: 0.0)
multiplier: 0.25, denotes that customView's width will be 1/4th of the parent view, viewForRow1.