SWIFT: programmatically created constraints not updating properly - ios

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

Related

How to use AutoLayout constraints of a uiview on another uiview?

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
}

Add View Outside of ContentView in Cell

I have a collection view cell and I want to add a view to it. Apple states that views should be added to the contentView. This is there documentation:
When configuring a cell, you add any custom views representing your cell’s content to this view. The cell object places the content in this view in front of any background views.
https://developer.apple.com/documentation/uikit/uicollectionviewcell/1620133-contentview
However, it seems I can also add view not inside the contentView and there is no warnings or crashes. Here is my code. Notice the comment QUESTION -- I want to know if this is ok to do:
class MyCollectionViewCell: UICollectionViewCell {
#IBOutlet private var imageView: UIImageView!
private let titleLabel = UILabel(frame: .zero)
override func awakeFromNib() {
super.awakeFromNib()
// QUESTION: Is this ok? Notice I am not adding the `titleLabel` to `self.contentView`
self.addSubview(titleLabel)
let horizontalCenterConstraint = NSLayoutConstraint(item: titleLabel, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1.0, constant: 0)
let verticalCenterConstraint = NSLayoutConstraint(item: titleLabel, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1.0, constant: 0)
self.addConstraint(horizontalCenterConstraint)
self.addConstraint(verticalCenterConstraint)
}
}
In the code above, instead of adding the view with self.contentView.addSubview... instead I simply do self.addSubview. Is this ok?
Most things are possible, The question is why do you want to do this?
By directly going against an API author's recommendation, you're risking a whole host of potential issues. e.g.
You may be breaking something you're unaware of
You may be adversely affecting performance
Even if it works currently, it may break in the future and the author has no reason to prevent this from happening.
If what you're trying to do is impossible otherwise and you're ok with the risks, go for it. Otherwise, stick to the author's explicit instructions.
Yes, I tried to add cell directly instead of contentView. Now, weird thing happens, the collection view could not scroll. It took me 1 hour to figure out that I should add itself to contentView

How to create a slide animation inside a view controller?

I'd like to animate a view to slide in and out from the left.
What I did so far:
When the user clicks on the upper left icon, an action (show/hide menu-view) is triggered.
The "menu-view" includes the dark mask view, the semi-transparent white view and all three views (label + image).
Now this menu view shall slide in and out.
I tried to add a constraint to the menu view:
func viewDidLoad() {
super.viewDidLoad()
menuView.translatesAutoresizingMaskIntoConstraints = false
menuViewLeftConstraint = NSLayoutConstraint(item: menuView, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: -1000)
menuViewLeftConstraint.isActive = true
}
and I toggled the constant on every click the user performs (-1000 or 0).
But the animation does not look like I thought it would.
Why do this programmatically with a fixed constant? Set the right of your uiview (trailing) equal to the leading (left) of your superciew (uiviewcontroller). Create an outlet of that constraint and animate it by adding a constant which is equal to the uiview’s width and maybe some offset.
Alternative you can make your subview equal to your superviews width - someoffset, equal height -someoffset, centerX and centerY to the superview and animate the centerX constraint.

How to get the number of lines of the message label used before the uitableview cell is being display

I have a label in the cell, now i am customize my cell and added a label into the cell. I am creating a chatting apps, however on the top of the conversation , i need to determine the top constraint suppose to given to the profile image or the message label, if the message label only got one line , the top constraint is given to the profile image, if the message label is taller than the profile image, i will give the constraint to the message label. Ok the logic sound good, but the problem is come, i added the above logic under the initialiser as below
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
print(messageLabel.frame.height)
}
However, when the cell is being created, the print result gave to me is 0, in the end i suspect the message label is havent created so the height is 0.
In this case, i use the following function
override func layoutSubviews() {
super.layoutSubviews()
if messageLabel.frame.height != 0
{
if self.messageLabel.tag != 1
{
messageLabel.tag = 1
if (bubbleImageView.frame.height > 60)
{
contentView.addConstraint(NSLayoutConstraint(item: bubbleImageView, attribute: .Top, relatedBy: .Equal, toItem: contentView, attribute: .Top, multiplier: 1, constant: 4.5))
} else
{
contentView.addConstraint(NSLayoutConstraint(item: profileImageView, attribute: .Top, relatedBy: .Equal, toItem: contentView, attribute: .Top, multiplier: 1, constant: 4.5))
}
}
}
}
However it is work, the message label returned me the value of the height, but there is a bug on the above code which is, the cell is display first, the code only can be execute , in this case , when i debug it in the simulator, it took one second before execute the code, in this case, the user will see how the cell transform from none constraint to have constraint which is a bad practice for the user experience. In the end, i have to return back how to get the number of lines of the message label before the cell being displayed. So i can i achieve it ?
You don't want to do this in func layoutSubviews() since your code now will add the constraint several times (func layoutSubviews() is executed more than once). The issue you are having is pretty much what your conclusion is, the label hasn't been laid out yet since this will happen when layoutSubviews() is executed. What you can do however is to force the layoutSubviews() to run before you add your constraint.
I don't really understand which views needs to be forced though, but in viewDidLoad for your UITableViewController you do
view.setNeedsLayout()
view.layoutIfNeeded()
and in your init for your cell you do the same for self. Maybe even for the label itself.
This function will return the number of line tableview will show
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myArray.count
}
It is numberOfRowsInSection from tableView delegate function

How to make a button half the width of a UITableViewCell using constraints (Storyboard or Programatically)

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.

Resources