Flip between two ViewControllers under the same NavigationController - ios

I had referred to this question and several question about view / view controller transition but still couldn't find an satisfied answer. Most of the solutions suggest to flip the views instead of view controllers. However, the two view controllers in my app have totally different operation and implementation logic, thus I'm avoiding to mix them together.
In my app, I have a modal view controller FrontViewController which is embedded in a NavigationController. After pushing one button on the view, the modal view controller should flip to BackViewController, and vice versa. I had ever tried the following in FrontViewController:
let navi = UINavigationController(rootViewController: backController)
navi.modalPresentationStyle = .CurrentContext
navi.modalTransitionStyle = .FlipHorizontal
self.presentViewController(backController, animated: true, completion: nil)
This works almost as what I want, except that it flips navigation bar as well. Moreover, if I dismiss the modal view, only the view controller on the top of the stack is dismissed, while I failed to get the right parent/presenting controller to dismiss all the other stack controllers in the modal view.
Thus I further tried to prevent viewcontroller stack and use transitionFromViewController in FrontViewController using the same navigation controller instead:
self.navigationController!.addChildViewController(backController)
self.willMoveToParentViewController(nil)
self.navigationController!.transitionFromViewController(self, toViewController: backViewController, duration: 1, options: .TransitionFlipFromLeft, animations: {}, completion: ({Bool -> Void in
self.removeFromParentController()
c.didMoveToParentViewController(self)
}))
Then I got this run time error on executiing: Parent view controller is using legacy containment in call to -[UIViewController transitionFromViewController:toViewController: duration:options:animations:completion:]
So, how to transition between two view controllers while preventing them remain in the view controller stack?

You can add custom transition to the navigation controllers layer just before pushing the view controller.
let transition = CATransition()
transition.duration = 0.3
transition.type = "flip"
transition.subtype = kCATransitionFromLeft
self.navigationController?.view.layer.addAnimation(transition, forKey: kCATransition)
self.navigationController?.pushViewController(viewController!, animated: false)
Note that the animated parameter should be false. Otherwise the default sliding animation will takes place

See this demo for you :
Swift code for flipAnimation :
let mainStory = UIStoryboard(name: "Main", bundle: nil)
let search = mainStory.instantiateViewControllerWithIdentifier("SecondViewController") as! SecondViewController
UIView.beginAnimations("animation", context: nil)
UIView.setAnimationDuration(1.0)
self.navigationController!.pushViewController(search, animated: false)
UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromLeft, forView: self.navigationController!.view, cache: false)
UIView.commitAnimations()
FlipViewController Animation
Output :

Swift 5 - I personally like using this approach.
//PUSH
let secondVC = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "secondVC") as! SecondVC
self.navigationController?.pushViewController(secondVC, animated: false)
UIView.transition(from: self.view, to: secondVC.view, duration: 0.85, options: [.transitionFlipFromLeft])
//POP
let firstVC = self.navigationController?.viewControllers[(self.navigationController?.viewControllers.count ?? 2) - 2] as? FirstVC
if let firstView = firstVC?.view{
self.navigationController?.popViewController(animated: false)
UIView.transition(from: self.view, to: firstView, duration: 0.85, options: [.transitionFlipFromRight])
} else {
self.navigationController?.popViewController(animated: true)
}

Swift 3.0 +
Should use UIView's animation block, make sure your viewController in UINavigationController stacks:
let viewController = ViewController(nibName: "xxx", bundle: nil)
UIView.transition(with: self.navigationController!.view, duration: 1.0, options: .transitionFlipFromLeft, animations: {
self.navigationController?.pushViewController(viewController, animated: false)
}, completion: nil)

