I am making a card game on iOS which has a lot of animations. Sometimes when two animations were happening at the same time, the animated card would snap back to its original position. I found out that this was happening because I animated the center of the UIView and not its constraints. Now, I'm trying to animate the card with its constraints. The card has to be animated to the top of another card. I was wondering if theres a way to copy all the constraints of one UIView to that of another?
I tried doing
newConstraints = object1.constraints
object2.addConstraints(newConstraints)
and then animate it, but it didn't work.
I used the following code to move the card:
private func replaceConstraints(_ superview: UIView, ofView view1: AnyObject, toView view2: AnyObject) -> [NSLayoutConstraint]{
var constraintsToRemove: [NSLayoutConstraint] = []
var constraintsNew_item1: [NSLayoutConstraint] = []
for constraint in superview.constraints
{
if (constraint.firstItem === view1)
{
constraintsToRemove.append(constraint)
constraintsNew_item1.append(NSLayoutConstraint(item: view2, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: constraint.secondItem, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant))
}
}
superview.removeConstraints(constraintsToRemove);
superview.addConstraints(constraintsNew_item1);
return constraintsToRemove
}
followed by: self.view.layoutIfNeeded()
The card did animate to the required position but the card on top of which it was supposed to be placed, moved a little from its position giving me the following warning:
[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.
(
"<NSLayoutConstraint:0x6000030aaa80 UIView:0x7f8f8870af50.centerY == 0.5*UIView:0x7f8f8870b0c0.centerY (active)>",
"<NSLayoutConstraint:0x6000030a7e80 UIView:0x7f8f8870af50.centerY == 1.5*UIView:0x7f8f8870b0c0.centerY (active)>",
"<NSLayoutConstraint:0x6000030baf80 'UIView-Encapsulated-Layout-Height' UIView:0x7f8f8870b0c0.height == 667 (active)>"
)
Also, I'm doing most of my animations in a CardView Class so I wanted to know if theres a dynamic way of doing it.
Thanks.
I did it by tweaking the code a little:
private func replaceConstraints(_ superview: UIView, view1: AnyObject, view2: AnyObject) -> [NSLayoutConstraint]{
var constraintsToRemove: [NSLayoutConstraint] = []
var constraintsNew_item1: [NSLayoutConstraint] = []
for constraint in superview.constraints
{
if (constraint.firstItem === view1)
{
constraintsNew_item1.append(NSLayoutConstraint(item: view2, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: constraint.secondItem, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant))
}
else if (constraint.firstItem === view2)
{
constraintsToRemove.append(constraint)
}
}
superview.removeConstraints(constraintsToRemove);
superview.addConstraints(constraintsNew_item1);
return constraintsToRemove
}
Related
So I have a UIView near the bottom of the superview, with a textfield inside of it. When the user taps inside the textfield to begin editing, I am bringing up the entire UIView with the keyboard. One problem with this, is that if you have constraints on said UIView, when you start typing in the textfield, the UIView conforms to its constraints and goes back down to its original spot, hidden by the keyboard. I created a work around by overriding updateViewContraints(), removing the default constraint (the one I set in storyboard), when the textfield is being edited, and adding a new constraint to keep it where I want it. Then, when the editing ends, the code is supposed to bring the UIView back down, and remove the new constraint, and replace it with the original. Here's that code:
override func updateViewConstraints() {
// The new constraint, active when the keyboard is shown
let constraintWhenKeyboardShown = NSLayoutConstraint(item: searchRadiusView!, attribute: .bottom, relatedBy: .equal, toItem: super.view, attribute: .bottom, multiplier: 1.0, constant: -310.0)
// The default constraint, active at all other times
let defaultConstraint = NSLayoutConstraint(item: searchRadiusView!, attribute: .bottom, relatedBy: .equal, toItem: super.view, attribute: .bottom, multiplier: 1.0, constant: -30.0)
if radiusTextfield.isEditing {
view.removeConstraint(defaultConstraint)
view.addConstraint(constraintWhenKeyboardShown)
print("new")
} else {
view.removeConstraint(constraintWhenKeyboardShown)
view.addConstraint(defaultConstraint)
print("default")
}
self.view.layoutIfNeeded()
super.view.updateConstraints()
}
I call the updateViewConstraints in each of my textfield methods:
// Raises the searchRadiusView upon editing
func textFieldDidBeginEditing(_ textField: UITextField) {
let newSearchRadiusViewYValue = self.searchRadiusView.center.y - 280
searchRadiusView.center.y = newSearchRadiusViewYValue
print(searchRadiusView.constraints)
self.updateViewConstraints()
}
// Lowers the searchRadiusView upon dismissal
func textFieldDidEndEditing(_ textField: UITextField) {
let newSearchRadiusViewYValue = self.searchRadiusView.center.y + 280
searchRadiusView.center.y = newSearchRadiusViewYValue
print("ended")
print(searchRadiusView.constraints)
self.updateViewConstraints()
}
However, upon ending editing, my UIView is not moving back down to its original position, and the debugger says
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:0x607000071590 UIView:0x61300002f280.bottom == UIView:0x613000071380.bottom - 310 (active)>",
"<NSLayoutConstraint:0x607000297f80 UIView:0x61300002f280.bottom == UIView:0x613000071380.bottom - 30 (active)>"
)
It seems that the 'constraintWhenKeyboardShown' constraint is still there (the one ending in -310), even though I have 'view.removeConstraint(constraintWhenKeyboardShown)
' in the updateViewConstraints() function. Is that what's causing my problem? Does anyone know how to fix this? Thanks
I've been trying to add constraints programmatically to a view that I'm also adding programmatically to my view controller. However, it seems like the constraints are not being followed.
The view has been added to the story board for the view controller, but isn't actually added to the view controller's view until later on (See screenshot below).
I've tried adding a variety of constraints but none of them have worked so far. I've simplified it now to the single constraint below and even this will not work. What am I doing wrong?
#IBOutlet var loadingView: LoadingView!
override func viewDidLoad() {
super.viewDidLoad()
displayLoadingView(true)
}
func displayLoadingView(display: Bool) {
if display {
view.addSubview(loadingView)
let widthConstraint = NSLayoutConstraint(item: loadingView, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 50.0)
view.addConstraint(widthConstraint)
}
}
set translatesAutoresizingMaskIntoConstraints = false to any view you are settings constraints programatically.
from the apple doc: translatesAutoresizingMaskIntoConstraints
If you want to use Auto Layout to dynamically calculate the size and position of your view, you must set this property to false, and then provide a non ambiguous, nonconflicting set of constraints for the view.
You don't set all necessary constraints, that can be the reason. Consider following rough example. MyView interface is defined in standalone xib file. Hope it helps:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
guard let myView = loadFromNib("MyView") else {
return
}
view.addSubview(myView)
myView.translatesAutoresizingMaskIntoConstraints = false
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-15-[myView]-15-|", options: NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics: nil, views: ["myView": myView]))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-15-[myView]-15-|", options: NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics: nil, views: ["myView": myView]))
}
func loadFromNib(cls: String) -> UIView? {
return NSBundle.mainBundle().loadNibNamed(cls, owner: nil, options: nil)[0] as? UIView
}
}
I am a newbie swift developer, following code examples in a good iOS 9 Swift book. The book has just demonstrated how to add a constraint (in code) between two objects (label, button) that are in two different branches of the view hierarchy.
myLabel is in viewBB container, which is in the top view controller.
myButton is in the top view controller, outside of viewBB.
The example code worked fine. But for an exercise, I wanted to create a second label2 inside of viewBB, and constrain its position to be offset from myLabel. So both labels (I think) are in viewBB.
Creating label2 in code and adding the constraints in code works fine.
But... if I create label2 in the interface builder, and try to remove the IB constraints and replace them with my own, the build succeeds but the app crashes with the usual "view hierarchy not prepared" error.
*** The interesting thing is the error message talks about a label and a button, suggesting that the book example code (I called it BLOCK 0 below) is the offender.
What am I doing wrong? (I've read everything I can find on the error message, including 4 posts in this forum. But I'm at a loss to know why the book code is failing.)
The error message:
2016-02-25 12:52:32.796 TwoViewConstraints[5713:1860768]
The view hierarchy is not prepared for the constraint: NSLayoutConstraint:0x7c9ce990 UILabel:0x7c9dbec0'Label'.centerX ==
UIButton:0x7c9deed0'Button'.centerX
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.
Message from debugger: Terminated due to signal 15
Here is my code:
class ViewController: UIViewController {
#IBOutlet weak var myLabel: UILabel!
#IBOutlet weak var myButton: UIButton!
#IBOutlet weak var centerConstraint: NSLayoutConstraint!
#IBOutlet weak var viewBB: UIView!
#IBOutlet weak var lab2cxh: NSLayoutConstraint!
#IBOutlet weak var lab2cxv: NSLayoutConstraint!
#IBOutlet weak var label2: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// BLOCK 0 - works fine, from the iOS9 book
// myLabel and myButton were created with the interface builder
// Goal is to remove an IB constraint, and replace it with one
// that is built in code. (Because label and buttons are in
// different branches of the view hierarchy).
//
// remove auto center constraint from parent label
viewBB.removeConstraint(centerConstraint)
// center the label and the button across views
let ccx = NSLayoutConstraint(item: myLabel,
attribute: NSLayoutAttribute.CenterX,
relatedBy: NSLayoutRelation.Equal,
toItem: myButton,
attribute: NSLayoutAttribute.CenterX,
multiplier: 1.0,
constant: 0)
self.view.addConstraint(ccx)
// BLOCK 1 - Create a second label object in code
// this code works fine with the two added constraints below
// create a second label object and add it to the view
// let label2 = UILabel()
// label2.translatesAutoresizingMaskIntoConstraints = false
// label2.text="Label2"
// viewBB.addSubview(label2)
// BLOCK 2 - create a second label object in the interface builder
// Suggested horiz/vert constraints are set in the IB.
// So now I want to remove them, and replace them with my own,
// as was done in the Swift book example code in BLOCK 0.
// remove original label 2 constraints
viewBB.removeConstraint(lab2cxv)
viewBB.removeConstraint(lab2cxh)
// adding these two constraints works fine when the label2 object
// is created in code. But when I create label2 in the interface
// builder and try to remove the IB constraints (as was done in the
// first block of code), I get the error:
//
// ... "The view hierarchy is not prepared
// for the constraint "Label.CenterX to Button.CenterX", which is
// the first block of code.
//
// NOTE** To me, it looks like the error is coming from the
// BLOCK 0 constraint, since it cites a label and a button, not
// two labels.
// What am I doing wrong?
let c2h = NSLayoutConstraint(item: label2,
attribute: NSLayoutAttribute.CenterY,
relatedBy: NSLayoutRelation.Equal,
toItem: myLabel,
attribute: NSLayoutAttribute.CenterY,
multiplier: 1.0,
constant: -50)
self.viewBB.addConstraint(c2h)
// constrain label 2 to be diagonally left of label one
let c2v = NSLayoutConstraint(item: label2,
attribute: NSLayoutAttribute.CenterX,
relatedBy: NSLayoutRelation.Equal,
toItem: myLabel,
attribute: NSLayoutAttribute.CenterX,
multiplier: 1.0,
constant: -100)
self.viewBB.addConstraint(c2v)
}
I did the whole piece of code over again, from scratch, and it worked. Go figure. Here is the code that worked properly. As far as I can see, it is pretty much the same code, with the exception of a minor name change here and there.
I have no idea why this version works, and the previous version did not. It makes me wonder if something was different in the Interface Builder setup.
class ViewController: UIViewController {
#IBOutlet weak var viewbb: UIView!
#IBOutlet weak var myLabel: UILabel!
#IBOutlet weak var centerConstraint: NSLayoutConstraint!
#IBOutlet weak var myButton: UIButton!
#IBOutlet weak var label2: UILabel!
#IBOutlet weak var label2centerConstraint: NSLayoutConstraint!
#IBOutlet weak var label2Yconstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// remove the IB centering constraint, then add our own between label and button
viewbb.removeConstraint(centerConstraint)
// make a new constraint to center label over button
let newcon = NSLayoutConstraint(item: myLabel,
attribute: NSLayoutAttribute.CenterX,
relatedBy: NSLayoutRelation.Equal,
toItem: myButton,
attribute: NSLayoutAttribute.CenterX,
multiplier: 1.0,
constant: 0)
self.view.addConstraint(newcon)
// remove the IB centering constraint, then add our own between label and button
viewbb.removeConstraint(label2centerConstraint)
viewbb.removeConstraint(label2Yconstraint)
// make a new constraint to center label over button
let newcon2 = NSLayoutConstraint(item: label2,
attribute: NSLayoutAttribute.CenterX,
relatedBy: NSLayoutRelation.Equal,
toItem: myLabel,
attribute: NSLayoutAttribute.CenterX,
multiplier: 1.0,
constant: -100) // left of mylabel
// make a new constraint to center label over button
let newcon3 = NSLayoutConstraint(item: label2,
attribute: NSLayoutAttribute.CenterY,
relatedBy: NSLayoutRelation.Equal,
toItem: myLabel,
attribute: NSLayoutAttribute.CenterY,
multiplier: 1.0,
constant: 0) // left of mylabel
self.view.addConstraint(newcon3)
self.view.addConstraint(newcon2)
}
I have a project I'm working on that needs two buttons with some data that will take up exactly half the width of the UITableViewCell that they are in.
In the past when I have wanted to do this I usually set a constraint that the button will be equal widths to its superview and give it a multiplier of 0.5.
For some reason however inside the UITableViewCell I can't get the Storyboards to give me this option. The "Equal Widths" constraint in the GUI is grayed out.
I resolved to just do it programmatically so in the custom cell I tied the following code. I've tried putting the cellInit() method below being called in the awakeFromNib and that gave an error. I've tried also just calling it on cellForRowAtIndexPath when the cell is loaded, and got the same error.
import UIKit
class PollCell: UITableViewCell {
#IBOutlet weak var option1: UIButton!
#IBOutlet weak var option2: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
//cellInit() //Commented out because causes error
}
func cellInit(){
option1.addConstraint(NSLayoutConstraint(item: option1, attribute: .Width, relatedBy: .Equal, toItem: self, attribute: .Width, multiplier: 0.5, constant: 0))
}
}
This is the error that I am getting:
'NSInternalInconsistencyException', reason: 'Impossible to set up layout with view hierarchy unprepared for constraint.
What I'm trying to achieve is pretty standard, so I assume this isn't anything to crazy and I'm probably doing something the wrong way. Either way I assume plenty of newcomers like myself will run into this. Thanks in advance!
In the comments, we discussed that you leverage contentView.frame.maxX
Alternatively, you can use AutoLayout: Make sure you setTranslatesAutoresizingMaskIntoConstraints(false)
Assign tags (optional). You only havetwo buttons but for more than two, I would use tags so you don't need to manually type UIButton for every button.
addConstraint(NSLayoutConstraint(item: self.viewWithTag(1) as UIButton, attribute: .Right, relatedBy: .Equal, toItem: self, attribute: .CenterX, multiplier: 0.33, constant: 0))
addConstraint(NSLayoutConstraint(item: self.viewWithTag(2) as UIButton, attribute: .Right, relatedBy: .Equal, toItem: self, attribute: .CenterX, multiplier: 0.66, constant: 0))
OR VFL using a Dictionary:
for button in buttonsDictionary.keys {
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[\(button1)]-[\(button2)]|", options: .allZeros, metrics: nil, views: buttonsDictionary))
}
call cell.updateConstraints() in your cellForRowAtIndexPath in TableView.
You can learn more in the link below: They have an example of two side by side buttons:
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/AutoLayoutinCode/AutoLayoutinCode.html#//apple_ref/doc/uid/TP40010853-CH11-SW1
I've encountered a similar problem like yours before. What I did was put a UIView in the cell first with its top, left, right, bottom constraints set to all 0, then place the button on top of the view. This way I get the 'equal width' option.
I've been going through the documentation and still seem to be on a sticking point.
I have a view controller object C_SelectPhoto. This has a container view. Inside the container view I want the childed view controller, C_SelectPhotoControllerView, to fit inside it. It will just be an array of photos. However, setting the frame and adding the child view controller is not working. If I move the x value of the desired child view controller, no effect happens.
To figure out what is going on I color coded everything. The container, below, is orange. The view the container expects, according to the storyboard is yellow. The view I actually want to fit in there is red.
Here is the storyboard:
Here is my controller code for C_SelectPhoto
class C_SelectPhoto:Controller
{
#IBOutlet weak var selectPhotoControllerView: UIView!
var _collectionViewController:C_SelectPhotoControllerView!
//TODO PERMISSION IS NEEDED BEFORE FETCHING
func initController()
{
_collectionViewController = Controller.STORYBOARD.instantiateViewControllerWithIdentifier("selectPhotoControllerView") as C_SelectPhotoControllerView
displayControllerViewController()
}
//show the photo selection
private func displayControllerViewController()
{
addChildViewController(_collectionViewController)
_collectionViewController.view.frame = CGRectMake(100, 0, 500, 500)
self.view.addSubview(_collectionViewController.view)
_collectionViewController.didMoveToParentViewController(self)
}
}
However the result is produces is below:
First, the yellow class shouldn't be added at all, I wanted only the red (the UICollectionViewController class). Second, I can tell the red class is being added to the wrong spot because its x value hasn't moved it over at all.
So my question is:
How can I add a UIContainerViewController, as a child to the main view controller, C_SelectPhoto, but have the UIContainerViewController frame FIT the container I have in the main view controller?
Thank you!!!
NOTE: The views I am trying to add are UICollectionViewControllers. When I add a UIViewController, the framing works just fine, but as you can see when adding the UICollectionViewControllers, the framing does NOT work, and they are getting added to random offsets and are not respecting my attempts to size them with frame assignments.
use following Extension for adding childViewController On View
extension UIViewController {
func configureChildViewController(childController: UIViewController, onView: UIView?) {
var holderView = self.view
if let onView = onView {
holderView = onView
}
addChildViewController(childController)
holderView.addSubview(childController.view)
constrainViewEqual(holderView, view: childController.view)
childController.didMoveToParentViewController(self)
childController.willMoveToParentViewController(self)
}
func constrainViewEqual(holderView: UIView, view: UIView) {
view.translatesAutoresizingMaskIntoConstraints = false
//pin 100 points from the top of the super
let pinTop = NSLayoutConstraint(item: view, attribute: .Top, relatedBy: .Equal,
toItem: holderView, attribute: .Top, multiplier: 1.0, constant: 0)
let pinBottom = NSLayoutConstraint(item: view, attribute: .Bottom, relatedBy: .Equal,
toItem: holderView, attribute: .Bottom, multiplier: 1.0, constant: 0)
let pinLeft = NSLayoutConstraint(item: view, attribute: .Left, relatedBy: .Equal,
toItem: holderView, attribute: .Left, multiplier: 1.0, constant: 0)
let pinRight = NSLayoutConstraint(item: view, attribute: .Right, relatedBy: .Equal,
toItem: holderView, attribute: .Right, multiplier: 1.0, constant: 0)
holderView.addConstraints([pinTop, pinBottom, pinLeft, pinRight])
}}
Updated for Swift 5+
Just one line in your view controller to add child view controller.
Super scalable methods in the extension if you want to add it on any custom view.
public extension UIViewController {
/// Adds child view controller to the parent.
///
/// - Parameter child: Child view controller.
func add(_ child: UIViewController) {
addChild(child)
view.addSubview(child.view)
child.didMove(toParent: self)
}
/// It removes the child view controller from the parent.
func remove() {
guard parent != nil else {
return
}
willMove(toParent: nil)
removeFromParent()
view.removeFromSuperview()
}
}
How to use:
Adding: In the view controller where you want to add the child view controller.
// let yourChildViewController = Load fro the storyboard or XIB
add(yourChildViewController)
Removing:
yourChildViewController.remove()
If you want the red controller to be the child controller, delete the yellow one, and control-drag from the container to the red controller. There's no need to add it in code, or do any resizing. The red controller will be set to the same size as the container in the storyboard.