Swift: removefromSuperview removes constraints - ios

Currently working on some swift program and I've a call to action where I remove a blurview from the superview and at the same time I'm animating 2 buttons.
Everything works like it should but there is one small problem. When I remove the blurview from my superview the constraints on my 2 buttons are being set to 0 on the bottom and animating from that position.
I don't want them to shift to 0. If I don't remove the blurview my animation is working perfectly. I've checked if my button constraints are related to the blurview, but that isn't the case. Because I assumed that it could only reset my constraints when they are relative to the blurview.
My storyboard looks the following:
view
|-> camera_view
|-> blur_view
|-> record_label
|-> record_button
The code that I'm executing is the following:
#IBAction func recordButton(sender: AnyObject) {
self.blurView?.removeFromSuperview()
UIButton.animateWithDuration(0.3, delay: 0.2, options: .CurveEaseOut, animations: {
var recordButtonFrame = self.recordButton.frame
var recordLabelFrame = self.recordLabel.frame
recordButtonFrame.origin.y -= recordButtonFrame.size.height
recordLabelFrame.origin.y -= recordLabelFrame.size.height
self.recordButton.frame = recordButtonFrame
self.recordLabel.frame = recordLabelFrame
}, completion: { finished in
print("Button moved")
})
}
What am I doing wrong?
Kind regards,
Wouter

Instead of removing blurView from superview you could hide it.
Replace
self.blurView?.removeFromSuperview()
with
self.blurView?.hidden = true

The problem is that you're animating frames while using constraints. You should be animating constrain changes / constraint constant value changes.
When you don't remove the view the layout isn't recalculated so your frame animation 'works'. It isn't correct and will get reorganised at some point in the future.
When you remove the view the layout is recalculated and everything moves around before your animation starts.
You don't give details of your constraints but it seems likely that you should be animating constraints before removing the view, then removing and ensuring the constraints are all sane on completion.

Related

Hide one UIView and show another

When I click on an a segment of my UISegmentedControl, I want one of two UIViews to be displayed. But how do I arrange it, that I only show the new view. Currently I am calling thisview.removeFromSuperview() on the old one, and then setup the new all from scratch. I also tried setting all the HeightConstants of the views subviews to zero and then set the heightConstants of the view itself to zero but I'd rather avoid that constraints-surgery..
What better approaches are there?
Agree with #rmaddy about using UIView's hidden property, a nice simple way to cause a view to not be drawn but still occupy its place in the view hierarchy and constraint system.
You can achieve a simple animation to make it a bit less jarring as follows:
UIView.animate(withDuration:0.4, animations: {
myView.alpha = 0
}) { (result: Bool) in
myView.isHidden = true
}
This will fade the alpha on the view "myView", then upon completion set it to hidden.
The same animation concept can be used also if you've views need to re-arrange themselves, animating layout changes will be a nice touch.
Based on #rmaddy and #CSmiths answer, I built the following function:
func changeView(newView: UIView, oldView: UIView) {
newView.isHidden = false
newView.alpha = 0
UIView.animate(withDuration:0.4, animations: {
oldView.alpha = 0
newView.alpha = 1
}) { (result: Bool) in
oldView.isHidden = true
}
}
I feel dumb now for all the hours I spent on that constraint-surgery. :|

UIButton frame doesn't change if its panned

