Haptic feedback during animation - mimic UIPickerView - ios

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... :)

Related

How to do interactive transition + ending animation?

Normally, we can do an interactive transitioning with animateTransition of UIViewControllerAnimatedTransitioning and updating progress via UIPercentDrivenInteractiveTransition.
Question:
How to have the interactive transitioning at first, then as we pass a certain threshold, perform a different ending animation?
What I want to achieve here is something like dismissing App store's Today card (https://gph.is/2qgcGHd). We can interactively shrink the card by panning a left edge of the screen. Then when it reaches the point, the card animates back to home page without any interactivity. It seems like a combination of interactive + animate transition to me.
What I've tried:
I tried doing this in UIView.animateWithKeyFrames by dividing into two parts of animation with 0.5 relative times for each. Then as the progress reach 0.5, I call finish() (of UIPercentDrivenInteractiveTransition) to have the second animation performing. It has some glitches there and it's like a hack. Want to know if there's a better way to do this.
In the end, I use UIView.animateKeyFramesand dividing the interactive transition into two-part animation (as explained in the question):
let progressUntilDismissing = 0.4
UIView.animateKeyframes(withDuration: 0.5, delay: 0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0.0,
relativeDuration: progressUntilDismissing,
animations: {
// interactive dismissing animation...
})
UIView.addKeyframe(withRelativeStartTime: progressUntilDismissing,
relativeDuration: (1 - progressUntilDismissing),
animations: {
// closing dismissing animation...
})
}) { (finished) in
//...
}
Then in the pan gesture recognizer, I calculate the pan progress and determine if it passes progressUntilDismissing or not.
If yes, call finish() on UIPercentDrivenInteractiveTransition subclass, it will animate the closing dismissing animation automatically.
In case anyone is curious, this is what I'm playing with:
AppStoreTodayInteractiveTransition

Building Card-Highlighting-Animation as in the App-Store

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)

Can I force an element to finish it's animation immediately?

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.

How to stop/cancel a UIProgressView progress animation?

I have a UIProgressView which must be be updated while the user holds a button. For a smooth animation I opted to use UIView.animateWithDuration instead of a Timer to update the progress.
What I expect to achieve is: the progress increases while the user holds a button, reaching 100% in 30 seconds (and then stops). If the user releases the button before the time ends, the progress bar just stops in the current progress. If the user then holds the button again, the progress resets to zero and the same process stars all over again.
Problem is I can't cancel the animation once the user releases the button. I tried to use progressView.layer.removeAllAnimation() but didn't work too.
Bellow is my code:
#IBAction func holdValueChanged(_ sender: CustomButton) {
if sender.isPressed {
progressView.progress = 1.0
UIView.animate(withDuration: 30.0, delay: 0.0, options: UIViewAnimationOptions.curveLinear, animations: {
self.progressView.layoutIfNeeded()
}, completion: { (finished) in
print("Ended progress animation")
})
}
else {
// stop progress animation
progressView.layer.removeAllAnimations()
}
}
Actually this is because there is no guarantee that the layer, that is being animated is progressView.layer. That's why progressView.layer.removeAllAnimations() will not work. Try this approach:
for (CALayer *layer in self.progressBar.layer.sublayers) {
[layer removeAllAnimations];
}
Try creating a second (short) animation from the current state to the desired stop point, e.g., something like this (untested):
UIView.animate(withDuration: 0.1, delay: 0,
options: [ .beginFromCurrentState, .allowUserInteraction ],
animations: {
self.progressView.progress = 1.0; // or whatever state it stopped at
}, completion:nil);
Starting a new animation should cancel any existing ones, and .beginFromCurrentState will prevent it from jumping to the end (in case you want to end at some other point). As a bonus, the visual effect of a short animation may also be nicer than just cancelling.

Difficulty allowing user interaction during UIView animation

I'm struggling to figure out how to allow user interaction with a view as it's being animated.
Here's the situation: I have a UIView cardView which holds card subviews. The cards are draggable tiles, similar to how the cards in Tinder are draggable/swipeable.
I am trying to fade out the card using animateWithDuration by animating to cardView.alpha = 0. Logically, this will also fade out all of the subviews (card objects). In this specific case, I am only targeting one card subview. However, during the animation, I am unable to drag/interact with the card.
Here is the code I'm using:
UIView.animateWithDuration(
duration,
delay: 0,
options: UIViewAnimationOptions.AllowUserInteraction,
animations: {self.cardView.alpha = 0}
) {
_ in
println("Card faded out")
card.removeFromSuperview()
}
Why doesn't this work? Any help will be appreciated. Thank you!!
I think you can find the answer in this previous post.
The interesting bit of the post is:
UIView's block animation by default blocks user interaction, and to get around it you need to pass UIViewAnimationOptionAllowUserInteraction as one of the options.
I fixed this problem by setting alpha to 0.1 instead of 0.0. I'm not sure if that will work in your case, but it shows that the event handling code thought that the view was not visible and disabled interaction even with the UIViewAnimationOptionAllowUserInteraction flag set. Oddly, setting the alpha to 0.01 did not work, so there is a threshold of visibility you have to stay above.
Swift 5
UIView.animateKeyframes(withDuration: 0.5, delay: 0, options: [.repeat, .autoreverse, .allowUserInteraction], animations: {
self.customButton.backgroundColor = .none
}, completion: nil)
The issue is with the Alpha value of 0. Alpha values of a certain proximity to Zero will remove the view from the view responder hierarchy. The fix here is to make the alpha setting to this:
self.cardView.alpha = 0.011
The view will still be invisible but not removed from the responder chain. From my testing the minimum amount is the following:
extension CGFloat {
static let minAlphaForTouchInput: CGFloat = 0.010000001
}

Resources