add constraints programmatically swift - ios

I am trying to add constraints to a facebook sdk login button.
I have the button inside a scroll view and I am trying to add a top constraint to a label that is also in the scroll view. I am able to successfully add the height constraint with no run time errors but the actual constraint does not seem to be applied to the button.
#IBOutlet weak var orLbl: UILabel!
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
var loginFBButton = FBSDKLoginButton()
loginFBButton.readPermissions = ["public_profile", "email"]
let heightConstraint = NSLayoutConstraint(
item: loginFBButton,
attribute: NSLayoutAttribute.Height,
relatedBy: NSLayoutRelation.Equal,
toItem: nil,
attribute: NSLayoutAttribute.NotAnAttribute,
multiplier: 1,
constant: 41)
let topConstraint = NSLayoutConstraint(
item: loginFBButton,
attribute: NSLayoutAttribute.TopMargin,
relatedBy: NSLayoutRelation.Equal,
toItem: self.orLbl,
attribute: NSLayoutAttribute.BottomMargin,
multiplier: 1,
constant: 31)
let leadingConstraint = NSLayoutConstraint(
item: loginFBButton,
attribute: NSLayoutAttribute.Leading,
relatedBy: NSLayoutRelation.Equal,
toItem: self.registerButton,
attribute: NSLayoutAttribute.Left,
multiplier: 1,
constant: 0)
let trailingConstraint = NSLayoutConstraint(
item: loginFBButton,
attribute: NSLayoutAttribute.Leading,
relatedBy: NSLayoutRelation.Equal,
toItem: self.registerButton,
attribute: NSLayoutAttribute.Right,
multiplier: 1,
constant: 0)
self.scrollView.addSubview(loginFBButton)
//loginFBButton.center = self.scrollView.center
loginFBButton.addConstraints([heightConstraint, topConstraint])
}
Then when I include the addition of the top constraint, I am getting a runtime error:
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.
Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to install constraint on view. Does the constraint reference something from outside the subtree of the view? That's illegal.
Both the label and the facebook button are in my scroll view? I've even printed out orLbl.superview and loginFBButton.superview and I am getting optional uiscrollview for both

There is a new (iOS8, OS 10.10), easier way to activate constraints that doesn't involve figuring out which views to add them to. The constraints already know which views they belong to, so first make sure your views have been added as subViews, then call the class method activateConstraints on NSLayoutConstraint to activate them:
NSLayoutConstraint.activateConstraints([heightConstraint, topConstraint])
When you create UI elements programmatically, you need to tell iOS not to turn its frame into constraints. To do that, just after creating loginFBButton do:
For Swift 1.2:
loginFBButton.setTranslatesAutoresizingMaskIntoConstraints(false)
For Swift 2.0 & 3.0:
loginFBButton.translatesAutoresizingMaskIntoConstraints = false
Finally, you are going to need more constraints. I suggest setting a width constraint for your loginFBButton and adding a constraint to position the button horizontally.

Related

Constraints Overlapping, Swift Xcode

I'm creating a custom keyboard in Xcode with swift. Everything runs great but I am running into a problem with constraints. Ill explain what I've done and what I am looking to do.
what I have done:1)I have created a 'world' button that will switch between the iOS default keyboard and the custom keyboard. It is constrained to the bottom left of the view, no matter what device it is loaded onto (iPhone 5,6,7 iPad etc). 2)I have then created a collection view that is constrained to start at the edge of the world button no matter the device. 3)I have created a delete button that is constrained to the bottom right of the view, no matter the device.
what I want to do: 1)I want the collection view to start at the world button and end at the delete button, no matter the device.
The trouble I am having is that the delete button overlaps the collection view on smaller devices. I want the collection view to stop at the delete button but cannot figure out why my constraints are not working.
These are the relevant constraints for the collection view.
// create the constraints
// leading constraint
let categoriesCollectionViewLeadingConstraint = NSLayoutConstraint(item: categoriesCollectionView, attribute: NSLayoutAttribute.leading, relatedBy: NSLayoutRelation.equal, toItem: backButton, attribute: NSLayoutAttribute.trailing, multiplier: 1, constant: 0)
// add the leading constraint
view.addConstraint(categoriesCollectionViewLeadingConstraint)
// bottom constraint
let categoriesCollectionViewBottomConstraint = NSLayoutConstraint(item: categoriesCollectionView, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: 0)
// add the bottom constraint
view.addConstraint(categoriesCollectionViewBottomConstraint)
// trailing constraint
let categoriesCollectionViewTrailingConstraint = NSLayoutConstraint(item: categoriesCollectionView, attribute: NSLayoutAttribute.trailing, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.trailing, multiplier: 1, constant: 0)
// set the priority to less than 1000 so it works correctly
categoriesCollectionViewTrailingConstraint.priority = 999
// add the trailing constraint
view.addConstraint(categoriesCollectionViewTrailingConstraint)
I think you should constraint your collection view like this:
// trailing constraint
let categoriesCollectionViewTrailingConstraint = NSLayoutConstraint(item: categoriesCollectionView, attribute: NSLayoutAttribute.trailing, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.trailing, multiplier: 1, constant: - deleteButtonWidth)

