Uitabbar Transition Issue Below iOS 11 Swift

Drawing a dismiss transition beneath the TabBar in Swift 4

UITabBarController does all of its layout using autoresizing masks. That being the case you can grab the tabBar add it to the container view, perform the animation then add it back to it's root view. For example using the Cards animation you can change the animateTransition(using transitionContext: UIViewControllerContextTransitioning) to:

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

// Animation Context Setup
let container = transitionContext.containerView
let to = transitionContext.viewController(forKey: .to)!
let from = transitionContext.viewController(forKey: .from)!
container.addSubview(to.view)
container.addSubview(from.view)

// If going to tab bar controller
// Add tab bar above view controllers
// Turn off interactions
if !presenting, let tabController = to as? UITabBarController {
tabController.tabBar.isUserInteractionEnabled = false
container.addSubview(tabController.tabBar)
}

guard presenting else {

// Detail View Controller Dismiss Animations
card.isPresenting = false

let detailVC = from as! DetailViewController
let cardBackgroundFrame = detailVC.scrollView.convert(card.backgroundIV.frame, to: nil)
let bounce = self.bounceTransform(cardBackgroundFrame, to: card.originalFrame)

// Blur and fade with completion
UIView.animate(withDuration: velocity, delay: 0, options: .curveEaseOut, animations: {

detailVC.blurView.alpha = 0
detailVC.snap.alpha = 0
self.card.backgroundIV.layer.cornerRadius = self.card.cardRadius

}, completion: { _ in

detailVC.layout(self.card.originalFrame, isPresenting: false, isAnimating: false)
self.card.addSubview(detailVC.card.backgroundIV)

// Add tab bar back to tab bar controller's root view
if let tabController = to as? UITabBarController {
tabController.tabBar.isUserInteractionEnabled = true
tabController.view.addSubview(tabController.tabBar)
}
transitionContext.completeTransition(true)
})

// Layout with bounce effect
UIView.animate(withDuration: velocity/2, delay: 0, options: .curveEaseOut, animations: {

detailVC.layout(self.card.originalFrame, isPresenting: false, transform: bounce)
self.card.delegate?.cardIsHidingDetail?(card: self.card)

}) { _ in UIView.animate(withDuration: self.velocity/2, delay: 0, options: .curveEaseOut, animations: {

detailVC.layout(self.card.originalFrame, isPresenting: false)
self.card.delegate?.cardIsHidingDetail?(card: self.card)
})
}
return

}

// Detail View Controller Present Animations
card.isPresenting = true

let detailVC = to as! DetailViewController
let bounce = self.bounceTransform(card.originalFrame, to: card.backgroundIV.frame)

container.bringSubview(toFront: detailVC.view)
detailVC.card = card
detailVC.layout(card.originalFrame, isPresenting: false)

// Blur and fade with completion
UIView.animate(withDuration: velocity, delay: 0, options: .curveEaseOut, animations: {

self.card.transform = CGAffineTransform.identity // Reset card identity after push back on tap
detailVC.blurView.alpha = 1
detailVC.snap.alpha = 1
self.card.backgroundIV.layer.cornerRadius = 0

}, completion: { _ in

detailVC.layout(self.card.originalFrame, isPresenting: true, isAnimating: false, transform: .identity)
transitionContext.completeTransition(true)
})

// Layout with bounce effect
UIView.animate(withDuration: velocity/2, delay: 0, options: .curveEaseOut, animations: {

detailVC.layout(detailVC.view.frame, isPresenting: true, transform: bounce)
self.card.delegate?.cardIsShowingDetail?(card: self.card)

}) { _ in UIView.animate(withDuration: self.velocity/2, delay: 0, options: .curveEaseOut, animations: {

detailVC.layout(detailVC.view.frame, isPresenting: true)
self.card.delegate?.cardIsShowingDetail?(card: self.card)

})
}

}

Which produces an animation like:

Sample Image

iPhone: How to switch tabs with an animation?

Update 04/2016: Justed wanted to update this to say thank you to everyone for all the votes. Please also note that this was originally written way back when ... before ARC, before constraints, before ... a lot of stuff! So please take this into account when deciding whether to use these techniques. There may be more modern approaches. Oh, and if you find one. Please add a response so everyone can see. Thanks.

