UIView transform, autolayout and anchor point - ios

I'm using ios 9 sdk. I created this view hierarchy setup with autoloayout:
----- Top
| 30
----- CustomView (A)
|
|
-----
| 0
----- Bot
I'm trying to create a second CustomView (call it B) behind the first (call it A), scaled by 80% in both directions, and above A by 5 points. So only the top of B is visible.
This should be easy, but it is not. Applying a -5 points translation and a .8 scaling on B moves B down instead of up, because autolayout seems to use the anchor point (set to middle by default) to position B: it seems it detects that a scaling has been applied and recenters B vertically - moving it down.
Changing the Anchor point to (x=0.5,y=1) moves A and B up by half their size, i don't understand why. So it does not fix the problem.
Any idea ?
Edit: some code
var card = new CardView();
InsertSubview(card, 0);
card.TranslatesAutoresizingMaskIntoConstraints = false;
this.AddConstraints(
card.AtTopOf(this, 30),
card.WithSameWidth(this).WithMultiplier(.95f),
card.WithSameCenterX(this),
card.AtBottomOf(this)
);
var transform = CGAffineTransform.MakeTranslation(0, -10);
transform.Scale(.8f,.8f);
Transform = transform;
Result: card is not at 20pts from top, it is at about 50pts, beside its original position of 30pt.

You're adding the wrong constraints. After some experimentation I got the results you want. The key seems to be to pin the center Y of the background view to the top of the foreground view, this then offsets the vertical shift given when changing the anchor point.
The following code in a playground gives the results you want:
import UIKit
import XCPlayground
let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 400, height:400)))
view.backgroundColor = UIColor.whiteColor()
XCPlaygroundPage.currentPage.liveView = view
let a = UIView()
a.translatesAutoresizingMaskIntoConstraints = false
a.backgroundColor = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.5)
view.addSubview(a)
a.topAnchor.constraintEqualToAnchor(view.topAnchor, constant: 30).active = true
a.widthAnchor.constraintEqualToAnchor(view.widthAnchor).active = true
a.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor).active = true
let b = UIView()
b.translatesAutoresizingMaskIntoConstraints = false
b.backgroundColor = UIColor(red: 0, green: 1, blue: 0, alpha: 1)
b.layer.anchorPoint = CGPoint(x: 0.5, y: 0)
view.addSubview(b)
view.sendSubviewToBack(b)
NSLayoutConstraint.activateConstraints([
b.centerYAnchor.constraintEqualToAnchor(a.topAnchor),
b.widthAnchor.constraintEqualToAnchor(a.widthAnchor),
b.heightAnchor.constraintEqualToAnchor(a.heightAnchor),
b.centerXAnchor.constraintEqualToAnchor(a.centerXAnchor)
])
let translate = CGAffineTransformMakeTranslation(0.0, -10)
let scale = CGAffineTransformMakeScale(0.8, 0.8)
b.transform = CGAffineTransformConcat(translate, scale)
Results:

The two possible approaches I see are:
- scale first, then add it to the view hierarchy
or
- change the anchorpoint of view B before you add it to the view hierarchy. Then you can transform it the way you want.

Related

iOS - Exchanging position of two UIViews inside Animation block

