Visual constraints not being recognized - ios

I simply want to set constraints which allows the UIWebView to fill the screen. (Screen has a UINavigationbar and a UIToolBar), I've tried following the examples given in the apple documentation but always get a warning and my view doesnt show up. Below is what I've tried.
private func addSubviews(){
navigationController?.setToolbarHidden(false, animated: false)
// webView = UIWebView(frame: CGRect(x: 0, y: 0, width: UIScreen.mainScreen().bounds.width, height: UIScreen.mainScreen().bounds.height))
webView = UIWebView(frame: CGRectZero)
webView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(webView)
let views = ["myView" : webView]
let formatString = "|-[myView]-|"
let constraints = NSLayoutConstraint.constraintsWithVisualFormat(formatString, options:[] , metrics: nil, views: views)
NSLayoutConstraint.activateConstraints(constraints)
}

Oofh, visual constraints make my head hurt. I never was a fan of the visual language, never made any sense to me. This may be a bit more code but I think it's much easier to read as a developer and far more expressive as to intent.
self.view.addSubview(webView)
self.view.addConstraint(NSLayoutConstraint(item: self.view, attribute: .Left, relatedBy: .Equal, toItem: webView, attribute: .Left, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: self.view, attribute: .Top, relatedBy: .Equal, toItem: webView, attribute: .Top, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: self.view, attribute: .Bottom, relatedBy: .Equal, toItem: webView, attribute: .Bottom, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: self.view, attribute: .Right, relatedBy: .Equal, toItem: webView, attribute: .Right, multiplier: 1, constant: 0))
These constraints pin all edges of a view to the respective edge with a distance of 0.
Then at the end of your constraints, don't forget:
self.view.layoutIfNeeded()
My problem with the visual language is I can't look at it and immediately be clear as to what it does. Here, it's quite explicit. Just for fun, here's a nice function you can use whenever your constant and multiplier is 0:
/**
Adds an edge constraint between the superview and subview with a constant of 0.
- parameter attribute: Layout attribute.
- parameter subview: Subview.
*/
func addEdgeConstraintWithAttribute(attribute: NSLayoutAttribute, withSubview subview: UIView) {
self.addConstraint(NSLayoutConstraint(item: subview, attribute: attribute, relatedBy: .Equal, toItem: self, attribute: attribute, multiplier: 1, constant: 0))
}
Used like:
self.addEdgeConstraint(.Top, withSubview: webView)
where the function is an extension on UIView

Im my opinion, your view didn't show up because you didn't set up enough constraints for it.
Basically, with a view, you should add constraints to let it know at least the position and the size. In some cases, you dont need to setup size constraints because that view has intrinsic content size like UILabel, UIButton
Back to your example, you should add constraints like this:
private func addSubviews(){
navigationController?.setToolbarHidden(false, animated: false)
// webView = UIWebView(frame: CGRect(x: 0, y: 0, width: UIScreen.mainScreen().bounds.width, height: UIScreen.mainScreen().bounds.height))
let webView = UIWebView(frame: CGRectZero)
webView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(webView)
let views = ["myView" : webView]
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[myView]-|", options:[] , metrics: nil, views: views))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[myView]-|", options:[] , metrics: nil, views: views))
}
H in H:|-[myView]-| stands for horizontal.
V in V:|-[myView]-| stands for vertical.

Related

Swift autolayout : constraints in sub-view not working