Some time later ...

After much research I came up with two working solutions. Both of these worked and did the animation between tabs.

Solution 1: transition from view (simple)

This is the easiest and makes use of a predefined UIView transition method. With this solution we don't need to manage the views because the method does the work for us.

// Get views. controllerIndex is passed in as the controller we want to go to. 
UIView * fromView = tabBarController.selectedViewController.view;
UIView * toView = [[tabBarController.viewControllers objectAtIndex:controllerIndex] view];

// Transition using a page curl.
[UIView transitionFromView:fromView
toView:toView
duration:0.5
options:(controllerIndex > tabBarController.selectedIndex ? UIViewAnimationOptionTransitionCurlUp : UIViewAnimationOptionTransitionCurlDown)
completion:^(BOOL finished) {
if (finished) {
tabBarController.selectedIndex = controllerIndex;
}
}];

Solution 2: scroll (more complex)

A more complex solution, but gives you more control of the animation. In this example we get the views to slide on and off. With this one we need to manage the views ourselves.

// Get the views.
UIView * fromView = tabBarController.selectedViewController.view;
UIView * toView = [[tabBarController.viewControllers objectAtIndex:controllerIndex] view];

// Get the size of the view area.
CGRect viewSize = fromView.frame;
BOOL scrollRight = controllerIndex > tabBarController.selectedIndex;

// Add the to view to the tab bar view.
[fromView.superview addSubview:toView];

// Position it off screen.
toView.frame = CGRectMake((scrollRight ? 320 : -320), viewSize.origin.y, 320, viewSize.size.height);

[UIView animateWithDuration:0.3
animations: ^{

// Animate the views on and off the screen. This will appear to slide.
fromView.frame =CGRectMake((scrollRight ? -320 : 320), viewSize.origin.y, 320, viewSize.size.height);
toView.frame =CGRectMake(0, viewSize.origin.y, 320, viewSize.size.height);
}

completion:^(BOOL finished) {
if (finished) {

// Remove the old view from the tabbar view.
[fromView removeFromSuperview];
tabBarController.selectedIndex = controllerIndex;
}
}];

This Solution in Swift:

extension TabViewController: UITabBarControllerDelegate {
public func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {

let fromView: UIView = tabBarController.selectedViewController!.view
let toView : UIView = viewController.view
if fromView == toView {
return false
}

UIView.transitionFromView(fromView, toView: toView, duration: 0.3, options: UIViewAnimationOptions.TransitionCrossDissolve) { (finished:Bool) in

}
return true
}
}

Swift: How to transition from Navigation Controller to Tab Bar Controler

Okey I found the issue. Thanks @Paulw11 for pointing out that a Tab Bar Viewcontroller can be used just like a normal view controller. The problem was the part "
as? MainViewController", this had to say as? UITabBarController and voila, it worked. For anyone in the future, thats how you do it.

How to animate Tab bar tab switch with a CrossDissolve slide transition?

There is a simpler way to doing this. Add the following code in the tabbar delegate:

Working on Swift 2, 3 and 4

class MySubclassedTabBarController: UITabBarController {

override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
}

extension MySubclassedTabBarController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {

guard let fromView = selectedViewController?.view, let toView = viewController.view else {
return false // Make sure you want this as false
}

if fromView != toView {
UIView.transition(from: fromView, to: toView, duration: 0.3, options: [.transitionCrossDissolve], completion: nil)
}

return true
}
}

EDIT (4/23/18)
Since this answer is getting popular, I updated the code to remove the force unwraps, which is a bad practice, and added the guard statement.

EDIT (7/11/18)
@AlbertoGarcía is right. If you tap the tabbar icon twice you get a blank screen. So I added an extra check

Unbalanced calls to begin/end appearance transitions for UITabBarController: 0x197870

Without seeing more of the surrounding code I can't give a definite answer, but I have two theories.

  1. You're not using UIViewController's designated initializer initWithNibName:bundle:. Try using it instead of just init.

  2. Also, self may be one of the tab bar controller's view controllers. Always present view controllers from the topmost view controller, which means in this case ask the tab bar controller to present the overlay view controller on behalf of the view controller. You can still keep any callback delegates to the real view controller, but you must have the tab bar controller present and dismiss.



Related Topics



Leave a reply



Submit