Swift 4 AppStore like transition - swift

Trying to get this transition https://www.youtube.com/watch?v=4TVnRx7PTTs
First VC has a card like image and second one is tableview with same image as sqare on first cell.
let storyboard = self.storyboard?.instantiateViewController(withIdentifier: "vc2")
let firstClassView = self.view
let secondClassView = storyboard?.view
secondClassView?.frame = self.card.frame
if let window = UIApplication.shared.keyWindow {
window.insertSubview(secondClassView!, aboveSubview: firstClassView!)
UIView.animate(withDuration: 0.5, animations: { () -> Void in
secondClassView?.frame = self.view.frame
}, completion: {(Finished) -> Void in
self.present(storyboard!, animated: false, completion: nil)
})
}
so i am adding the second vc's view to first vc with the frame of cardImage and animating it to full frame of a view. but the cardImage is not square so it doesn't look as good as app store. any suggestions?

Related

Swift animate with duration xib

I am wanting to create a subview which slides in when my viewController loads.
However the view is presenting without the animation effect, how would I get the view to animate?
I am calling on my viewContoller:
menuBarView.show(viewController: self)
class menuBarView {
static func show(viewController: UIViewController){
menuBarSubView = UINib(nibName: "MenuBar", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as? UIView
menuBarSubView?.frame.size.height = viewController.view.frame.height * 0.1
menuBarSubView?.frame.size.width = viewController.view.frame.width
menuBarSubView?.frame.origin.x = viewController.view.frame.origin.x
menuBarSubView?.frame.origin.y = viewController.view.frame.origin.y - menuBarSubView.frame.height
menuBarSubView?.tag = 1
viewController.view.addSubview(menuBarSubView!)
viewController.view.layoutIfNeeded()
UIView.animate(withDuration: 2, animations: {
viewController.view.viewWithTag(1)?.frame.origin.y += (viewController.view.viewWithTag(1)?.frame.height)!
viewController.view.layoutIfNeeded()
})
}
}

UIViewControllerAnimatedTransitioning doesn't complete on iOS 9/10, but works iOS 11 and later?

I have a custom presentation controller which shows a UIViewController as a "bottom sheet" above the current ViewController, and allows an interactive gesture to dismiss the presented modal. The modal's modalPresentationStyle is set to .current, and the transitioningDelegate is set to my custom class during init(). The current code works perfectly on iOS 11 and 12, but appears to "freeze" the device on iOS 9 and 10.
Here is where the animation takes place. I have confirmed the presentedFrame and dismissedFrame are correct.
extension BottomSheetPresentationManager: UIViewControllerAnimatedTransitioning {
func transitionDuration(
using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.25
}
func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return self
}
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return self
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let key = presenting ? UITransitionContextViewControllerKey.to
: UITransitionContextViewControllerKey.from
let keyV = presenting ? UITransitionContextViewKey.to
: UITransitionContextViewKey.from
let controller = transitionContext.viewController(forKey: key)!
let view = transitionContext.view(forKey: keyV)!
if presenting {
transitionContext.containerView.addSubview(view)
}
let presentedFrame = transitionContext.finalFrame(for: controller)
var dismissedFrame = presentedFrame
switch direction {
case .left:
dismissedFrame.origin.x = -presentedFrame.width
case .right:
dismissedFrame.origin.x = transitionContext.containerView.frame.size.width
case .top:
dismissedFrame.origin.y = -presentedFrame.height
case .bottom:
dismissedFrame.origin.y = transitionContext.containerView.frame.size.height
}
let initialFrame = presenting ? dismissedFrame : presentedFrame
let finalFrame = presenting ? presentedFrame : dismissedFrame
// Runs when the animation finishes
let completionBlock: (Bool) -> Void = { (finished) in
// tell our transitionContext object that we've finished animating
if transitionContext.transitionWasCancelled {
if self.interactive {
transitionContext.cancelInteractiveTransition()
}
transitionContext.completeTransition(false)
} else {
if self.interactive {
finished ? transitionContext.finishInteractiveTransition() : transitionContext.cancelInteractiveTransition()
}
transitionContext.completeTransition(finished)
}
}
// Put what you want to animate here.
let animationBlock: () -> Void = {
view.frame = finalFrame
}
// Set up for the animation
let animationDuration = transitionDuration(using: transitionContext)
view.frame = initialFrame
// Perform a different animation based on whether we're interactive (performing a gesture) or not
if interactive {
// Do a linear animation so we match our dragging with our transition
UIView.animate(withDuration: animationDuration,
delay: 0,
options: .curveLinear,
animations: animationBlock,
completion: completionBlock)
} else {
// Do a spring animation with easing
UIView.animate(withDuration: animationDuration,
delay: 0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 0.45,
options: [.curveEaseInOut],
animations: animationBlock,
completion: completionBlock)
}
}
}
After much testing, I have figured out that the completion block on the UIView.animate block never gets called (completionBlock in the code), and in the Capture View Hierarchy view, it actually shows my presented view on top, with what looks like a screenshot of the view (UIVisualEffectView) directly under the presented menu.
What you are seeing there is my bottom menu (the UITableView with cells that you see in front), with a UIVisualEffectView behind it (appears to be a screenshot of the UICollection view behind that), and a UICollectionView that called the present() method for the menu.
What would cause the UIView.animate block not to complete? Is there any reason the behavior would differ between iOS 10 and 11? Any help would be greatly appreciated.

