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)
Related
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/
I have some small black flickering in the transition moment between controllers.
I have to load many things to the new controller before I show it, and I pass it some UIImage argument called "Art".
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller:ModelController = storyboard.instantiateViewController(withIdentifier: "ModController") as! ModelController
controller.artwork=art //pass this to load the new
let transition = CATransition()
transition.duration = 0.30
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromRight
view.window!.layer.add(transition, forKey: kCATransition)
present(controller, animated: false, completion: nil)
On the new controller, I load everything after :
override func viewDidLoad() {
super.viewDidLoad()
//load scroller here
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.
I am using the Side menu controller. The Side view controller works fine but when another view controller B is presented over another view controller A. The side menu doesnot appears on the view controller B while swiping. The code for embedding the side menu controller is:
let homeViewController = UIStoryboard(name: "Home", bundle: nil).instantiateViewController(withIdentifier: "homeLandingIdentifier") as? HomeLandingViewController
let leftViewController = UIStoryboard(name: "SideBar", bundle: nil).instantiateViewController(withIdentifier: "sideBar") as? SideBarViewController
let mainNavigationController: UINavigationController = UINavigationController(rootViewController: homeViewController!)
let slideMenuController = SlideMenuTrackerController(mainViewController: mainNavigationController, leftMenuViewController: leftViewController!)
AppDelegate.sharedInstance().window?.rootViewController = slideMenuController
leftViewController?.mainViewController = mainNavigationController
slideMenuController.automaticallyAdjustsScrollViewInsets = true
AppDelegate.sharedInstance().window?.backgroundColor = UIColor(red: 236.0, green: 238.0, blue: 241.0, alpha: 1.0)
AppDelegate.sharedInstance().window?.makeKeyAndVisible()
The code for presenting the view controller is:
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)
}
The side menu which i am using is:
Side menu
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()
}
}