Change root view controller using HERO transition - ios

I have this code....
UIView.transition(with: window!, duration: 0.3, options: **animation**, animations: {
window!.rootViewController = rootVC
}, completion: nil
Is it possible put Hero transition replacing "animation" var?

As an example:
let current = window.rootViewController!
let transition = HeroTransition()
// Collapse vertically in dismissal, over 1 second.
current.view.hero.modifiers = [.scale(x: 1, y: 0.1, z: 1), .duration(1), .fade]
// Rotate in, over 2 seconds.
replacement.view.hero.modifiers = [.rotate(z:1.6), .duration(2), .fade]
transition.transition(from: current, to: replacement, in: window) { (finished) in
window.rootViewController = replacement
}

Related

How to make UIView keyframe animation be linear from start to finish [duplicate]

I'm playing around with custom and interactive view controller transistion, with UIPercentDrivenInteractiveTransition. I'm building an app that present a card (other view controller) modally. I've made custom transistions with UIViewControllerAnimatedTransitioning that is animating the card view controller a lot like the standart model presentation style.
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
{...}
let fullScreenFrame = transitionContext.finalFrame(for: cardVC)
let offsetFrame = cardVC.view.frame.offsetBy(dx: 0, dy: cardVC.view.frame.height)
let finalFrame = presenting ? fullScreenFrame : offsetFrame
cardVC.view.frame = presenting ? offsetFrame : fullScreenFrame
containerView.addSubview(cardVC.view)
UIView.animateKeyframes(
withDuration: transitionDuration(using: transitionContext),
delay: 0,
options: UIViewKeyframeAnimationOptions.calculationModeLinear,
animations: {
UIView.addKeyframe(
withRelativeStartTime: 0,
relativeDuration: 1,
animations: { [weak self] in
guard let strongSelf = self else { return }
backgroundView.alpha = strongSelf.presenting ? 1 : 0
})
UIView.addKeyframe(
withRelativeStartTime: 0,
relativeDuration: 1,
animations: {
cardVC.view.frame = finalFrame
})
}, completion: { finished in
backgroundView.removeFromSuperview()
gradientView.alpha = 1
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
And then I use a pan gesture recognizer to interactively drive the dismiss animation:
func handleGesture(_ gestureRecognizer: UIScreenEdgePanGestureRecognizer) {
let translation = gestureRecognizer.translation(in: gestureRecognizer.view)
var progress = (translation.y / (UIScreen.main.bounds.height - 70))
progress = CGFloat(fminf(fmaxf(Float(progress), 0.0), 1.0))
switch gestureRecognizer.state {
case .began:
interactionInProgress = true
viewController.dismiss(animated: true, completion: nil)
case .changed:
shouldCompleteTransition = progress > 0.35
update(progress)
case .cancelled:
interactionInProgress = false
cancel()
case .ended:
interactionInProgress = false
if !shouldCompleteTransition {
cancel()
} else {
finish()
}
default:
print("Unsupported")
}
}
When I dismiss the presented view controller by dragging it down, it doesn't seem to move linearly with the gesture. It seems like the Interaction Controller is using some easeInEaseOut function. Maybe setting UIPercentDrivenInteractiveTransition's timingCurve to make the transition run linearly. Is that posible, or am I getting something wrong?
You yourself have given the animation a default ease in - ease out timing curve, by not setting the timing curve to anything different in your call to UIView.animateKeyframes. That applies even during interaction.
Note that setting the animation's options to UIViewKeyframeAnimationOptions.calculationModeLinear does not change the timing curve (in case that's what you thought you were accomplishing here). The way to add a linear timing curve to a linear calculation mode keyframe animation is like this:
var opts : UIViewKeyframeAnimationOptions = .calculationModeLinear
let opt2 : UIViewAnimationOptions = .curveLinear
opts.insert(UIViewKeyframeAnimationOptions(rawValue:opt2.rawValue))
// and now use `opts` as your `options`
I was having the same issue, was able to set a custom animation curve for animateKeyframes using UIView.setAnimationCurve like this:
UIView.animateKeyframes(withDuration: 4, delay: 0, options: [], animations: {
UIView.setAnimationCurve(.linear)
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.24, animations: {
// Animations...
})
// Other key frames...
})
In my case, It works with interruptibleAnimator(using:) by return UIViewPropertyAnimator

How to move UITableViewCell back and forth to show it can be swiped?

I see in some apps when you come to a screen with a tableview there's a short animation of the cell starting to be swiped, showing the red "swipe to delete" button (UIContextualAction button) and then it returns to normal. It is giving the user the hint: "These rows can be swiped."
Is there a way to achieve this effect? Maybe a way to programmatically start a row swipe then cancel it?
Swift Solution
Well, about 1.5 years later I finally came up with a solution.
Step 1 - Cell UI Setup
I set up my custom table view cell like this:
A and B represent the swipe action colors.
C is the UIView that I will animate side-to-side.
Step 2 - Add Animation to Cell
func animateSwipeHint() {
slideInFromRight()
}
private func slideInFromRight() {
UIView.animate(withDuration: 0.5, delay: 0.3, options: [.curveEaseOut], animations: {
self.cellBackgroundView.transform = CGAffineTransform(translationX: -self.swipeHintDistance, y: 0)
self.cellBackgroundView.layer.cornerRadius = 10
}) { (success) in
UIView.animate(withDuration: 0.2, delay: 0, options: [.curveLinear], animations: {
self.cellBackgroundView.transform = .identity
}, completion: { (success) in
// Slide from left if you have leading swipe actions
self.slideInFromLeft()
})
}
}
private func slideInFromLeft() {
UIView.animate(withDuration: 0.5, delay: 0, options: [.curveEaseOut], animations: {
self.cellBackgroundView.transform = CGAffineTransform(translationX: self.swipeHintDistance, y: 0)
}) { (success) in
UIView.animate(withDuration: 0.2, delay: 0, options: [.curveLinear], animations: {
self.cellBackgroundView.transform = .identity
})
}
}
Step 3 - Trigger the Animation
In the viewDidLoad of the view controller that has the table view, I have this code:
if self.tableView.visibleCells.count > 0 {
let cell = self.tableView.visibleCells[0] as! TableViewCell
cell.animateSwipeHint()
}
Example:
Video Solution
I created a video if you'd like a more in-depth walkthrough of this solution:
https://youtu.be/oAGoFd_GrxE
I have a piece of code that I saw long time ago to animate a view. Since our UITableViewCell is also a view, we can use it :) You just need to get your visible cell to animate, like so:
if let visibleCell = self.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? CustomCell {
print("Started animation...")
let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.duration = 0.6
animation.values = [-20.0, 20.0, -20.0, 20.0, -10.0, 10.0, -5.0, 5.0, 0.0 ]
visibleCell.layer.add(animation, forKey: "shake")
}
Let me know if this helps. Tested it.
EDIT:
Animating your UITableView to let the user see that they can swipe on a cell is pretty easy, try it like so:
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
self.tableView.setEditing(true, animated: true)
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
self.tableView.setEditing(false, animated: true)
}
}
HOWEVER, if you want to swipe programmatically your cell to show your custom row actions, (I've been researching this for an hour), you can only achieve this, as far as I know, by using method swizzling. See this SO answer: http://codejaxy.com/q/186524/ios-swift-uitableview-how-to-present-uitableviewrowactions-from-pressing-a-button

Animating a UIView's alpha in sequence with UIViewPropertyAnimator

I have a UIView that I want to reveal after 0.5 seconds, and hide again after 0.5 seconds, creating a simple animation. My code is as follows:
let animation = UIViewPropertyAnimator.init(duration: 0.5, curve: .linear) {
self.timerBackground.alpha = 1
let transition = UIViewPropertyAnimator.init(duration: 0.5, curve: .linear) {
self.timerBackground.alpha = 0
}
transition.startAnimation(afterDelay: 0.5)
}
animation.startAnimation()
When I test it out, nothing happens. I assume it's because they're both running at the same time, which would mean they cancel each other out, but isn't that what the "afterDelay" part should prevent?
If I run them separately, i.e. either fading from hidden to visible, or visible to hidden, it works, but when I try to run them in a sequence, it doesn't work.
My UIView is not opaque or hidden.
You can use Timer, and add appearing / hiding animations blocks on every timer tick to your UIViewPropertyAnimatorobject.
Here's a codebase:
#IBOutlet weak var timerBackground: UIImageView!
private var timer: Timer?
private var isShown = false
private var viewAnimator = UIViewPropertyAnimator.init(duration: 0.5, curve: .linear)
override func viewDidLoad() {
super.viewDidLoad()
viewAnimator.addAnimations {
self.timerBackground.alpha = 1
}
viewAnimator.startAnimation()
isShown = true
self.timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(self.startReversedAction), userInfo: nil, repeats: true)
}
func startReversedAction() {
// stop the previous animations block if it did not have time to finish its movement
viewAnimator.stopAnimation(true)
viewAnimator.addAnimations ({
self.timerBackground.alpha = self.isShown ? 0 : 1
})
viewAnimator.startAnimation()
isShown = !isShown
}
I've implemented the very similar behavior for dots jumping of iOS 10 Animations demo project.
Please, feel free to look at it to get more details.
Use UIView.animateKeyframes you'll structure your code nicely if you have complicated animations. If you'll use UIView animations nested within the completion blocks of others, it will probably result in ridiculous indentation levels and zero readability.
Here's an example:
/* Target frames to move our object to (and animate)
or it could be alpha property in your case... */
let newFrameOne = CGRect(x: 200, y: 50, width: button.bounds.size.width, height: button.bounds.size.height)
let newFrameTwo = CGRect(x: 300, y: 200, width: button.bounds.size.width, height: button.bounds.size.height)
UIView.animateKeyframes(withDuration: 2.0,
delay: 0.0,
options: .repeat,
animations: { _ in
/* First animation */
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.5, animations: { [weak self] in
self?.button.frame = newFrameOne
})
/* Second animation */
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5, animations: { [weak self] in
self?.button.frame = newFrameTwo
})
/* . . . */
}, completion: nil)
What worked for me, was using sequence of UIViewPropertyAnimators. Here is example of my code:
let animator1 = UIViewPropertyAnimator(duration:1, curve: .easeIn)
animator1.addAnimations {
smallCoin.transform = CGAffineTransform(scaleX: 4, y: 4)
smallCoin.center = center
}
let animator2 = UIViewPropertyAnimator(duration:1, curve: .easeIn)
animator2.addAnimations {
center.y -= 20
smallCoin.center = center
}
let animator3 = UIViewPropertyAnimator(duration:10, curve: .easeIn)
animator3.addAnimations {
smallCoin.alpha = 0
}
animator1.addCompletion { _ in
animator2.startAnimation()
}
animator2.addCompletion { _ in
animator3.startAnimation()
}
animator3.addCompletion ({ _ in
print("finished")
})
animator1.startAnimation()
You can even add afterdelay attribute to manage speed of animations.
animator3.startAnimation(afterDelay: 10)