ViewController slide animation

I want to create an animation like the iOS app facebook at tabswitch[1]. I have already tried to develop some kind of animation, the problem that occurs is that the old view controller becomes invisible directly on the switch, instead of fading out slowly while the new controller is sliding in fast.
I've found this SO question How to animate Tab bar tab switch with a CrossDissolve slide transition? but the as correct marked solution does not really work for me (it is not a slide it is a fade transition). What I'd also like to get is the function to make slide left or right to switch the tabs. Like it was on a older version of facebook.
What I've got so far is this:
extension TabBarController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
guard let fromView = selectedViewController?.view,
let toView = viewController.view else { return false }
if fromView != toView {
toView.transform = CGAffineTransform(translationX: -90, y: 0)
UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
toView.transform = CGAffineTransform(translationX: 0, y: 0)
})
}; return true
}
}
class TabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
}
How to fix this?
[1]
I would very much like to add a gif from the Facebook app. The problem is that I don't want to censor the video and just reveal too much of my data. (Even if fb already has them). Also on youtube I didn't find a suitable recording. Please try it yourself in the fb app in iOS.
You can use the following idea: https://samwize.com/2016/04/27/making-tab-bar-slide-when-selected/
Also, here's the code updated to Swift 4.1 and I also removed the force unwrappings:
import UIKit
class MyTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
}
extension MyTabBarController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
guard let tabViewControllers = tabBarController.viewControllers, let toIndex = tabViewControllers.index(of: viewController) else {
return false
}
animateToTab(toIndex: toIndex)
return true
}
func animateToTab(toIndex: Int) {
guard let tabViewControllers = viewControllers,
let selectedVC = selectedViewController else { return }
guard let fromView = selectedVC.view,
let toView = tabViewControllers[toIndex].view,
let fromIndex = tabViewControllers.index(of: selectedVC),
fromIndex != toIndex else { return }
// Add the toView to the tab bar view
fromView.superview?.addSubview(toView)
// Position toView off screen (to the left/right of fromView)
let screenWidth = UIScreen.main.bounds.size.width
let scrollRight = toIndex > fromIndex
let offset = (scrollRight ? screenWidth : -screenWidth)
toView.center = CGPoint(x: fromView.center.x + offset, y: toView.center.y)
// Disable interaction during animation
view.isUserInteractionEnabled = false
UIView.animate(withDuration: 0.3,
delay: 0.0,
usingSpringWithDamping: 1,
initialSpringVelocity: 0,
options: .curveEaseOut,
animations: {
// Slide the views by -offset
fromView.center = CGPoint(x: fromView.center.x - offset, y: fromView.center.y)
toView.center = CGPoint(x: toView.center.x - offset, y: toView.center.y)
}, completion: { finished in
// Remove the old view from the tabbar view.
fromView.removeFromSuperview()
self.selectedIndex = toIndex
self.view.isUserInteractionEnabled = true
})
}
}
So, you need to subclass UITabBarController and you also have to write the animation part, you can tweak the animation options (delay, duration, etc).
I hope it helps, cheers!
I've never seen Facebook so I don't know what the animation is. But you can have any animation you like when a tab bar controller changes its tab (child view controller), coherently and without any hacks, using the built-in mechanism that Apple provides for adding custom animation to a transition between view controllers. It's called custom transition animation.
Apple first introduced this mechanism in 2013. Here's a link to their video about it: https://developer.apple.com/videos/play/wwdc2013/218/
I immediately adopted this in my apps, and I think it makes them look a lot spiffier. Here's a demo of a tab bar controller custom transition that I like:
The really cool thing is that once you've decided what animation you want, making the transition interactive (i.e. drive it with a gesture instead of a button click) is easy:
Now, you might be saying: Okay, but that's not quite the animation I had in mind. No problem! Once you've got the hang of the custom transition architecture, changing the animation to anything you like is easy. In this variant, I just commented out one line so that the "old" view controller doesn't slide away:
So let your imagination run wild! Adopt custom transition animations, the way that iOS intends.
If you want something for pushViewController navigation, you can try this.
However, when switching between tabs on a TabBarController, this will not work. For that, I'd go with #mihai-erős 's solution
Change the Animation duration as per your liking, and assign this class to your navigation segues, for a Slide Animation.
class CustomPushSegue: UIStoryboardSegue {
override func perform() {
// first get the source and destination view controllers as UIviews so that they can placed in navigation stack
let sourceVCView = self.source.view as UIView!
let destinationVCView = self.destination.view as UIView!
let screenWidth = UIScreen.main.bounds.size.width
//create the destination view's rectangular frame i.e starting at 0,0 and equal to screenwidth by screenheight
destinationVCView?.transform = CGAffineTransform(translationX: screenWidth, y: 0)
//the destinationview needs to be placed on top(aboveSubView) of source view in the app window stack before being accessed by nav stack
// get the window and insert destination View
let window = UIApplication.shared.keyWindow
window?.insertSubview(destinationVCView!, aboveSubview: sourceVCView!)
// the animation: first remove the source out of screen by moving it at the left side of it and at the same time place the destination to source's position
// Animate the transition.
UIView.animate(withDuration: 0.3, animations: { () -> Void in
sourceVCView?.transform = CGAffineTransform(translationX: -screenWidth,y: 0)
destinationVCView?.transform = CGAffineTransform.identity
}, completion: { (Finished) -> Void in
self.source.present(self.destination, animated: false, completion: nil)
})
}
}