I am trying to set up a view with two collection views and a view on the center , which in turn has elements inside it.
I have been trying to set constraints using visual format like this :
func setupViews() {
self.view.addSubview(playGroundView)
self.view.addSubview(firstCollectionView)
self.view.addSubview(secondCollectionView)
self.playGroundView.backgroundColor = UIColor.clear
aTextView.backgroundColor = UIColor.yellow
titleTextView.backgroundColor = UIColor.green
searchBar?.backgroundColor = UIColor.darkGray
if #available(iOS 9.0, *) {
playGroundView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor).isActive = true
firstCollectionView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor).isActive = true
secondCollectionView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor).isActive = true
} else {
// Fallback on earlier versions
}
self.playGroundView.addSubview(publishButton)
self.playGroundView.addSubview(searchBar!)
self.playGroundView.addSubview(aTextView)
self.playGroundView.addSubview(titleTextView)
publishButton.addConstraint(NSLayoutConstraint(item: publishButton, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 44))
aTextView.addConstraint(NSLayoutConstraint(item: aTextView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 44))
titleTextView.addConstraint(NSLayoutConstraint(item: titleTextView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 44))
searchBar!.addConstraint(NSLayoutConstraint(item: searchBar!, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 44))
self.playGroundView.addConstraintsWithFormat("V:[v0]-2-|", views: publishButton)
self.playGroundView.addConstraintsWithFormat("H:[v0]-2-|", views: publishButton)
self.playGroundView.addConstraintsWithFormat("V:|-2-[v0(44)]", views: searchBar!)
self.view.bringSubview(toFront: aTextView)
self.playGroundView.layoutIfNeeded()
self.playGroundView.layoutSubviews()
self.view.addConstraintsWithFormat("H:|-8-[v0(100)][v1][v2(100)]-8-|", views: secondCollectionView,playGroundView,firstCollectionView)
self.view.addConstraintsWithFormat("V:[v0]-2-|", views: playGroundView)
self.view.addConstraintsWithFormat("V:[v0]-2-|", views: secondCollectionView)
self.view.addConstraintsWithFormat("V:[v0]-2-|", views: firstCollectionView)
if(self.isCreate){
self.titleTextView.text = self.recipeDictionary?.value(forKey: "recipeName") as! String!
}
}
// Function to set constraints
func addConstraintsWithFormat(_ format: String, views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index,view) in views.enumerated() {
let key = "v\(index)"
viewsDictionary[key] = view
view.translatesAutoresizingMaskIntoConstraints = false
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
I don't see the elements inside the sub-view (playGroundView) being rendered. Can someone please suggest what am I doing wrong here ?
I would try the following to debug:
Temporarily set a solid background color to playGroundView instead of UIColor.clear and run it to make playGroundView is having a correct size and position.
Temporarily set borders to the missing subviews and run it to see if the views are even within the playGroundView bounds. Alternatively, you can also run the code, go to Xcode > Debug > View Debugging > Capture View Hierarchy
Set translatesAutoresizingMaskIntoConstraints = false for publishButton, searchBar, etc before adding to playGroundView. While it is not wrong to perform that in your helper function addConstraintsWithFormat, there is no need to make that call multiple times.

ViewController constraints in UIScrollView, using Storyboard

I have two UIViewControllers with Storyboard identifiers "vc0" and "vc1", which I am trying to instantiate objects of within a UIScrollView with paging enabled such that the two view controllers are adjacent to each other. The goal is to have the user swipe left and right to swipe between view controllers, similar to SnapChat.
When running my code, the first page of the scrollview contains the first view controller, while the second page contains nothing at all. I am assuming that this is because the first view controller is overlapping the second. How can I alter my constraints (or anything at all) to fix this issue, so that the right edge of vc0 meets with the left edge of vc1?
The UIScrollView is contained within a UIView, within a view controller. Here is the viewDidLoad() which contains all the relevant code. Please let me know of any additional helpful information I should provide.
override func viewDidLoad()
{
super.viewDidLoad()
let screenWidth = self.view.frame.width
let screenHeight = self.view.frame.height
let vc0 = self.storyboard?.instantiateViewControllerWithIdentifier("vc0")
self.addChildViewController(vc0!)
self.scrollView.addSubview(vc0!.view)
vc0!.didMoveToParentViewController(self)
let vc1 = self.storyboard?.instantiateViewControllerWithIdentifier("vc1")
var frame1 = vc1!.view.frame
frame1.origin.x = self.view.frame.size.width
vc1!.view.frame = frame1
self.addChildViewController(vc1!)
self.scrollView.addSubview(vc1!.view)
vc1!.didMoveToParentViewController(self)
self.scrollView.contentSize = CGSizeMake(self.view.frame.size.width * 2, self.view.frame.size.height);
//ADD CONSTRAINTS TO vc0
vc0!.view.translatesAutoresizingMaskIntoConstraints = false
let horizontalConstraint = NSLayoutConstraint(item: vc0!.view, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: scrollView, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: 0)
view.addConstraint(horizontalConstraint)
let widthConstraint = NSLayoutConstraint(item: vc0!.view, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: screenWidth)
view.addConstraint(widthConstraint)
let heightConstraint = NSLayoutConstraint(item: vc0!.view, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: screenHeight)
view.addConstraint(heightConstraint)
//ADD CONSTRAINTS TO vc1
vc1!.view.translatesAutoresizingMaskIntoConstraints = false
let horizontalConstraint2 = NSLayoutConstraint(item: vc1!.view, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: vc0!.view, attribute: NSLayoutAttribute.Right, multiplier: 1, constant: 0)
view.addConstraint(horizontalConstraint2)
let widthConstraint2 = NSLayoutConstraint(item: vc1!.view, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: screenWidth)
view.addConstraint(widthConstraint)
let heightConstraint2 = NSLayoutConstraint(item: vc1!.view, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: screenHeight)
view.addConstraint(heightConstraint)
}
I think there is a problem in constraints!!
You should add one view to scrollview and then add views of both VC to that view not direct to scrollview.
view's size should be same as scrollview's contentsize
Now your constrains should be like :
scrollview : top,bottom,leading,trailing
View in scrollview : top,bottom,leading,trailing, Vertical center in container(center Y) and fixed width (because you want horizontal scrolling), If want vertical scrolling then constraint should be (horizontal center in container (center X) and fixed height).
And you views from both VCs : top,leading,fixed width, fixed height (width and height should be same as screen size)
Hope this will help :)