I've been having a strong headache with this problem, because I know for a fact should be a simple solution but I can't find the right answer.
The idea is that I have two UIViews (let's call them A and B). All I want to do is to swap their positions on button press, by the A view going down to B position, and B view going up to A position.
Here's what I'm trying to achieve.
I'm trying to achieve that by just swapping the .center of the elements that I need (as A and B are both UIViews that cointains a bunch of elements inside them). This is inside an animation block - UIView.animate
However, what is currently happening is that the animation is performing in exactly the opposite way: The A view would go up the screen, then re-appear in the bottom side of the screen and end up in the desired position (B initial position). Same thing with the B view, it's currently going all the way to the bottom of the screen, then re-appear at the top, and finish the animation in the desired position (A initial position).
This is what is currently happening.
This is my code so far (note that ownAccountTransferView is defined in another file, and below code is placed in my UIViewController that stores that view). I call this function on button press, after swapping the the data (like labels and such) between the two cards.
fileprivate func performSwitchAnimation() {
// NOTE: ownAccountTransferView is a reference to my view, think of it like self.view
let fromAccountCardCenter =
self.ownAccountTransferView.fromAccountCardView.center
let fromAccountTypeLabelCenter =
self.ownAccountTransferView.fromAccountTypeLabel.center
let fromAccountTypeViewCenter =
self.ownAccountTransferView.fromAccountTypeView.center
let fromAccountBalanceLabelCenter =
self.ownAccountTransferView.fromAccountBalanceLabel.center
let fromAccountNameLabelCenter =
self.ownAccountTransferView.fromAccountNameLabel.center
let fromAccountChevronCenter =
self.ownAccountTransferView.fromAccountChevronView.center
let toAccountCardCenter =
self.ownAccountTransferView.toAccountCardView.center
let toAccountTypeLabelCenter =
self.ownAccountTransferView.toAccountTypeLabel.center
let toAccountTypeViewCenter =
self.ownAccountTransferView.toAccountTypeView.center
let toAccountBalanceLabelCenter =
self.ownAccountTransferView.toAccountBalanceLabel.center
let toAccountNameLabelCenter =
self.ownAccountTransferView.toAccountNameLabel.center
let toAccountChevronCenter =
self.ownAccountTransferView.toAccountChevronView.center
UIView.animate(withDuration: 1, delay: 0, options: []) {
self.ownAccountTransferView.switchAccountsButton.isUserInteractionEnabled = false
self.ownAccountTransferView.fromAccountCardView.center = toAccountCardCenter
self.ownAccountTransferView.fromAccountTypeLabel.center = toAccountTypeLabelCenter
self.ownAccountTransferView.fromAccountTypeView.center = toAccountTypeViewCenter
self.ownAccountTransferView.fromAccountBalanceLabel.center = toAccountBalanceLabelCenter
self.ownAccountTransferView.fromAccountNameLabel.center = toAccountNameLabelCenter
self.ownAccountTransferView.fromAccountChevronView.center = toAccountChevronCenter
self.ownAccountTransferView.toAccountCardView.center = fromAccountCardCenter
self.ownAccountTransferView.toAccountTypeLabel.center = fromAccountTypeLabelCenter
self.ownAccountTransferView.toAccountTypeView.center = fromAccountTypeViewCenter
self.ownAccountTransferView.toAccountBalanceLabel.center = fromAccountBalanceLabelCenter
self.ownAccountTransferView.toAccountNameLabel.center = fromAccountNameLabelCenter
self.ownAccountTransferView.toAccountChevronView.center = fromAccountChevronCenter
} completion: { isDone in
if isDone {
self.ownAccountTransferView.switchAccountsButton.isUserInteractionEnabled = true
}
}
}
So, how can I make the animation work the way I want - A view going to the bottom and B view going to the top?
Thanks in advance.
UPDATE: I fixed it by ussing transform. Tried messing around with animating constraints A LOT, tried every possible interaction between top/bottom constraints, but it would either not do anything at all or just bug my view and send it to hell. I even was insisting with animating constraints in support calls with my coworkers. And so were they.
However, after googling despair measures, I ended up using viewController.myView.myLabel.transform = CGAffineTransform(translationX: 0, y: 236).
Of course, you can reduce this to: view.transform = CGAffineTransform(translationX: 0, y: 236).
The value is arbitrary, and I'm not sure if it can some day break or cause undesired behaviour. I tested it on iPhone 8 and iPhone 13, and in both the animations performs just ok (A view goes down, B goes up, click switch and do the opposite, etc...).
Side note: If you gonna use this, you need to NOT copy/paste the values when you need your views to "come back" to where they were at the beginning. I used 236 and -236 for A and B respectively, but to restore them I need to use -2 and 2 or it super bugs out. Idk why tho.
But it works!
Thanks all
Enclose both the views in a parent view and do the following:
if let firstIndex = view.subviews.firstIndex(of: view1), let secondIndex = view.subviews.firstIndex(of: view2) {
let firstViewFrame = view1.frame
UIView.animate(withDuration: 1, animations: {
self.view1.frame = self.view2.frame
self.view2.frame = firstViewFrame
self.view.exchangeSubview(at: firstIndex, withSubviewAt: secondIndex)
})
}
Check out the answer here.
EDIT: I was able to achieve the said animation using Autolayout constraints. Basically what I did was have fixed constraints for two views initially. And have two sets of changing constraints - startConstraints and endConstraints. Simply activating and deactivating the constraints did the job. Check out the demo. Here's my code:
class ViewController: UIViewController {
var view1: UIView = {
let v = UIView()
v.backgroundColor = .red
return v
}()
var view2: UIView = {
let v = UIView()
v.backgroundColor = .blue
return v
}()
var button: UIButton = {
let b = UIButton()
b.setTitle("Animate", for: .normal)
b.setTitleColor(.black, for: .normal)
return b
}()
var fixedConstraints: [NSLayoutConstraint]!
var startConstraints: [NSLayoutConstraint]!
var endConstraints: [NSLayoutConstraint]!
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(view1)
view.addSubview(view2)
view.addSubview(button)
view1.translatesAutoresizingMaskIntoConstraints = false
view2.translatesAutoresizingMaskIntoConstraints = false
button.translatesAutoresizingMaskIntoConstraints = false
fixedConstraints = [
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
view1.centerXAnchor.constraint(equalTo: view.centerXAnchor),
view2.centerXAnchor.constraint(equalTo: view.centerXAnchor),
view1.heightAnchor.constraint(equalToConstant: 100),
view1.widthAnchor.constraint(equalToConstant: 100),
view2.heightAnchor.constraint(equalToConstant: 100),
view2.widthAnchor.constraint(equalToConstant: 100),
]
startConstraints = [
view1.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0),
view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: view2.bottomAnchor),
]
startConstraints.append(contentsOf: fixedConstraints)
NSLayoutConstraint.activate(startConstraints)
button.addTarget(self, action: #selector(animateSwitchingOfViews), for: .touchUpInside)
}
#objc
private func animateSwitchingOfViews(){
endConstraints = [
view2.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0),
view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo:view1.bottomAnchor)
]
endConstraints.append(contentsOf: fixedConstraints)
UIView.animate(withDuration: 2, animations: {
NSLayoutConstraint.deactivate(self.startConstraints)
NSLayoutConstraint.activate(self.endConstraints)
self.view.layoutIfNeeded()
})
}
}
UPDATE: I fixed it by ussing transform. Tried messing around with animating constraints A LOT, tried every possible interaction between top/bottom constraints, but it would either not do anything at all or just bug my view and send it to hell. I even was insisting with animating constraints in support calls with my coworkers. And so were they. However, after googling despair measures, I ended up using viewController.myView.myLabel.transform = CGAffineTransform(translationX: 0, y: 236).
Of course, you can reduce this to: view.transform = CGAffineTransform(translationX: 0, y: 236). The value is arbitrary, and I'm not sure if it can some day break or cause undesired behaviour. I tested it on iPhone 8 and iPhone 13, and in both the animations performs just ok (A view goes down, B goes up, click switch and do the opposite, etc...).
Side note: If you gonna use this, you need to NOT copy/paste the values when you need your views to "come back" to where they were at the beginning. I used 236 and -236 for A and B respectively, but to restore them I need to use -2 and 2 or it super bugs out. Idk why tho. But it works! Thanks all
Assuming you want to "swap the centers" and you don't want to modify the constraints / constants, you can use transforms.
Instead of hard-coding values, though, you can do it like this:
// if the views are in their "original" positions
if ownAccountTransferView.transform == .identity {
let xOffset = fromAccountTypeView.center.x - ownAccountTransferView.center.x
let yOffset = fromAccountTypeView.center.y - ownAccountTransferView.center.y
UIView.animate(withDuration: 1.0, animations: {
self.ownAccountTransferView.transform = CGAffineTransform(translationX: xOffset, y: yOffset)
self.fromAccountTypeView.transform = CGAffineTransform(translationX: -xOffset, y: -yOffset)
})
} else {
UIView.animate(withDuration: 1.0, animations: {
self.ownAccountTransferView.transform = .identity
self.fromAccountTypeView.transform = .identity
})
}