override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
// for back button
changeTransition()
}
//btnMap.addTarget(self, action: #selector(searchHotelsResultVC.goToMap), for: .touchUpInside)
//btnMap.addTarget(self, action: #selector(MapViewController.backToList), for: .touchUpInside)
func goToMap() {
// for pushing
changeTransition()
navigationController?.pushViewController(settingsVC, animated: false)
}
func backToList() {
// for dismiss
changeTransition()
navigationController?.popViewController(animated: false)
dismiss(animated: true, completion: nil)
}
func changeTransition() {
let transition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
//transition.type = kCATransitionPush
transition.type = "flip"
transition.subtype = kCATransitionFromLeft
navigationController?.view.layer.add(transition, forKey: kCATransition)
}

here Student is NSObjectClass
var Arraydata:[Student] = []
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let arr = Arraydata[indexPath.row]
if indexPath.row == arr
{
let story = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
UIView.beginAnimations("", context: nil)
UIView.setAnimationDuration(1.0)
UIView.setAnimationCurve(UIViewAnimationCurve.easeInOut)
UIView.setAnimationTransition(UIViewAnimationTransition.flipFromRight, for: (self.navigationController?.view)!, cache: false)
self.navigationController?.pushViewController(story, animated: true)
UIView.commitAnimations()
}
}

Related

How to transition from viewcontroller to TabbarController Programmatically without StoryBoards?

From my onboarding viewcontroller i need to transition to my tabBarController which is also a navigationController and i want to make it as a root viewcontroller afterwards.
#objc func willGoToMain(sender: UIButton!) {
let tabBarController = TabBarController()
let navigationController = UINavigationController(rootViewController: tabBarController)
navigationController.isNavigationBarHidden = true
self.present(tabBarController, animated: true, completion: nil)
}
Thread 1: "Application tried to present modally a view controller <MyStarterProject.TabBarController: 0x7f9bd8011400> that has a parent view controller <UINavigationController: 0x7f9bd7022800>
I like to transition it like a modal presentation or cross dissolve. not just to appeared as a rootviewcontroller all of a sudden.
Here's what i'm trying to do and this solves my problem.
#objc func willGoToMain(sender: UIButton!) {
guard let window = UIApplication.shared.keyWindow else {
return
}
let tabbarController = TabBarController()
let navigationController = UINavigationController(rootViewController: tabbarController)
navigationController.isNavigationBarHidden = true
window.rootViewController = navigationController
window.makeKeyAndVisible()
let options: UIView.AnimationOptions = .transitionCrossDissolve
let duration: TimeInterval = 0.3
UIView.transition(with: window, duration: duration, options: options, animations: {}, completion:
{ completed in
// maybe do something on completion here
})
}
Change this:
self.present(tabBarController, animated: true, completion: nil)
with this:
self.present(navigationController, animated: true, completion: nil)
Please try this one.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let mainTabBarController = storyboard.instantiateViewController(identifier: "MainTabBarController")
mainTabBarController.modalPresentationStyle = .fullScreen
self.present(mainTabBarController, animated: true, completion: nil)
If you need more details. please visit this blog
https://fluffy.es/how-to-transition-from-login-screen-to-tab-bar-controller/

Dismissing view controller does not goes back to the previous view controller