How to add a static bottom view in swift

I want to add a staticUIView in the bottom of my UIViewController. This is a story board app written in swift. in my viewDidLoad() I did something like this.
self.vWindow = UIApplication.sharedApplication().keyWindow
let mainBound = CGRectMake(0, self.view.frame.size.height-135, self.view.frame.size.width, 135)//CGSizeMake(UIScreen.mainScreen().bounds.size.width, 135.0)
self.vwVideo = UIView.init(frame: mainBound)
self.vwVideo.backgroundColor = UIColor.blackColor()
self.vWindow.addSubview(self.vwVideo)
But this adds nothing to my viewController. Why is that? Please help me.
Thanks
let vWindow = UIApplication.sharedApplication().keyWindow
let mainBound = CGRectMake(0, self.view.frame.size.height-135, self.view.frame.size.width, 135)//CGSizeMake(UIScreen.mainScreen().bounds.size.width, 135.0)
self.vwVideo = UIView.init(frame: mainBound)
self.vwVideo.backgroundColor = UIColor.blackColor()
vWindow!.addSubview(self.vwVideo)
Use this code it will add your view in window at the bottom side.
You shouldn't be attaching views directly to the window, add it to the ViewController.
try something like this:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// without autolayout
let header = UIView(frame: CGRectMake(0,0, view.bounds.size.width, 50))
header.backgroundColor = UIColor.redColor()
view.addSubview(header)
// with auto layout
let footer = UIView()
footer.backgroundColor = UIColor.blackColor()
footer.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(footer)
NSLayoutConstraint(item: footer, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 50).active = true
NSLayoutConstraint(item: footer, attribute: .Leading, relatedBy: .Equal, toItem: view, attribute: .Leading, multiplier: 1.0, constant: 0).active = true
NSLayoutConstraint(item: footer, attribute: .Trailing, relatedBy: .Equal, toItem: view, attribute: .Trailing, multiplier: 1.0, constant: 0).active = true
NSLayoutConstraint(item: footer, attribute: .Bottom, relatedBy: .Equal, toItem: view, attribute: .Bottom, multiplier: 1.0, constant: 0).active = true
}
Here you have two ways of attaching the view, One with autolayout and one with a fixed size and position.
If you want to add complex views, you may be better creating a nib.. .a view in storyboard which you can load when required.
If you need this view to appear in many places you could look at instead attaching it to a parent view, maybe something like a navigation controller or paging view controller, but you would need to manage when it should and shouldn't be displayed.
you are adding your created view to window i think. You need to that view to your current content view something like,
self.view.addSubView(self.vwVideo)
And you not need to take window and bounds of it, you can simply achieve it something like,
let bottomView: UIView = UIView(frame: CGRectMake(0, self.view.frame.size.height - 135, self.view.frame.size.width, 135))
self.view!.addSubview(bottomView)
Hope this will help :)
Its better to do it using AutoLayout.
Add your view to superview
view.addSubview(footerView)
then disable auto conversion of masks to constraints using
footerView.translatesAutoresizingMaskIntoConstraints = false
then add the constraint using following lines of code
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[view]-0-|", options: [], metrics: nil, views: ["view":footerView]))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[view(135)]-0-|", options: [], metrics: nil, views: ["view":footerView]))