Here is an image of a simple APP I am trying to make
The App has a pan gesture recogniser for the centre purple button, the button when moved if it intersects with any of the 4 orange buttons, the purple button animates and moves inside the bounds of button it intersects otherwise it animates back to the initial starting position i.e centre.
For the first time when I pan the purple button the frame of the button updates, but If I try for the second time the frame of button remains the same (the value when it's at the centre of the view).
I am guessing this is something related to Auto Layout that I am missing, because if I remove the constrains on the centre purple button the frame updates every time correctly if I pan.
Can anybody explain what I have to keep in mind when animating with constraints
Here is my code for handling the Pan gesture:
#objc func handleCenterRectPan(_ pannedView: UIPanGestureRecognizer) {
let fallBackAnimation = UIViewPropertyAnimator(duration: 0.3, dampingRatio: 0.5) {
if self.button1.frame.intersects(self.centerRect.frame) {
self.centerRect.center = self.button1.center
} else if self.button2.frame.contains(self.centerRect.frame) {
self.centerRect.center = self.button2.center
} else if self.button3.frame.contains(self.centerRect.frame) {
self.centerRect.center = self.button3.center
} else if self.button4.frame.contains(self.centerRect.frame) {
self.centerRect.center = self.button4.center
} else {
// no intersection move to original position
self.centerRect.frame = OGValues.oGCenter
}
}
switch pannedView.state {
case .began:
self.centerRect.center = pannedView.location(in: self.view)
case .changed:
print("Changed")
self.centerRect.center = pannedView.location(in: self.view)
case .ended, .cancelled, .failed :
print("Ended")
fallBackAnimation.startAnimation();
//snapTheRectangle()
default:
break
}
}
First of all, you shouldn't be giving constraints if your aim is to move your views smoothly. And If you can't resist from constraints than you have to keep updating constraints and not the frames (centre),
In your code, I assume you have given your purple button centre constraints and then you are changing button's centre as user drag using the pan gesture. The problem is constraints you have given are still active and its try to set it back as soon as layout needs to update.
So what you can do is
Don't give constraints as its need to move freely when user drag
OR
Create IBoutlet for constraints and set .isActive to false when user dragging and make it true if it's not inside any of the other buttons and update UpdateLayoutIfNeeded, so it will come back to original position. (good approach)
#IBOutlet var horizontalConstraints: NSLayoutConstraint!
#IBOutlet var verticalConstraints: NSLayoutConstraint!
//make active false as the user is dragging the view
horizontalConstraints.isActive = false
verticalConstraints.isActive = false
self.view.layoutIfNeeded()
//make it true again if its not inside any of the other views
horizontalConstraints.isActive = true
verticalConstraints.isActive = true
self.view.layoutIfNeeded()
OR
Update constraints.constants for the horizontal and vertical constraints when user is moving the view (Not a good approach) and make it zero again if it's not inside of any other view
//make it true again if its not inside any of the other views
horizontalConstraints.constant = change in horizontal direction
verticalConstraints..constant = change in vertical direction
self.view.layoutIfNeeded()
Edit As you said constraints is nil after setting isActive = false because
what isActive actually do is add and remove those constraints as Apple doc says
Activating or deactivating the constraint calls addConstraint(:) and removeConstraint(:) on the view that is the closest common ancestor of the items managed by this constraint. Use this property instead of calling addConstraint(:) or removeConstraint(:) directly.
So making constraints strong reference does solve the issue but I think setting the strong reference to IBOutelet is not a good idea and adding and removing constraints programmatically is better. check out this question deallocating constraints

How to animate centered square to the top

I have a UIView in a portrait-only app.
The view is centered vertically and horizontally with AutoLayout ("manually" using storyboards).
The width equals the (main)view.width * 0.9
The height is the same size of the width (it is a square).
I want to tap a button inside this UIView and animate it only vertically until it reaches the top border of the screen (eg. height*0.9, 10 pts, whatever is possible).
When I click again, I want to reposition back the view to its original position (centered as it was when I first tapped).
During the transition the square should not be tappable.
After reading many posts I could not understand what's the best way to do this (I red mainly developers saying old techniques using centerX should be avoided and lamentations about some versions of the SO behaving in strange ways).
I suppose I should find a way to get the current "position" of the constraints and to assign a constraint the "final" position, but I was not able to do it.
Any help is appreciated
You are all going the wrong way about this.
Add one constraint that pins the view to the top, and add one constraint that pins the view to centerY. It will complain, so pick one and disable it (I think the property in Interface Builder is called Installed).
If the initial state is the view in the center, disable the constraint that pins it to the top, and viceversa.
Now write IBOutlets for both constraints in your controller and connect them to those constraints. Make sure the declaration of that variable is not weak, otherwise the variable will become nil when the constraint is disabled.
Whenever you want to toggle your animation you can enable one constraint and disable the other.
#IBOutlet var topConstraint: NSLayoutConstraint!
#IBOutlet var centerConstraint: NSLayoutConstraint!
func toggleState(moveToTop: Bool) {
UIView.animateWithDuration(0.25) {
self.topConstraint.isActive = moveToTop
self.centerConstraint.isActive = !moveToTop
self.view.layoutIfNeeded()
}
}
While you can use Autolayout to animate - to take the constraint constraining the centerY and set its constant to a value that would move to the top (e.g., constant = -(UIScreen.main.bounds.height / 2)), I would recommend using view's transform property.
So to move the view to the top you can use:
let topMargin = CGFloat(20)
let viewHalfHeight = self.view.bounds.height / 2
let boxHalfHeight = self.box.bounds.height / 2
UIView.animate(withDuration: 0.2) {
box.transform = CGAffineTransform.identity
.translatedBy(x: 0, y: -(viewHalfHeight - (boxHalfHeight + topMargin)))
}
You are moving box.center related to the view.center - so if you want to move the box to the top, you have to move its center by half a view's height (because the view's centerY is exactly height / 2 far from the view's top). That is not enough though, because then only a bottom half of the box is visible (now the box.centerY == view.top). Therefore you have to move it back by the box.bounds.height / 2 (in my code boxHalfHeight) - to make the top half visible. And to that boxHalfHeight you add topMargin so that there is some margin to the top.
Then, to move the box back to original position:
UIView.animate(withDuration: 0.2) {
box.transform = CGAffineTransform.identity
}
EDIT
If you really want to go with autolayout, you have to have a reference to the centerY constraint, so for example if it is created this way:
let boxCenterYConstraint = self.box.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)
boxCenterYConstraint.isActive = true
Then you can try this:
// calculating the translation is the same
let topMargin = CGFloat(20)
let viewHalfHeight = self.view.bounds.height / 2
let boxHalfHeight = self.box.bounds.height / 2
let diff = -(viewHalfHeight - (boxHalfHeight + topMargin))
boxCenterYConstraint.constant = diff
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.2) {
self.view.layoutIfNeeded()
}
And animation back:
boxCenterYConstraint.constant = 0
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.2) {
self.view.layoutIfNeeded()
}