Is it possible to make 2 subviews with same z index?

I am looking to create a water fill effect into a shape and am using WaveViewAnimation project.
I want to allow the user to press on different buttons to create different colour waves to fill the shape. And when user presses on second colour while the first colour is
waveRed = WaveAnimationView(frame: CGRect(origin: .zero, size: lapView.bounds.size), color: UIColor.red.withAlphaComponent(0.5))
lapView.addSubview(waveRed)
waveBlue = WaveAnimationView(frame: CGRect(origin: .zero, size: lapView.bounds.size), color: UIColor.green.withAlphaComponent(0.5))
lapView.addSubview(waveBlue)
waveRed.layer.zPosition = 1
waveBlue.layer.zPosition = 1
waveRed.startAnimation()
waveBlue.startAnimation()
I want the output something like the combination of 2 colours blended, something like the If red wave is filled and green was is pressed. The wave overlap area needs to be in yellow colour.
Could someone please guide me/advice me how to achieve this.
You may be able to get your desired result by using the .compositingFilter property of CALayer.
There are various "blend" filters... this one:
topLayer.compositingFilter = "screenBlendMode"
is probably what you want to play with.
Apple's docs on it are vague -
Discussion
This results in colors that are at least as light as either of the two contributing sample colors. The formula used to create this filter is described in the PDF specification, which is available online from the Adobe Developer Center. See PDF Reference and Adobe Extensions to the PDF Specification.
Of course, the link doesn't help... but I did find this from searching:
Screen
Multiplies the complements of the backdrop and source color values, then complements the result. The result color is always at least as light as either of the two constituent colors. Screening any color with white produces white; screening with black leaves the original color unchanged. The effect is similar to projecting multiple photographic slides simultaneously onto a single screen.
So, if we have a red circle overlapping a green circle...
We get this with NO filter (or the default "normalBlendMode"):
If we give the red circle layer .compositingFilter = "screenBlendMode" we get this:
As we see, "screen blending" 1, 0, 0, 1 (red) with 0, 1, 0, 1 (green) gives us 1, 1, 0, 1 -- yellow.
Notice that we lose the "top" of the red circle. That's because any color screen-blended with white results in white.
If we add another "bottom" layer matching the top red layer, like this:
We'll get this result:
because, as we'd expect, any color screen-blended with itself results in itself.
Here's the sample code I used to produce these images that you can play with:
class ViewController: UIViewController {
let theView: UIView = UIView()
let topLayer = CAShapeLayer()
let bottomLayer = CAShapeLayer()
let middleLayer = CAShapeLayer()
let infoLabel: UILabel = {
let v = UILabel()
v.numberOfLines = 0
v.textAlignment = .center
v.font = .systemFont(ofSize: 18.0, weight: .light)
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
theView.backgroundColor = .white
infoLabel.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
theView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(theView)
infoLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(infoLabel)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// add the view 20-points inset from the sides
// height 1.2 times the width
// centered vertically
theView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
theView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
theView.heightAnchor.constraint(equalTo: theView.widthAnchor, multiplier: 1.2),
theView.centerYAnchor.constraint(equalTo: g.centerYAnchor, constant: -40.0),
infoLabel.topAnchor.constraint(equalTo: theView.bottomAnchor, constant: 20.0),
infoLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
infoLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
infoLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 40.0),
])
// bottom layer is red
bottomLayer.fillColor = UIColor.red.cgColor
// middle layer is green
middleLayer.fillColor = UIColor.green.cgColor
// top layer same color as bottom layer
topLayer.fillColor = UIColor.red.cgColor
theView.layer.addSublayer(bottomLayer)
theView.layer.addSublayer(middleLayer)
theView.layer.addSublayer(topLayer)
nextStep()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// set layer shapes to overlapping ovals
var b = theView.bounds.insetBy(dx: 20.0, dy: 20.0)
b.size.height *= 0.75
var pth = UIBezierPath(ovalIn: b)
// bottom and top layers get the same path
// so they exactly overlay each other
bottomLayer.path = pth.cgPath
topLayer.path = pth.cgPath
// shift the oval rect down for the "middle" layer
b.origin.y += theView.bounds.height * 0.25 - 10.0
pth = UIBezierPath(ovalIn: b)
middleLayer.path = pth.cgPath
}
var counter: Int = -1
func nextStep() {
counter += 1
switch counter % 3 {
case 1:
// screen blend
// hide bottom layer
topLayer.compositingFilter = "screenBlendMode"
bottomLayer.opacity = 0
infoLabel.text = "Screen Blend - with bottom layer hidden"
case 2:
// screen blend
// show bottom layer
topLayer.compositingFilter = "screenBlendMode"
bottomLayer.opacity = 1
infoLabel.text = "Screen Blend - with bottom layer visible"
default:
// normal blend
// bottom layer opacity doesn't matter, since it will be covered by top layer
topLayer.compositingFilter = "normalBlendMode"
infoLabel.text = "Default - no Blend Effect"
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
nextStep()
}
}
Each tap anywhere on the screen will step through the different blend-modes and "bottom layer" visibility.