Weird UIVisualEffectView behavior in multitasking

I have an AccountsViewController which has UIVisualEffectView with blur effect in background (subview at index 0), so that it covers previous controller with blur during transition. But when I then switch to another app and open multitasking menu again to switch back to my app, its blurry UIVisualEffectView seem to be damaged (as shown on the screenshot 1). When I make my app active again, the view gets "repaired" and looks ok again (as shown on the second screenshot)
Some code from my custom transitioning class where I add the blur view
// in the animateTransition.. method
guard let container = transitionContext.containerView() else {
return
}
let blurEffectView: UIVisualEffectView
if container.viewWithTag(101) != nil {
blurEffectView = container.viewWithTag(101)! as! UIVisualEffectView
}
else {
blurEffectView = UIVisualEffectView(frame: container.frame)
blurEffectView.translatesAutoresizingMaskIntoConstraints = false
blurEffectView.tag = 101
container.addSubview(blurEffectView)
}
if self.isPresenting {
// We're presenting a view controller over another
toViewController.view.transform = CGAffineTransformMakeScale(0.7, 0.7)
container.addSubview(toViewController.view)
container.sendSubviewToBack(blurEffectView)
toViewController.view.alpha = 0
UIView.animateWithDuration(self.transitionDuration(transitionContext), delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.8, options: [], animations: { () -> Void in
fromViewController.view.transform = CGAffineTransformMakeScale(0.8, 0.8)
toViewController.view.transform = CGAffineTransformMakeScale(1, 1)
blurEffectView.effect = UIBlurEffect(style: .Dark)
toViewController.view.alpha = 1
}, completion: { (completed) -> Void in
self.context?.completeTransition(completed)
})
}
else {
// We're dismissing a view controller
UIView.animateWithDuration(self.transitionDuration(transitionContext), delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.8, options: [], animations: { () -> Void in
fromViewController.view.transform = CGAffineTransformMakeScale(0.7, 0.7)
toViewController.view.transform = CGAffineTransformMakeScale(1, 1)
blurEffectView.effect = nil
fromViewController.view.alpha = 0
}, completion: { (completed) -> Void in
fromViewController.view.removeFromSuperview()
self.context?.completeTransition(completed)
})
}
Seems like I've figured it out myself: this issue appears only when there is an UIWindow back (which is black) showing through.
In this question I had a transition animation container with these views:
fromViewController's view (with a scale transform 0.8;0.8)
UIVisualEffectView with .Dark style blur applied to it
toViewController's view
Because of fromViewController's view size (which was smaller than the screen because of scale transform), the black UIWindow view was showing through. And according to my research this causes UIVisualEffectView to produce a glitch described in this question.
The solution is to add a black UIView with the frame of an UIWindow and add it to the bottom of the container's view hierarchy:
let blackView = UIView(frame: container.frame)
blackView.backgroundColor = UIColor.blackColor()
container.insertSubview(blackView, atIndex: 0)
And then this glitch seem to appear no longer.

Swift Detect the position of a Button in UIView

I can detect the button pressed in UIView through this simple code
func handlePan(recognizer:UIPanGestureRecognizer) {
if let buttonRecognize = recognizer.view as? UIButton {
let subviews = self.view.subviews as [UIView]
for v in subviews {
if let button = v as? UIButton {
if button.tag != -1 {
var p = button.superview?.convertPoint(button.center, toView: view)
println(p)
}
UIView.animateWithDuration(Double(slideFactor * 2),
delay: 0,
options: UIViewAnimationOptions.CurveEaseOut,
animations: {recognizer.view!.center = finalPoint },
completion: nil)
}}}}
}
As can detect the position of a button in UIView ?
for (0,0) of Uiview = (367.666656494141,207.33332824707) for func handlePan
I would like to find the value of the button than the UIView where the center is that of UIView itself and not the value of func handlePan where the center is the top left corner !!
P.S. I tried to move the let subview etc after
completion: nil
but the result does not change !!
You can convert a point coordinate system to that of the specified view using
convertPoint(_ point: CGPoint, toView view: UIView?)
The toView is the coordinate system to which it will be converted.