SnapKit Updating Constraint causes conflict - ios

I'm building an iOS app against iOS 12 SDK, Swift 4 and SnapKit 4.2
I want to update a constraint when I tap a button but it creates a conflict with the previous version of the constraint.
Here's my code:
private var menuConstraint: Constraint?
override func updateViewConstraints() {
super.updateViewConstraints()
menuVc.view.snp.makeConstraints { (make) in
self.menuConstraint = make.top.equalTo(view.snp.top).constraint
make.right.equalTo(view.snp.right)
make.width.equalTo(100)
make.height.equalTo(100)
}
}
#objc func onMenuTap() {
self.menuConstraint!.update(offset: 100)
}
When onMenuTap is called I get the following error:
[LayoutConstraints] 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.
(
"<SnapKit.LayoutConstraint:0xABC#MyViewController.swift#77 UIView:0xDEF.top == UIView:0xGHI.top>",
"<SnapKit.LayoutConstraint:0xABC#MyViewController.swift#77 UIView:0xDEF.top == UIView:0xGHI.top + 100.0>"
)
As you can see the previous version of the top constraint (without the offset) is conflicting with the new version. It's as if it didn't update the existing constraint but instead just created a new one.
I've tried a few variations:
wrapping the update line in a snp.updateConstraints closure
setting an initial offset when first creating the constraint
grabbing the underlying LayoutConstraint and updating constant directly.
I always get the same error message.
Do I have something configured wrong?

Don't put constraints inside updateViewConstraints as it'll recreate constraints as it's called multiple times so set the code inside viewDidLoad

Related

View doesn't update after changing constraints programmatically

I want to change the relation of a constraint programmatically that is why I create a new one and replace the old one with it. I have an IBOutlet of my constraint which I want to change. As because I am using multi-os-engine my code for updating the constraint looks a little bit different to normal objectiv-c or swift.
setBasketTopConstraint(
NSLayoutConstraint.constraintWithItemAttributeRelatedByToItemAttributeMultiplierConstant(
basketTopConstraint().firstItem(),
basketTopConstraint().firstAttribute(),
NSLayoutRelation.LessThanOrEqual,
basketTopConstraint().secondItem(),
basketTopConstraint().secondAttribute(),
basketTopConstraint().multiplier(),
basketTopConstraint().constant()
)
);
My problem is, that I can't see any effect from changing the constraint. It seems like I need to refresh my view somehow. I called view().setNeedsLayout() and view().layoutIfNeeded() but with no success.
Any advice?
You need to ensure that you:
Install any new constraints and activate them
Deactivate and/or remove any old constraints
Call layoutIfNeeded()
Did you try adding the code to change constraints in view will layout subviews??

xcode reporting constraint error at runtime - but can't find it?

I am getting this error during runtime:
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7ff70350c8a0 UITableViewCellContentView:0x7ff7058511c0.trailingMargin == UIView:0x7ff705851f00.trailing>
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.
2016-08-29 16:53:12.533 xxxx[7125:4051730] 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:0x7ff705a0aca0 UITableViewCellContentView:0x7ff705853420.trailingMargin == UIView:0x7ff7058546f0.trailing - 10>",
"<NSLayoutConstraint:0x7ff705a0ad90 UITableViewCellContentView:0x7ff705853420.trailingMargin == UIView:0x7ff7058546f0.trailing>"
)
However, I have been unable to identify from where it gets
UITableViewCellContentView:0x7ff705853420.trailingMargin ==
UIView:0x7ff7058546f0.trailing
I can not find in the IDE. I have tried searching code for where I set constraints - but nothing that match the error as far as I can tell.
Is it possible there is a constraint defined that Xcode IDE does not show?
You can extend NSLayoutConstraint and override description to return slightly more human readable information when a constraint breaks:
extension NSLayoutConstraint {
override public var description: String {
let id = identifier ?? "NO ID"
return "id: \(id), constant: \(constant)"
}
}
The tedious part of this is that you have to manually set an id for each constraint, though you can limit the amount of work needed by commenting out the extension and only setting identifiers for constraints that are related to the standard constraint warning.
You can try opening MainStoryBoard as source code and search there. The problem is you have 2 same constrains (trailing margin to UIView), one of them is 0, other is 10. It is possible it was not deleted from source code of storyboard.