How can I add the safe area to the view height?

I am creating a top "banner" and I want it to cover the safe area and some more.
I can't seem to find how to cover the Safe area.
The first picture is the desired effect, the second is the effect on notched iPhones.
Desired Effect
What's Happening
How can I add the safe area to the desired value of height?
Code:
let shadowView = UIView( frame: CGRect( x: -10, y: -20, width: (view.frame.width+20), height: 90 ) )
view.addSubview( shadowView )
shadowView.backgroundColor = .clear
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOpacity = 0.3
shadowView.layer.shadowOffset = CGSize(width: 5, height: 3)
shadowView.layer.shadowRadius = 4.0
let titleView = UIView( frame: CGRect( x: 0, y: 0, width: ( shadowView.frame.width ), height: 90 ) )
shadowView.addSubview( titleView )
titleView.backgroundColor = .systemGreen
titleView.layer.cornerRadius = 45
titleView.layer.masksToBounds = true
The problem is here:
let shadowView = UIView( frame: CGRect( x: -10, y: -20, width: (view.frame.width+20), height: 90 ) )
You are hardcoding the frame - don't do this! Well, a height of 90 is fine. But the x: -10, y: -20, width: (view.frame.width+20) is terrible. Not all phones are the same size.
Technically, you could calculate the safe area inset height as NoeOnJupiter commented, but this is still pretty bad. What happens when the user rotates their device, and the notch moves? It sounds like a lot of work.
What you want is Auto Layout and the Safe Area. With Auto Layout, simply define some constraints, then watch your UIViews look great on all screen sizes. Then, the Safe Area defines what parts of the screen are "safe," meaning "not covered by notches or rounded screen corners."
So, you can pin shadowView to the edges of the screen (beyond the notch/safe area), and add the .systemGreen background color. Then, make titleView 90 points high, pinning it vertically to shadowView. Just note how titleView.topAnchor is pinned to shadowView.safeAreaLayoutGuide.topAnchor — this makes it stay clear of the notch.
let shadowView = UIView() /// green background with shadow and corner radius
shadowView.translatesAutoresizingMaskIntoConstraints = false
shadowView.backgroundColor = .systemGreen /// put the background color here instead of on `titleView`, because this view stretches beyond the notch
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOpacity = 0.3
shadowView.layer.shadowOffset = CGSize(width: 5, height: 3)
shadowView.layer.shadowRadius = 4.0
shadowView.layer.cornerRadius = 45
shadowView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner] /// only round the bottom corners
view.addSubview(shadowView)
let titleView = UIView() /// container for title label.
titleView.translatesAutoresizingMaskIntoConstraints = false
shadowView.addSubview(titleView)
let titleLabel = UILabel() /// the title label itself
titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleView.addSubview(titleLabel)
titleLabel.text = "Catalogues"
titleLabel.font = UIFont.systemFont(ofSize: 36, weight: .medium)
NSLayoutConstraint.activate([
/// constrain `shadowView`
shadowView.topAnchor.constraint(equalTo: view.topAnchor),
shadowView.rightAnchor.constraint(equalTo: view.rightAnchor),
shadowView.leftAnchor.constraint(equalTo: view.leftAnchor),
/// constrain `titleView`
titleView.topAnchor.constraint(equalTo: shadowView.safeAreaLayoutGuide.topAnchor), /// most important part!
titleView.heightAnchor.constraint(equalToConstant: 90), /// will also stretch `shadowView` vertically
titleView.rightAnchor.constraint(equalTo: shadowView.rightAnchor),
titleView.leftAnchor.constraint(equalTo: shadowView.leftAnchor),
titleView.bottomAnchor.constraint(equalTo: shadowView.bottomAnchor),
/// constrain `titleLabel`
titleLabel.centerXAnchor.constraint(equalTo: titleView.centerXAnchor),
titleLabel.centerYAnchor.constraint(equalTo: titleView.centerYAnchor)
])
Result:
iPhone 8
iPhone 12
For further reading, I have a blog post on this topic.

