I have code that animate a view (change constraints)
UIView .animate(withDuration: TimeInterval(duration), delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseInOut, animations: {
self.topConstraint!.constant = self.yPositioOfOpenPanel
self.superview?.layoutIfNeeded()
}
The strange behaviour is that on iPhone X layoutSubviews is called but on other simulators and real devices is not. (And I think it should be)
Same behaviour is when I change constraint manually
if let constraint = topConstraint {
constraint.constant = self.superview!.frame.height - recogniser.location(in: self.superview).y + yPositionOfBeginTouchInPanel
}
EDIT:
As suggested I am adding some new information.
Animations works very well, everything is animating as it should.
The problem is that I would like to trigger some new events, while animation is in place. (I would use topConstraint.constant to calculate something while animations is in place, or when I change topConstraint manually) That's why I would like to set some trigger on method layoutSubviews. This method should be triggered when animations changes subviews.
I think that this works prior iOS 11 (but I can't be 100% sure, because this functionality is new in my program, but as I can remember I use this in other programs and it worked). It is still working for iPhone X (Simulator) but not for other simulators or real devices (obviously the real iPhone X is not out yet).
I think you should slightly change the animation code:
// update constraints before you call animate
self.topConstraint!.constant = self.yPositioOfOpenPanel
// tell superview that the layout will need to be updated
self.superview?.setNeedsLayout()
UIView.animate(withDuration: TimeInterval(duration), delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseInOut, animations: {
// here you tell it to update the layout according to new constraints
self.superview?.layoutIfNeeded()
}
It turns out that I need to do is:
UIView .animate(withDuration: TimeInterval(duration), delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseInOut, animations: {
self.topConstraint!.constant = self.originalConstraintConstant
self.superview?.layoutIfNeeded()
self.layoutIfNeeded()
}
This triggered layoutIfNeeded inside this view.
But I still don't know why this works on all iPhones, and other works only on iPhone X. It should be a bug inside iOS or Xcode.
Related
I created the circle with deck of cards, that user can spin to select one. After the paning ends, it snaps to designated angle with a nice deceleration animation. In future there will some kind of indication that the card at 45 degrees is the selected one.
I would like to indication that the selection changed with haptic feedback, just like in UIPickerView. For now I am trying to add haptic feedback to the deceleration animation. My idea was to make feedback generator and call .selectionChanged() in animation as many times as number of cards that were skipped. But for now I decided to just simply call it 3 times. Unfortunately, none of my ideas work - even creating a separate UIViewPropertyAnimator does not work. I suppose I should only put animatable properties in animation closure. The animator itself works right - the deceleration animation works.
animator.addAnimations {
UIView.animateKeyframes(withDuration: 5.0, delay: 0.0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 1.0/3.0, relativeDuration: 0.0, animations: {
self.selectionGenerator.selectionChanged()
})
UIView.addKeyframe(withRelativeStartTime: 2.0/3.0, relativeDuration: 0.0, animations: {
self.selectionGenerator.selectionChanged()
})
UIView.addKeyframe(withRelativeStartTime: 3.0/3.0, relativeDuration: 0.0, animations: {
self.selectionGenerator.selectionChanged()
})
})
}
animator.startAnimation()
How can I mimic the haptic feedback behaviour of for example DatePicker , which vibrates when selection changes?
you can add haptic feedback using
HAPTICA https://github.com/efremidze/Haptica
and add this feedback in animation loop i hope it will work... :)
I am animating my constraints using UIView.animate. drawerHeight is a constraint variable.
Here is the code:
drawerHeight?.constant = newHeight
UIView.animate(withDuration: 0.35, delay: 0, usingSpringWithDamping: 0.65, initialSpringVelocity: 2, options: .allowUserInteraction, animations: {
self.superview?.layoutIfNeeded()
})
Here is a video of what the issue looks like.
Looking at the bottom of the video, the bottom edge appears to lag behind. Why do these constraints seem to lag when animated?
This is a self-answered question. This post is to help people in the future discover what they did wrong, and how to quickly fix it.
The issue was that the constraint was attached to a view in a deeper hierarchy than before. When using layoutIfNeeded(), it should be called on the superview of the view containing the constraints.
In my situation, I put the view with the drawerHeight constraint within another UIView, which made it deeper in the hierarchy.
I fixed the issue by doing
self.superview?.superview?.layoutIfNeeded() ,
instead of
self.superview?.layoutIfNeeded() .
In the AppStore (iOS 11) on the left "today"-tab, there are several card views. If you highlight one, it shrinks a little bit. How can I rebuild this animation?
I guess changing the constraints of the card view during an animation will not be what we need, since you would also have to adapt all the other constraints (e.g. of the labels) to match the new size.
Is there an easier way to shrink a view with all its subviews?
Also, when you click the card, it increases to fullscreen with an animation. Do you have any ideas how to achieve this effect?
For tapping and shrinking card, I also wrote about this in detail. Here's the idea:
Use a scaling transform to animate shrinking (like in accepted answer)
Disable delaysContentTouch to make it shrink faster upon touch
(scrollView.delaysContentTouch = false)
Always allow users to scroll using .allowUserInteraction animation option:
UIView.animate(withDuration: 1.0,
delay: 0.0,
options: [.allowUserInteraction],
animations: ...,
completion: ...)
(By default when you use transform, it disables the interaction a bit. User can't scroll successively without doing that)
About the expanding to full screen with animation, I have tried to replicate it with the native's transition APIs which you can check out here: https://github.com/aunnnn/AppStoreiOS11InteractiveTransition
In short, I use UIViewControllerAnimatedTransitioning to do custom animation. Hide the original card and create a new dummy card view just for animation. Then setup AutoLayout constraints of that card, including 4 to each of the screen edges. Then animate those constraints to make it fill the screen.
After everything is done, hide that dummy view and show the destination detail page.
Note: The exact implementation detail is a bit different and involved.
You can get an easy scale animation using transform:
UIView.animate(withDuration: 0.2) {
view.transform = CGAffineTransform.identity.scaledBy(x: 0.9, y: 0.9)
}
As to the fullscreen animation, you want to check out some tutorials on how to create custom transition animations.
If you are interested in a more complete functionality you can use this library:
https://github.com/PaoloCuscela/Cards
this is also a good rebuild of that animation when you press a card:
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.8, options: .beginFromCurrentState, animations: {
self.transform = .init(scaleX: 0.95, y: 0.95)
}, completion: nil)
Try to animate the width of a UIView like this. But it does not do anything. Why? The commented part changes alpha of the view, and that works.
UIView.animate(withDuration: 1, delay: 0, options: [.curveEaseInOut], animations: {
self.constraintLookupWidth.constant = 300
self.viewLookup.setNeedsLayout()
//self.viewLookup.alpha = 0.2
}, completion: {complete in
self.constraintLookupWidth.constant = 200
self.viewLookup.setNeedsLayout()
//self.viewLookup.alpha = 1.0
})
I guess that you need to integrate the call with a layoutIfNeeded(), this ask auto layout to force the layout right now.
Here I have some code to show a UIView with a label as a notification.
self.not1cons.constant = 0
self.notificationLbl1.text = self.notification1
UIView.animate(withDuration: 2.5, delay: 0.3, options: .allowAnimatedContent, animations: {
self.view.layoutIfNeeded()
}, completion: { finsihed in
self.not1cons.constant = -100
UIView.animate(withDuration: 2.5, delay: 2.0, options: .allowAnimatedContent, animations: {
self.view.layoutIfNeeded()
}, completion: { finshed in
})
})
It start off-screen and descends in to view. It stays in place for a few seconds and returns to its original position off-screen. I need some code to make these chained animations happen instantly. Is this possible?
You could probably accomplish this by manipulating the CAAnimations the system generates behind the scenes, but that is fairly tricky business, and not a great idea since it relies on undocumented details of the current implemention, which is risky.
Instead I'd suggest reworking your animations to use the new-to-iOS 10
UIViewPropertyAnimator, which has support for pausing and resuming animations, as well as scrubbing them back and forth to arbitrary points.
I have a demo project on Gitub called UIViewPropertyAnimator-test that lets you scrub an animation back and forth using a slider. It is more complex than your need, but it should give you the idea.