UIViewController hangs in Xcode 7

Not sure whether title is correct or not, I'm calling newViewController on button click but it is not rendering that. as it was working fine in Xcode 6.4 but in Xcode 7 its hangs the UI and not showing new view controller after pushViewController.
this is the error logs which print on console
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. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"<NSLayoutConstraint:0x16bbe0c0 V:[UIView:0x157fb230(45)]>",
"<NSLayoutConstraint:0x16bbc390 V:[UIView:0x157fb230(0)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x16bbe0c0 V:[UIView:0x157fb230(45)]>
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.
I add break point in viewDidLoad of next controller it is calling that but not loading that ViewController.
EDIT
I seen that CPU usage are at 100% and second thing that my Viewcontroller have tableView so when i debug then it call my CellForRowatIndexPath for all the rows, so not sure where it exactly getting freezed my ViewController.
UPDATE
I'm updating my question again, I removed that error constrains (i.e. 45 and 0) now I'm not getting any constrains warning or error on console but still my UIView is freezes. Please help Thanks
It seems like you have two conflicting height constraints in one of your views. One is telling your view to have 0 height, the other tells it to have 45.
Also not related to the error message: You do not need the "Align center X to Superview" constraint, as that will be implicit because of leading/trailing constraints.
EDIT:
Try the implementing the following initializer and instantiate it with [MyViewControllerClass new]. I have had trouble previously with the default behavior of the viewcontroller loading a nib with the same name as the view controller by default. This is of course assuming you're not using a storyboard, in which case I can't really help.
- (instancetype)init
{
self = [super initWithNibName:#"MyNibName" bundle:nil]
if (self)
{
}
return self
}
I had this problem as well and it had to do with placeholder text (I am using storyboard). Clear out any placeholder text you have in fields and see if it works! Mine does. If that helps, just set the placeholder text in code as opposed to storyboard. I answered a similar question here:
Instantiating Modal view controller freezes the app - Probable issue with Xcode 7

updateViewConstraints called when creating constraints in viewDidLoad

Currently I'm experiencing a strange behavior. I removed the auto resizing mask from a view for viewForHeaderInSection. When I'm doing this the app crashes because I tried to remove a constraint which is nil.
I'm creating the concerned constraints in viewDidLoad in my parent controller. In debug mode I found out that when the system tries to create a constraint where my child view controller is involved (which has the removed auto resizing mask from the section header view) it directly jumps to updateViewConstraints. Of course the constraints are nil because the weren't created yet.
If I add again the auto resizing mask the app works, but I can't do what I'm trying to do (to layout my views correctly).
If I create my constraints in updateViewConstraints the app also works.
I don't understand why this is happening. On a similar view controller it is working without problems. Sometimes I think auto layout is more a pain than a gain. On a server error an alert was displayed. Here the view could be correctly loaded. Seems that this is a kind of timing problem.
I want to know why this is happening and how should I proceed in future that such an error doesn't happen anymore. Am I doing something wrong?
Edit:
Don't know if it helps but if I call setNeedsLayout and layoutIfNeeded on the view of my child view controller in viewDidLoad of my parent then also the app crashes.
Edit 2:
Seems that it occurs when I add multiple views with constraints on different places to my view controller. For my table I add an empty message if there are currently no entries. If I don't add the label as subview to my table everything works fine.
So when I'm allowed to add my constraints? Currently I add them right after the view was added as subview. For the empty message it is in viewDidLoad and for the section header it is viewForHeaderInSection. Do I have to use something like setNeedsLayout?
Edit 3:
Adding a subview to the table view isn't a good idea at all (especially when using auto layout). For now I'm using the background view, but that's not the solution I'm looking for.
You do not need to change the value of translatesAutoresizingMaskIntoConstraints unless you actually have set the autoresizingMask to something other than 0. If you are using constraints, the solution is to completely ignore autoresizingMask and translatesAutoresizingMaskIntoConstraints.
Now I got the same problem again. My workaround didn't worked:
// on iOS 7 this would bring "Auto Layout still required after executing -layoutSubviews. UITableView's implementation of -layoutSubviews needs to call super." and the app crashes
// on iOS 8 this is needed, otherwise "Unable to simultaneously satisfy constraints."
// bug?
if (UIDevice.CurrentDevice.CheckSystemVersion (8, 0)) {
sectionHeader.TranslatesAutoresizingMaskIntoConstraints = false;
}
What I've found out so far is that it has to do with the headerview and the empty message (view). When both are used I get this problem. Either the app crashes or I get
Unable to simultaneously satisfy constraints
depending if I have the autoresizing mask turned off or not. Interestingly, only iOS 8 made this problem with my workaround. A solution would be to set a boolean variable in viewDidLoad if the constraints have been set up and check for this in updateViewConstraints. But I wanted to know the real cause for this and not using a workaround for a workaround ...
Every time I retrieve new data for my table I checked if the number of records is zero or not. If they were zero I showed my empty message. The problem seems to be caused by the reloadData, which I called before this check. Than UITableViewHeaderFooterContentView got a height of zero from the NSAutoresizingMaskLayoutConstraint. Restructuring my code to this variant
if (myList.Count == 0) {
this.TableView.BackgroundView = emptyMessage;
this.TableView.SeparatorStyle = UITableViewCellSeparatorStyle.None;
} else {
this.TableView.BackgroundView = null;
this.TableView.SeparatorStyle = UITableViewCellSeparatorStyle.SingleLine;
if (emptyMessage != null) {
emptyMessage.RemoveFromSuperview ();
}
}
// call this afterwards!
TableView.ReloadData();
seems to solve my issue.

Where should I be setting autolayout constraints when creating views programmatically

I see different examples where constraints are set. Some set them in viewDidLoad / loadView (after the subview was added). Others set them in the method updateViewConstraints, which gets called by viewDidAppear.
When I try setting constraints in updateViewContraints there can be a jumpiness to the layout, e.g. slight delay before the view appears. Also, if I use this method, should I clear out existing constraints first i.e. [self.view [removeConstraints:self.view.constraints]?
I set up my constraints in viewDidLoad/loadView (I'm targeting iOS >= 6). updateViewConstraints is useful for changing values of constraints, e.g. if some constraint is dependent on the orientation of the screen (I know, it's a bad practice) you can change its constant in this method.
Adding constraints in viewDidLoad is showed during the session "Introduction to Auto Layout for iOS and OS X" (WWDC 2012), starting from 39:22. I think it's one of those things that are said during lectures but don't land in the documentation.
UPDATE: I've noticed the mention of setting up constraints in Resource Management in View Controllers:
If you prefer to create views programmatically, instead of using a
storyboard, you do so by overriding your view controller’s loadView
method. Your implementation of this method should do the following:
(...)
3.If you are using auto layout, assign sufficient constraints to each of
the views you just created to control the position and size of your
views. Otherwise, implement the viewWillLayoutSubviews and
viewDidLayoutSubviews methods to adjust the frames of the subviews in
the view hierarchy. See “Resizing the View Controller’s Views.”
UPDATE 2: During WWDC 2015 Apple gave a new explanation of updateConstraints and updateViewConstraints recommended usage:
Really, all this is is a way for views to have a chance to make changes to constraints just in time for the next layout pass, but it's often not actually needed.
All of your initial constraint setup should ideally happen inside Interface Builder.
Or if you really find that you need to allocate your constraints programmatically, some place like viewDidLoad is much better.
Update constraints is really just for work that needs to be repeated periodically.
Also, it's pretty straightforward to just change constraints when you find the need to do that; whereas, if you take that logic apart from the other code that's related to it and you move it into a separate method that gets executed at a later time, your code becomes a lot harder to follow, so it will be harder for you to maintain, it will be a lot harder for other people to understand.
So when would you need to use update constraints?
Well, it boils down to performance.
If you find that just changing your constraints in place is too slow, then update constraints might be able to help you out.
It turns out that changing a constraint inside update constraints is actually faster than changing a constraint at other times.
The reason for that is because the engine is able to treat all the constraint changes that happen in this pass as a batch.
I recommend creating a BOOL and setting them in the -updateConstraints of UIView (or -updateViewConstraints, for UIViewController).
-[UIView updateConstraints]: (apple docs)
Custom views that set up constraints themselves should do so by overriding this method.
Both -updateConstraints and -updateViewConstraints may be called multiple times during a view's lifetime. (Calling setNeedsUpdateConstraints on a view will trigger this to happen, for example.) As a result, you need to make sure to prevent creating and activating duplicate constraints -- either using a BOOL to only perform certain constraint setup only once, or by making sure to deactivate/remove existing constraints before creating & activating new ones.
For example:
- (void)updateConstraints { // for view controllers, use -updateViewConstraints
if (!_hasLoadedConstraints) {
_hasLoadedConstraints = YES;
// create your constraints
}
[super updateConstraints];
}
Cheers to #fresidue in the comments for pointing out that Apple's docs recommend calling super as the last step. If you call super before making changes to some constraints, you may hit a runtime exception (crash).
This should be done in ViewDidLoad, as per WWDC video from Apple and the documentation.
No idea why people recommend updateConstraints. If you do in updateConstraints you will hit issues with NSAutoresizingMaskLayoutConstraint with auto resizing because your views have already taken into account the auto masks. You would need to remove them in updateConstraints to make work.
UpdateConstraints should be for just that, when you need to 'update' them, make changes etc from your initial setup.
Do it in view did layout subviews method
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
I have this solution to change constraints before those who are in the storyboard are loaded.
This solution removes any lags after the view is loaded.
-(void)updateViewConstraints{
dispatch_async(dispatch_get_main_queue(), ^{
//Modify here your Constraint -> Activate the new constraint and deactivate the old one
self.yourContraintA.active = true;
self.yourContraintB.active= false;
//ecc..
});
[super updateViewConstraints]; // This must be the last thing that you do here -> if! ->Crash!
}
You can set them in viewWillLayoutSubviews: too:
override func viewWillLayoutSubviews() {
if(!wasViewLoaded){
wasViewLoaded = true
//update constraint
//also maybe add a subview
}
}
This worked for me:
Swift 4.2
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Modify your constraints in here
...
}
Although honestly I am not sure if it is worth it. It seems a bit slower to load than in viewDidLoad(). I just wanted to move them out of the latter, because it's getting massive.
Add your constraints in viewWillLayoutSubviews() to add constraints programmatically
See Apple Documentation in Custom Layout Section
If possible, use constraints to define all of your layouts. The
resulting layouts are more robust and easier to debug. You should only
override the viewWillLayoutSubviews or layoutSubviews methods when you
need to create a layout that cannot be expressed with constraints
alone.
Following example is to pass any view to another class. create my view from storyboard
Swift 5.0
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DispatchQueue.main.async {
self.abcInstance = ABC(frame: self.myView.frame)
}
}
If you miss DispatchQueue.main.async, it will take time to update constraints in viewWillAppear. Create myView in storyboard and give constraints same as screen width & height, then try printing frame of myView. It will give accurate value in DispatchQueue.main.async or in viewDidAppear but not give accurate value in viewWillAppear without DispatchQueue.main.async.

Resources