UIView animating to wrong position after being scaled while using constraints

So I'm trying to animate an UILabel to match another UILabel size and position. At first I was only working with the animation of the position part using constraints like this:
private lazy var constraintsForStateA: [NSLayoutConstraint] = [
firstLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
firstLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor)
]
private lazy var constraintsForStateB: [NSLayoutConstraint] = [
firstLabel.leadingAnchor.constraint(equalTo: secondLabel.leadingAnchor),
firstLabel.topAnchor.constraint(equalTo: secondLabel.topAnchor)
]
So I basically have two arrays with these constraints above (constraintsForStateA and constraintsForStateB), and when I need to animate the position of my firstLabel I just do something like:
// From state A to state B
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut) {
NSLayoutConstraint.deactivate(self._constraintsUnselected)
NSLayoutConstraint.activate(self._constraintsSelected)
self.layoutIfNeeded()
} completion: { _ in
self.firstLabel.alpha = 0
self.secondLabel.alpha = 1
}
}
So far this has been working exactly as I was expecting, but the part with the size is giving me some trouble, since I can't apply the same strategy to the text size I'm using a transform like this:
let scaleX = secondLabel.bounds.width / firstLabel.bounds.width
let scaleY = secondLabel.bounds.height / firstLabel.bounds.height
and then in the same .animate method above from state A to state B I do this:
firstLabel.transform = CGAffineTransform(scaleX: scaleX, y: scaleY)
and from state B to state A:
firstLabel.transform = .identity
Which works as I want but the problem I'm facing is that the position is no longer in the expected place. I think this is happening because the transformation is happening having in consideration the anchorPoint at the center of the label. I've tried sort of blindly making it work changing the anchorPoint to be at (0,0), but it's not working either. I've lost 2 days on this already, any help or guidance is welcome!
When you animate, it would be much simpler if you just forget about the constraints and just deal with frames:
NSLayoutConstraint.deactivate(self.constraintsForStateA)
firstLabel.translatesAutoresizingMaskIntoConstraints = true
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut) {
firstLabel.frame = secondLabel.frame
}
Then to transition from state B to A:
firstLabel.translatesAutoresizingMaskIntoConstraints = false
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut) {
NSLayoutConstraint.activate(self.constraintsForStateA)
self.layoutIfNeeded()
}