User interaction disabled for parent viewController when removed from child ViewController

I am using below code to move from left to right.
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.rightNavController?.view.frame = CGRect(x: 0, y: 0, width: (appDelegate.window?.frame.size.width)!, height: (appDelegate.window?.frame.size.height)!)
self.removeNavigationController()
appDelegate.rightNavController = UINavigationController(rootViewController: leftmenuViewController)
appDelegate.rightNavController?.navigationBar.isHidden = true
baseController.addChildViewController(appDelegate.rightNavController!)
baseController.view.addSubview((appDelegate.rightNavController?.view)!)
UIView.animate(withDuration: 0.25, delay: 0.0, options: UIViewAnimationOptions(), animations: {
}, completion: { finished in
appDelegate.rightNavController?.view.frame = CGRect(x: 0, y: 0, width: 320, height: 568)
})
my view hierarchy in side menu is UIView->UiTableView.I have added tap gesture on UIView to dismiss side menu.
Below is the code used to dismiss:(tap gesture method)
self.willMove(toParentViewController: nil)
UIView.animate(withDuration: 0.5, delay: 0.0, options: UIViewAnimationOptions(), animations: {
self.view.frame = CGRect(x:-self.view.frame.size.width, y: 0 ,width : self.view.frame.size.width, height :self.view.frame.size.height)
}, completion: { finished in
self.removeFromParentViewController()
self.view.removeFromSuperview()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.rightNavController = nil
appDelegate.rightNavController?.view = nil
})
Now the issue is when child viewController is removed, parent view controller user interaction is disabled.
Any help to solve this issue.
It's not entirely clear what you're trying to achieve from the wording of the question but I would look for the isUserInteractionEnabled property on the view that's causing you trouble.
Also, maybe have a look to yourView.layer.zPosition. The view getting the interaction is the one with the highest zPosition value. Everyone defaults to zPosition = 0. All else being equal, the order in which the views where added will dictate who gets the events.