I am presenting a view controller after pushing another view controller with animation as my application requires it. The code is as given below:
func pushViewController() {
let viewcontroller = UIStoryboard(name: "Home", bundle: nil).instantiateViewController(withIdentifier: "homeSearchIndentifier") as? HomeSearchViewController
let searchViewController = UIStoryboard(name: "Home", bundle: nil).instantiateViewController(withIdentifier: "searchRideIdentifier") as? SearchRideViewController
searchViewController?.parentView = .homeLandingPage
let duration = 0.4
let transition: CATransition = CATransition()
transition.duration = duration
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromBottom
self.navigationController!.view.layer.add(transition, forKey: kCATransition)
self.navigationController?.pushViewController(viewcontroller!, animated: false)
viewcontroller?.present(searchViewController!, animated: true, completion: nil)
}
In the presented view controller i am assigning a Side Menu controller as i need to show the side menu while swiping to the right:
override func viewDidLoad() {
super.viewDidLoad()
self.addSearchListScreen()
self.locationManager.delegate = self
yourLocation.layer.sublayerTransform = CATransform3DMakeTranslation(7, 0, 0)
let leftViewController = UIStoryboard(name: "SideBar", bundle: nil).instantiateViewController(withIdentifier: "sideBar") as? SideBarViewController
let slideMenuController = SlideMenuTrackerController(mainViewController: self, leftMenuViewController: leftViewController!)
AppDelegate.sharedInstance().window?.rootViewController = slideMenuController
leftViewController?.mainViewController = self
slideMenuController.automaticallyAdjustsScrollViewInsets = true
AppDelegate.sharedInstance().window?.backgroundColor = UIColor(red: 236.0, green: 238.0, blue: 241.0, alpha: 1.0)
AppDelegate.sharedInstance().window?.makeKeyAndVisible()
}
Now i have a custom back button in this view controller. On clicking it i need to dismiss the view controller and show the view controller from where it was presented . The name is "HomeSearchViewController"
For dismissing i wrote the following code in the button action:
func navigateToPreviousController() {
self.dismiss(animated: true, completion: nil)
}
But this is not dismissing the view controller. May i know what is the issue?
in order to dismiss the view controller your view controller should be embedded inside a navigation controller.
eg:
let controller =storyboard.instantiateViewController(withIdentifier: "storyboardidentifier") as! yourviewcontroller
let navController = UINavigationController(rootViewController: controller)
present(navController, animated: true, completion: nil)
and then on the presented view controller if you use below code it will dismiss the view controller.
func dismissButtonPressed(){
self.dismiss(animated: true, completion: nil)
}
Cheers!!
Edit: My first answer addressed only part of the problem.
Looking further at your code...
It appears you are pushing HomeSearchViewController onto the navigationController stack, and then also presenting SearchRideViewController from HomeSearchViewController.
However...
In:
HomeSearchViewController viewDidLoad()
you create an instance of
SideBarViewController (leftViewController)
and you create an instance of
SlideMenuTrackerController (slideMenuController)
and assign
self as the .mainViewController and
leftViewController as the .leftMenuViewController
of slideMenuController.
If that's not confusing enough, you also:
AppDelegate.sharedInstance().window?.rootViewController = slideMenuController
which replaces HomeSearchViewController ... which you had just pushed onto the navigationController stack!
So, your view hierarchy is now (or, at least, seems to be if I am following your code):
Window
slideMenuController (instance of SlideMenuTrackerController)
+- searchViewController (instance of SearchRideViewController)
+- leftViewController (instance of SideBarViewController)
In order to "get back to" where you came from, you will need to re-create whatever VC you want to get to and (again) replace AppDelegate.sharedInstance().window?.rootViewController.
Without seeing your entire project, I'm not sure how you should really go about that.
[original answer]
In here:
func pushViewController() {
// ... the other lines
self.navigationController?.pushViewController(viewcontroller!, animated: false)
viewcontroller?.present(searchViewController!, animated: true, completion: nil)
}
You push viewcontroller and also .present(searchViewController!... from viewcontroller (that's a bit odd to begin with, but regardless)...
Then in here:
func navigateToPreviousController() {
self.dismiss(animated: true, completion: nil)
}
You .dismiss a view controller, but you never .pop back on the navigation controller's stack.

Present New ViewController programmatically [Swift 3]

What I am trying to do is present a new view controller programmatically with Swift 3 with an animated fade-out then fade-in and a pop-up then pop-down depending on what view controllers the user enters. This is so my app feels more modern and less old and blocky.
Here is the current method I am using for presenting a new view controller. It works, but it is rather abrupt and robotic:
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let ExampleVC = storyBoard.instantiateViewController(withIdentifier: "ExampleVC")
//ExampleVC standing for ExampleViewController
self.navigationController?.pushViewController(ExampleVC, animated: false)
here is a method for animating UIViews but it doesnt work with UIViewControllers
func popIn(yourView : UIView) {
yourView.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
UIView.animateKeyframes(withDuration: 0.2, delay: 0.0, options: UIViewKeyframeAnimationOptions.calculationModeDiscrete, animations: {
yourView.transform = .identity
}, completion: nil)
self.view.addSubview(yourView)
}
i need something similar to this but for a UIViewController
UPDATE
here is what you would use to to present a new viewcontroller in a cool way to make your app more modern
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let VC = storyboard.instantiateViewController(withIdentifier: "Example") //<<< make sure you enter in your storyboard ID here or you will crash your app
VC.modalTransitionStyle = .crossDissolve //you can change this to do different animations
VC.view.layer.speed = 0.1 //adjust this to animate at different speeds
self.navigationController?.present(VC, animated: true, completion: nil)
if you have any questions comment them so i can help you guys out
var storyboard = UIStoryboard(name: "Main", bundle: nil)
var ivc = storyboard.instantiateViewController(withIdentifier: "ExampleVC")
ivc.modalTransitionStyle = .crossDissolve
ivc.view.layer.speed = 0.1
self.present(ivc, animated: true, completion: { _ in })
Check this code for animation to view controller

How to push a UIViewController without using a navigational controller?

I am trying to imitate the behaviour that can be observed with SFSafariViewController. When presented, the push transition animation is used just like with a root UINavigationalController. When doing an edge pan swipe right, the pop transition follows.
let url = URL(string: "http://google.com")!
let safari = SFSafariViewController(url: url)
present(safari, animated: true, completion: nil)
However, I need the same transition to work with any other UIViewController.
let storyboard = UIStoryboard(name: "Place", bundle: nil)
let controller = storyboard.instantiateInitialViewController()
present(controller!, animated: true, completion: nil)
Using CATransition you can able to change the animation direction while presenting the ViewController
let controller = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
let transition = CATransition()
transition.duration = 0.5
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromRight
view.window!.layer.add(transition, forKey: kCATransition)
present(controller, animated: false, completion: nil)
#Fuxing, It is not possible to push a view controller without navigation controller.Ther are two option to push a view controller.
Add a navigation controller to your viewController in the storyboard.
Create an object of the navigation controller.
let viewToPush = YourViewController()
let nav = UINavigationController(rootViewController: viewToPush)
nav.pushViewController(nav, animated: true)

Modal view controller does not cover status bar

I'm developing an ios app. I have a a main view and in this view
im trying to present a modal view controller with dimmed background(black with opacity).
The problem is that the status bar is not affected by this color and remains the same.
This is how i present the view controller:
let shareViewController = self.storyboard?.instantiateViewControllerWithIdentifier("ShareViewController") as! ShareViewController
shareViewController.battle = battle
shareViewController.delegate = self
let animation = CATransition()
animation.duration = 1
animation.type = kCATransitionFade
self.view.window?.layer.addAnimation(animation, forKey: kCATransition)
presentViewController(shareViewController, animated: false) {
() in
// nothing here
}
Here are some screenshots to demonstrate the problem:
This is the problem(status bar color):
Problem illustration
This is the modal view in storyboard:
storyboard
I cannot reproduce your problem, the following code works without problems in my single view app:
let viewController = UIViewController()
viewController.modalPresentationStyle = .overFullScreen
viewController.view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
let animation = CATransition()
animation.duration = 1
animation.type = kCATransitionFade
self.view.window?.layer.add(animation, forKey: kCATransition)
self.present(viewController, animated: false, completion: nil)
However note that you should be presenting over the root controller of the view. Sometimes you can get strange effects when presenting from your internal controllers:
self.view.window?.rootViewController?.present(viewController, animated: false, completion: nil)
Also make sure you are using the correct modalPresentationStyle.
Set your view controller as the root view controller of a UIWindow, then present the window at the UIWindowLevelAlert level.
Below is a Swift 3 class used to animate a modal popup over all other UI elements, including the status bar. A scrim view is used to shade background UI and intercept touches to dismiss the view.
import UIKit
class ModalViewController: UIViewController {
private let scrimView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.black
view.alpha = 0.0
return view
}()
private var myWindow: UIWindow?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.clear
// Setup scrim View
view.addSubview(scrimView)
view.topAnchor.constraint(equalTo: scrimView.topAnchor).isActive = true
view.leftAnchor.constraint(equalTo: scrimView.leftAnchor).isActive = true
view.rightAnchor.constraint(equalTo: scrimView.rightAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: scrimView.bottomAnchor).isActive = true
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismiss as (Void) -> Void))
scrimView.addGestureRecognizer(tapGestureRecognizer)
// Layout custom popups or action sheets
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 0.25) {
self.scrimView.alpha = 0.5
// Animate in custom popups or action sheets
}
}
func present() {
myWindow = UIWindow(frame: UIScreen.main.bounds)
myWindow?.windowLevel = UIWindowLevelAlert
myWindow?.backgroundColor = UIColor.clear
myWindow?.rootViewController = self
myWindow?.isHidden = false
}
func dismiss() {
UIView.animate(
withDuration: 0.25,
animations: {
self.scrimView.alpha = 0.0
// Animate out custom popups or action sheets
},
completion: { success in
self.myWindow = nil
}
)
}
}
To present the view:
let modalView = ModalViewController()
modalView.present()
To dismiss the view, tap anywhere on the scrim.
this code works for me, when I am presenting UIViewController with alpha != 1. present UIViewController like:
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let destinationVC = storyBoard.instantiateViewController(withIdentifier: "AddComment") as! AddCommentViewController
destinationVC.modalPresentationStyle = .overCurrentContext //this line is important
destinationVC.delegate = self
destinationVC.restId = self.restaurant.id
self.present(destinationVC, animated: true, completion: nil)
then in destinationVC view controller
override func viewWillDisappear(_: Bool) {
UIView.animate(withDuration: 1, animations: { () in
self.view.backgroundColor = .clear
})
super.viewWillDisappear(true)
}
override func viewWillAppear(_: Bool) {
UIView.animate(withDuration: 1, animations: { () in
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
})
super.viewWillAppear(true)
}
and set its backgroundColor to .clear in viewDidLoad or storyboard. So UIViewController covers whole screen including status bar.
Here is the solution you might be looking for:
if let window = UIApplication.shared.keyWindow {
window.windowLevel = UIWindowLevelStatusBar + 1
}
The main idea behind this code is, window of your application has a window level which is lower than status bar window level. And what this code does is, just put your window's window level higher than status bar window level, and your window can now cover the status bar. Don't forget, this code has to be called on main thread, just before presenting your view controller. Good luck!
Custom animation transitions should be performed using UIViewControllerAnimatedTransitioning. Here is a tutorial for this purpose:
https://www.raywenderlich.com/110536/custom-uiviewcontroller-transitions
If all you want is a fade animation you can have it by changing the modalTransitionStyle property of the viewController you are going to display.
Try by fixing your code this way:
guard let shareViewController = self.storyboard?.instantiateViewControllerWithIdentifier("ShareViewController") as! ShareViewController else {
//Fallback in case of nil?
return
}
shareViewController.modalTransitionStyle = .crossDissolve
presentViewController(shareViewController, animated: true, completion: nil)
Also please note that presentViewController(shareViewController, animated: true, completion: nil) is for swift 2. The equivalent swift 3 would be present(shareViewController, animated: true, completion: nil)
you can add this code to view controller for Swift 3:
let statusView: UIView = UIView(frame: CGRect(x: 0.0, y: -20.0, width: UIScreen.main.bounds.size.width, height: 20.0))
statusView.backgroundColor = UIColor.black
statusView.alpha = 0.8
self.addSubview(self.statusView)
You could be extremely practical and simply hide the status bar when your modal view controller is up:
override func prefersStatusBarHidden() -> Bool {
return true
}

Resources