Set NSLayoutConstraint of view to top right

I'm at wits end trying to figure this out. In CSS, you can do something like this:
img {
position: absolute;
width: 50px;
height: 50px;
top: 0;
right: 0;
}
In Swift, I am using the programatic interface to set layout constraints of an image. It looks like this:
let img = UIImageView(image: UIImage(named: "my-image"))
img.setTranslatesAutoresizingMaskIntoConstraints(false)
mainScrollView.addSubview(img)
mainScrollView.addConstraint(NSLayoutConstraint(item: img, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 50))
mainScrollView.addConstraint(NSLayoutConstraint(item: img, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 50))
mainScrollView.addConstraint(NSLayoutConstraint(item: img, attribute: .Top, relatedBy: .Equal, toItem: mainScrollView, attribute: .Top, multiplier: 1, constant: 0))
mainScrollView.addConstraint(NSLayoutConstraint(item: img, attribute: .Trailing, relatedBy: .Equal, toItem: mainScrollView, attribute: .Trailing, multiplier: 1, constant: 0))
However for some bizarre reason it respects the width and height layout constraints, even the top one. but it's basically off screen to the left by 50. If I set the last .Trailing constant to 50, it actually moves it into view at the top left (yes I'm very confused by this).
The constraints should be applied to the view which is the nearest common ancestors of the view involved in the constraint. So I can see two errors in the code you posted:
if you are setting the height or the width of a view, the view itself is the nearest common ancestor view so the constraint should be applied directly to it. So your code should be:
item.addConstraint(NSLayoutConstraint(item: img, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 50))
item.addConstraint(NSLayoutConstraint(item: img, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 50))
Since the views involved in this constraint:
NSLayoutConstraint(item: img, attribute: .Top, relatedBy: .Equal, toItem: mainView, attribute: .Top, multiplier: 1, constant: 0))
are one the subview of the other, it should be applied to the parent view:
mainView.addConstraint(NSLayoutConstraint(item: img, attribute: .Top, relatedBy: .Equal, toItem: mainView, attribute: .Top, multiplier: 1, constant: 0)))
Regarding the problem with the Trailing attribute you should considering that when you are adding a constraint you are defining this formula:
item1.attribute = item2.attribute + constant
So if you are defining a constraint where both the attributes are Trailing you should to use a negative constant to have one view inside the other:
--------------------------
View1 |
------------- |
View2 | - (-50) - |
Have you tried doing it the way that doesn't involve code? Using the buttons on the bottom right of the story board view?
You just want to set it to top right with width and height both 50? Maybe you can do it like this:
let img = UIImageView(image: UIImage(named: "my-image"))
img.setTranslatesAutoresizingMaskIntoConstraints(false)
mainScrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[img(50)]-0-|", options: NSLayoutFormatOptions(0), metrics: nil, views: ["img" : img]))
mainScrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[img(50)]", options: NSLayoutFormatOptions(0), metrics: nil, views: ["img" : img]))
The issue is that unlike standard views, constraints between a scroll view and its subviews don't dictate the size of the subviews, but rather the contentSize of the scroll view. This may seem curious, but it's actually a wonderful feature, getting us out of the business of calculating contentSize manually. For more information, see Technical Note TN2154.
But this means that in order to dictate the size of a subview within a scroll view, you actually have to reference something outside of the scroll view (e.g. the view controller's main view).
So you can place a "container" view inside the scroll view and put the image view in the upper right corner of that. And then you can define the container to be the width of the main view. That way you're accomplishing both goals, dictating the width of the contents of the the scroll view, and dictating the placement of the image view within that:
// define the container inside the scroll view
let containerView = UIView()
containerView.setTranslatesAutoresizingMaskIntoConstraints(false)
scrollView.addSubview(containerView)
// put the imageView in the container
let imageView = UIImageView(image: UIImage(named: "my-image"))
imageView.setTranslatesAutoresizingMaskIntoConstraints(false)
containerView.addSubview(imageView)
let views = ["containerView" : containerView, "view" : view, "imageView" : imageView]
// constraints for the container
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[containerView(==view)]|", options: nil, metrics: nil, views: views))
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[containerView]|", options: nil, metrics: nil, views: views))
// define the imageview constraints
containerView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[imageView(==50)]|", options: nil, metrics: nil, views: views))
containerView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[imageView(==50)]", options: nil, metrics: nil, views: views))

Swift Constraints Don't Take After Rotate

I am creating a UIButton programmatically in the code and am constraining it to the top and the right of the ViewController. When the ViewController loads, the UIButton is in the correct position on the screen. The problem comes when I rotate the device, the constraints don't seem to have taken. Here is the code I use to create the UIButton:
let xValue = UIScreen.mainScreen().bounds.width - 44
let closeButton = UIButton(frame: CGRect(origin: CGPoint(x: xValue, y: 0), size: CGSize(width: 44, height: 44)))
closeButton.setImage(UIImage(named: "close"), forState: .Normal)
closeButton.addTarget(self, action: "closeClicked", forControlEvents:.TouchUpInside)
self.view.addSubview(closeButton)
self.view.addConstraint(NSLayoutConstraint(item: closeButton, attribute: .Top, relatedBy: .Equal, toItem: self.view, attribute: .Top, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: closeButton, attribute: .Right, relatedBy: .Equal, toItem: self.view, attribute: .Right, multiplier: 1, constant: 0))
Is there something else I need to do to ensure the constraints are applied after an orientation change?
You shouldn't combine frames with constraints. By default, iOS will convert your frame to constraints, and that is causing a conflict with the constraints you are setting. After creating your button, you need to call:
For Swift 1.2:
closeButton.setTranslatesAutoresizingMaskIntoConstraints(false)
For Swift 2.0:
closeButton.translatesAutoresizingMaskIntoConstraints = false
so that iOS doesn't try to convert your frame into constraints. Then you need to add two additional constraints for the width and height of your button:
closeButton.addConstraint(NSLayoutConstraint(item: closeButton, attribute: .Width,
relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 44))
closeButton.addConstraint(NSLayoutConstraint(item: closeButton, attribute: .Height,
relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 44))
Since your button is no longer using the frame, you can create it with:
let closeButton = UIButton()
Now your button will stick to the upper right of your screen in all orientations for all devices.
All You need is to disable animation (uncheck "Animation" checkbox) on segue and then will be no need to update constraints.
Can be found in Segue options in right panel while editing storyboard.

Resources