Using animateWithDuration when a button is pressed but it has no effect (swift 2)

I'm using the following code to have a label slide onto the screen when a button is pressed, but it's having no effect.
override func viewDidLayoutSubviews() {
summaryLabel.alpha = 0
}
#IBAction func searchButton(sender: AnyObject) {
UIView.animateWithDuration(2) { () -> Void in
self.summaryLabel.center = CGPointMake(self.summaryLabel.center.x - 400, self.summaryLabel.center.y)
self.summaryLabel.alpha = 1
self.summaryLabel.center = CGPointMake(self.summaryLabel.center.x + 400, self.summaryLabel.center.y)
}
//button code continues...
I've tested what's going on by fixing the alpha at 1, but the label just stays where it is and does not move when the button is pressed. What am I missing?
A couple of things:
First of all, your two changes to the view's center cancel each other out. The animation applies the full set of changes that are inside the animation block all in one animation. If the end result is no change, then no change is applied. As Ramy says in his comment, you either need 2 animations, timed so the second one takes place after the first one has completed, or you nee to apply the first change before the animation begins. I would suggest starting with a single change, and a single animation, and get that working first.
Second problem: View controllers use auto layout by default. With auto layout, you can't animate the position of a view directly. It doesn't work reliably. Instead, you have to put a constraint on the view, connect it to an outlet, and the animate a change to the constraint's constant value by changing the constant and calling layoutIfNeeded() inside the animation block. The call to layoutIfNeeded() inside the animation block causes the view's position to be changed, and since it's inside the animation block, the change is applied with animation.

How do I move an UI Object in swift that has its constraints set in a storyboard?

So I am trying to create a simple moving animation for my UIImageView.
Problem is I've had its constraints set in the storyview (3 constraints, top, left and right positions).
So I cannot just alter coordinates - my image doesn't move if I do so.
So I guess I have to alter my constraints first. But I cannot even get them in code.
println(myPicture.constraints())
This returns an empty array.
How do I animate an object that has its constraints set in the storyboard?
You can change the constant values of the constraints. I would make outlets for them. Something like this:
#IBOutlet var topConstraint: NSLayoutConstraint!
You can connect these in Storyboard just like any other outlet. It's easiest if you find the constraint in the left-side view hierarchy menu and drag there to make the connections. Then in your code:
topConstraint.constant = 50 // or whatever you want to set it to
That should do it. If you want to animate the movement, you could put it an an animation block. If the change isn't happening when you do this, try calling this afterwards:
UIView.animateWithDuration(0.2,
animations: { () -> Void in
self.topConstraint.constant = 50
self.topConstraint.layoutIfNeeded()
},
completion: nil)

Resources