"Launch" and drop a UIView

Above picture illustrates what I want:
The red circle is the starting point of a UIView
I want to launch it upwards (Y position) with a change in the X position
I want to make it drop within boundaries.
What I tried:
For the launch I can make use of the UIView.animate function. For the drop I can use the UIDynamicAnimator. However there are some problems:
-In the UIView.animate I cannot 'curve' the animation, only a straight line. I can use this answer here to draw a curve line: https://stackoverflow.com/a/43813458/7715250
-Combining both functions is not working. After the UIView.animate is done, the UIView just drop straight downwards.
The code for UIDynamicAnimator:
var animator: UIDynamicAnimator!
//view did appear
let view2 = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view2.center = self.view.center
view2.backgroundColor = .blue
self.view.addSubview(view2)
animator = UIDynamicAnimator(referenceView: self.view)
let gravity = UIGravityBehavior(items: [view2])
animator.addBehavior(gravity)
let collosion = UICollisionBehavior(items: [view2])
collosion.translatesReferenceBoundsIntoBoundary = true
let dynamic = UIDynamicItemBehavior(items: [view2])
dynamic.elasticity = 0.7
animator.addBehavior(collosion)
animator.addBehavior(dynamic)
That will drop the UIView with a nice bounce effect. But how to launch the UIView? How to change the X and Y position and remain the added behaviours?
I think what you need is UIPushBehavior. Add this extra behavior.
let push = UIPushBehavior(items: [view2], mode: UIPushBehaviorMode.instantaneous)
push.setAngle(CGFloat(-M_PI/2 - 0.1), magnitude: 8.0) // Adjust angle and magnitude
animator.addBehavior(push)

Resources