Create constraints with code - getting error "Unable to simultaneously satisfy constraints" [duplicate]

This question already has answers here:
Prevent Interface Builder from auto creating Constraints?
(2 answers)
Closed 6 years ago.
I added a UIView in the storyboard (called swipedView) and I didn't set any auto-layout constraints to it. Nothing. Now in the code, I want to add some constraints. To test it out I want to pin it to the sides of the screen (so it's fullscreen).
When I run it, I get a long error message, something like:
2016-07-18 22:59:07.990 FindTheWay[87145:18423835] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x7ff02d11a6e0 V:|-(0)-[UIView:0x7ff02b479a40] (Names: '|':UIView:0x7ff02b450100 )>",
"<NSIBPrototypingLayoutConstraint:0x7ff02b416a50 'IB auto generated at build time for view with fixed frame' V:|-(20)-[UIView:0x7ff02b479a40] (Names: '|':UIView:0x7ff02b450100 )>"
)
Will attempt to recover by breaking constraint
<NSIBPrototypingLayoutConstraint:0x7ff02b416a50 'IB auto generated at build time for view with fixed frame' V:|-(20)-[UIView:0x7ff02b479a40] (Names: '|':UIView:0x7ff02b450100 )>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
That's my ViewController
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var swipedView: UIView!
#IBOutlet weak var foregroundView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// that was a proposed fix to my problem I found in the internet. Didn't work
view.translatesAutoresizingMaskIntoConstraints = false
}
override func viewDidAppear(animated: Bool) {
let pinTop = NSLayoutConstraint(item: swipedView, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1.0, constant: 0)
let pinLeft = NSLayoutConstraint(item: swipedView, attribute: .Leading, relatedBy: .Equal, toItem: view, attribute: .Leading, multiplier: 1.0, constant: 0)
let pinRight = NSLayoutConstraint(item: swipedView, attribute: .Trailing, relatedBy: .Equal, toItem: view, attribute: .Trailing, multiplier: 1.0, constant: 0)
let pinBottom = NSLayoutConstraint(item: swipedView, attribute: .Bottom, relatedBy: .Equal, toItem: view, attribute: .Bottom, multiplier: 1.0, constant: 0)
NSLayoutConstraint.activateConstraints([pinTop, pinLeft, pinRight, pinBottom])
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I'm totally new to setting up constraints via code so I'm not sure how to deal with this. I checked several similar questions I found on stackOverflow but nothing helped me here, unfortunately.
Set swipedView's translatesAutoresizingMaskIntoConstraints to false, not view's.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var swipedView: UIView!
#IBOutlet weak var foregroundView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// that was a proposed fix to my problem I found in the internet. Didn't work
swipedView.translatesAutoresizingMaskIntoConstraints = false
}
override func viewDidAppear(animated: Bool) {
let pinTop = NSLayoutConstraint(item: swipedView, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1.0, constant: 0)
let pinLeft = NSLayoutConstraint(item: swipedView, attribute: .Leading, relatedBy: .Equal, toItem: view, attribute: .Leading, multiplier: 1.0, constant: 0)
let pinRight = NSLayoutConstraint(item: swipedView, attribute: .Trailing, relatedBy: .Equal, toItem: view, attribute: .Trailing, multiplier: 1.0, constant: 0)
let pinBottom = NSLayoutConstraint(item: swipedView, attribute: .Bottom, relatedBy: .Equal, toItem: view, attribute: .Bottom, multiplier: 1.0, constant: 0)
NSLayoutConstraint.activateConstraints([pinTop, pinLeft, pinRight, pinBottom])
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The reason this solution ends up working can be found in Apple's Documentation.
Note that the autoresizing mask constraints fully specify the view’s size and position; therefore, you cannot add additional constraints to modify this size or position without introducing conflicts.
When setting translatesAutoresizingMaskIntoConstraints to true, we're saying that we want the related view's size and position to be turned into actual constraints.
By setting translatesAutoresizingMaskIntoConstraints to false, we are saying that the related view's size and position should not be turned into constraints for whatever reason (in this case, we're creating our own).
Since we're not looking to apply constraints that modify both the size and position of view, but actually swipedView, we need to set swipedView's translatesAutoresizingMaskIntoConstraints equal to false.
I've had the same problem when using interface builder and the solution was to identify the constraint that is causing the warnings (possibly by commenting them out one at a time in this case) and then setting the priority of that constraint to 999 rather than 1000 (which I think is the default). Programmatically, you do this with:
constraint.priority = 999
(Replacing 'constraint' with whatever the affected constraint is.)
This has worked for me a few times and doesn't affect the layout, but clears the warnings. I think 999 is the equivalent of saying "get as close as you can, but don't worry about it" so you don't get the warnings if the constraint fails to be satisfied by a tiny amount. Hope that helps.
Use the technique mentioned in this article:
http://travisjeffery.com/b/2013/11/preventing-ib-auto-generated-at-build-time-for-view-with-fixed-frame-when-using-auto-layout/
The reason why it didn't work for me was the mistake I made with
view.translatesAutoresizingMaskIntoConstraints = false
It has to be
swipedView.translatesAutoresizingMaskIntoConstraints = false
Now it works. I don't know why (maybe some can explain?)
Thanks #ZGski and #dan