Custom Segue Class UIView Animation Issue

I am having trouble creating a custom segue using swift 3 and iOS 8. I am trying to transition between view controllers by fading from one VC to a black screen and then fading from black to my second VC. I tried to implement the segue by creating a custom segue using the code below, but it is not working as I would like it to. My goal is to perform an animation when the black square goes from 0.5 alpha to 1.0 alpha, then present my second view controller, then set the black square from 1.0 alpha back to 0.5 alpha and delete it. Right now, it does the first part correctly but after the animation finishes you can see the first VC for a brief instant before the second VC pops up. How should I change my code to make the transition smoother and get the desired result?
override func perform() {
let sourceVC = self.source
let destinationVC = self.destination
let square = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
square.alpha = 0.5
square.backgroundColor = UIColor.black
sourceVC.view.addSubview(square)
UIView.animate(withDuration: 0.2, animations: {
square.alpha = 1.0
})
UIView.animate(withDuration: 0.2, delay: 0.2, animations: {
square.alpha = 0.5
}) { (finished) in
sourceVC.present(destinationVC, animated: false, completion: nil)
}
}
Update your code like below and try:
UIView.animate(withDuration: 0.2, animations: {
square.alpha = 1.0
UIView.animate(withDuration: 0.2, delay: 0.2, animations: {
square.alpha = 0.5
}) { (finished) in
DispatchQueue.main.async {
sourceVC.present(destinationVC, animated: false, completion: nil)
}
})
}

Resources