Can't Add Constraints to Button Programatically - Swift 2.0

I have a subView named loginView and within that a few other elements, namely loginButton and loginUsername
I am trying to programatically add a Facebook login button that has constraints relative to loginButton, loginView and loginUsername
Here is my code
self.loginView.addSubview(facebookLoginButton)
let facebookLoginButtonTopConstraint = NSLayoutConstraint(
item: facebookLoginButton,
attribute: NSLayoutAttribute.Top,
relatedBy: NSLayoutRelation.Equal,
toItem: loginButton,
attribute: NSLayoutAttribute.Bottom,
multiplier: 1,
constant: 8
)
let facebookLoginButtonLeadingConstraint = NSLayoutConstraint(
item: facebookLoginButton,
attribute: NSLayoutAttribute.Leading,
relatedBy: NSLayoutRelation.Equal,
toItem: loginView,
attribute: NSLayoutAttribute.Leading,
multiplier: 1,
constant: 8
)
let facebookLoginButtonWidthConstraint = NSLayoutConstraint(
item: facebookLoginButton,
attribute: NSLayoutAttribute.Width,
relatedBy: NSLayoutRelation.Equal,
toItem: loginUsername,
attribute: NSLayoutAttribute.Width,
multiplier: 1,
constant: 0
)
self.loginView.addConstraints([
facebookLoginButtonTopConstraint,
facebookLoginButtonLeadingConstraint
])
self.facebookLoginButton.addConstraint(facebookLoginButtonWidthConstraint)
This code is going in my viewDidLoad method. The error I'm getting is:
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. Break on -[UIView(UIConstraintBasedLayout) _viewHierarchyUnpreparedForConstraint:] to debug.
Any help greatly appreciated, thanks!
The error says all. You need to prepare all views to hierarchy before adding constraints. Check that all four views are added to view hierarchy before adding constraints (didn't you forget addSubvew for some view?). Do not move this code to viewWillLayoutSubviews method, this is wrong hint! Because you don't want to add constraints multiple times. Also, you should add facebookLoginButtonWidthConstraint to view that owns both (loginView?), facebookLoginButton and loginUsername, since this is shared constraint

UIScrollView dynamic height content (autolayout) not filling whole space

I am creating a small chat app in which I use a custom subclass of UIScrollView to present the messages. This app is just for practicing so I don't want to use a third party library. I am implementing this UIScrollView via autolayout, after reading the technical note 2154 by Apple and several tutorials explaining this, and my implementation is almost working but the content view of my UIScrollView doesn't seem to fill all the space available.
The code which presents the ScrollView is:
public class ChatView: UIScrollView {
private var contentView: UIView
...
// This get called by all the init methods. contentView is already created ( contentView = UIView() )
private func setupViews() {
self.translatesAutoresizingMaskIntoConstraints = false
contentView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(contentView)
let constraint1 = NSLayoutConstraint(item: self, attribute: .Top, relatedBy: .Equal, toItem: contentView, attribute: .Top, multiplier: 1.0, constant: 0.0)
let constraint2 = NSLayoutConstraint(item: self, attribute: .Bottom, relatedBy: .Equal, toItem: contentView, attribute: .Bottom, multiplier: 1.0, constant: 0.0)
let constraint3 = NSLayoutConstraint(item: self, attribute: .Width, relatedBy: .Equal, toItem: contentView, attribute: .Width, multiplier: 1.0, constant: 0.0)
let constraint4 = NSLayoutConstraint(item: self, attribute: .CenterX, relatedBy: .Equal, toItem: contentView, attribute: .CenterX, multiplier: 1.0, constant: 0.0)
self.addConstraints([constraint1, constraint2, constraint3, constraint4])
self.layoutIfNeeded()
// Later, the messages are added to the contentView. I don't think is relevant to see the exact code (but I can post it if needed)
// Each message is added using autolayout and the constraints only reference the messages themselves and contentView
}
}
When I add a ChatView to my view controller (using storyboards), with its four sides pinned to views which are not in his hierarchy, the following problem happens:
In the image, the scrollView cannot be scrolled upwards any more. There seem to be a space which should be filled and isn't. If I scroll down, I have the exact same problem but the empty space is below the content. In the following images you can see that the contentView is smallest than the ChatView itself:
And the same view hierarchy but with the constraints shown:
In both images the view in the background is the ChatView and the selected one is the contentView. I haven't been able to figure why the content view doesn't cover the full ChatView space.
Thanks in advance!
I finally stumbled upon the answer while searching a different problem, in another stackoverflow question. The key is to open the storyboard and set in the container view controller "AdjustsScrollViewInsets" to "NO".
In code it's simply (inside the view controller):
self.automaticallyAdjustsScrollViewInsets = NO;

Programmatic Autolayout changes behaviour of a UIView

I have a controller where I add a subview programmatically. With the configuration of the subview I add autolayout constraints programmatically. Everthing is working except that the view doesn't react on touches if I add the constraints and even the set backgroundcolor is not displayed.
The buttonView should be displayed in the lower right corner of my parent view.
Any ideas what could be wrong?
Here is how I add my constraints:
private func configureAutolayoutConstraints(buttonView: UIView, parentView: UIView){
buttonView.translatesAutoresizingMaskIntoConstraints = false
let bottomConstraint = NSLayoutConstraint(item: buttonView, attribute:
.Bottom, relatedBy: .Equal, toItem: parentView, attribute: .Bottom, multiplier: 1.0, constant: -130)
parentView.addConstraint(bottomConstraint)
let trailingConstraint = NSLayoutConstraint(item: buttonView, attribute:
.Trailing, relatedBy: .Equal, toItem: parentView, attribute: .Trailing, multiplier: 1.0, constant: -90)
parentView.addConstraint(trailingConstraint)
}
Autolayout engine needs at least 4 constraints to determine the frame of view. You have applied bottom and trailing constraints only. You need either width+height OR leading+top constraint to make it work.

Resources