For my tab bar controller, I want a transition for 4/5 tabs. The fifth tab i was able to successfully perform a different transition, but trying to add it to the other view controllers caused a crash.
Action Storyboard: This storyboard contains my tab bar controller, and my camera view controller. The transition for this view controller is the only one that works. When i try to transition different tabs that are not in the tab bar controllers storyboard, it crashes
This is the storyboard with the problem
For example, I want to provide a different transition for the view controller in this storyboard. However, i get the error:
Unexpectedly found nil while unwrapping an optional value
This is my code:
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if let restoreID = viewController.restorationIdentifier {
if restoreID == "NavigationCamera" {
// This is the transition that works
if let nav = tabBarController.viewControllers![tabBarController.selectedIndex] as? UINavigationController {
print("Nav is allowed")
let newVC = tabBarController.storyboard?.instantiateViewController(withIdentifier: "CameraView")
let transition = CATransition()
transition.duration = 0.25
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromTop
nav.view.layer.add(transition, forKey: nil)
nav.pushViewController(newVC!, animated: false)
return false
}
} else {
if let otherNav = tabBarController.viewControllers![tabBarController.selectedIndex] as? UINavigationController {
print("Other nav is allowed")
let vcID = restoreID + "View"
print(vcID)
let myVC = otherNav.storyboard?.instantiateInitialViewController()
let otherTransition = CATransition()
otherTransition.duration = 0.25
otherTransition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
otherTransition.type = kCATransitionPush
otherTransition.subtype = kCATransitionFade
otherNav.view.layer.add(otherTransition, forKey: nil)
// This is where the error occurs and crashes
otherNav.pushViewController(myVC!, animated: false)
return false
}
}
}
return true
}
Once again the error i get is:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
let storyboard = UIStoryboard(name: <other storyboard>, bundle: nil)
let myVC = storyboard.instantiateViewController(withIdentifier: <your navigation controler>)
Related
Inside my app I have a MainVC. From there I present another ViewControllerB and I would like to push inside there so this is how I achieve this:
let ViewControllerB = self.storyboard?.instantiateViewController(withIdentifier: "ViewControllerB") as! ViewControllerB
let navController = UINavigationController(rootViewController: communityVC)
self.present(navController, animated: true, completion: nil)
This is working exactly how I want it to work. However in
ViewControllerB I also would like to push a certain ViewControllerC full screen from bottom and NOT just inside the modalView. I am calling this:
let firstLaunchVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "FirstLaunchVC")
let transition:CATransition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromTop
self.navigationController?.modalPresentationStyle = .fullScreen
self.navigationController!.view.layer.add(transition, forKey: kCATransition)
self.navigationController?.pushViewController(firstLaunchVC, animated: true)
This is pushing from the bottom but NOT fullscreen. I also get this message in the terminal:
Setting modalPresentationStyle once <UINavigationController: 0x1060f2a00> has been presented will have no effect until it is dismissed and presented again.
How can I push fullscreen from a presented ViewController?
I hope I made my problem clear. Let me know if you need more information on something.
Coordinator for transition - UINavigationControllerDelegate
Create a simple class that will conform to UINavigationControllerDelegate, I won't get into details but the main method which is the answer for your question is navigationController(_:animationControllerFor:from:to:)
Inside body of this method you can retrieve the operation which is being performed (push, pop...), additionally you can make a decision for animator based upon the view controllers (from, to).
In case you want default animator just return nil.
Example
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
switch operation {
case .push: // For push method
return YourCustomAnimator() //Conforming to `UIViewControllerAnimatedTransitioning`
case .pop:
return nil // For pop we want default animation
// I skipped the default case
}
// CASE 2
// Or for example you want to make custom animation only for specific view controller
if toVC is SpecialViewController {
return YourCustomAnimator()
}
return nil // for the rest use default animation
}
I fixed it by calling this:
let navigationController = UINavigationController(rootViewController: firstLaunchVC)
navigationController.modalPresentationStyle = .fullScreen
present(navigationController, animated: true)
This is working exactly how I want it to work, even though I am not quite sure if I am messing up my ViewController-Hirarchy... anyway it is working :D thanks for the help
So when I click on a button from another View Controller, it is suppose to present the next View Controller. However, when clicking the button, it just show the next View Controller with a black screen, even though there is content on it. How do I fix this?
I do know that there had already been similar questions asked but none helped me in solving this issue.
Button clicked code:
#IBAction func onSignUp(_ sender: Any) {
presentDetail(SignUpViewController())
}
Present next View Controller and animation code:
extension UIViewController {
func presentDetail(_ viewControllerToPresent: UIViewController) {
let transition = CATransition()
transition.duration = 0.25
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromRight
self.view.window!.layer.add(transition, forKey: kCATransition)
self.present(viewControllerToPresent, animated: false)
}
func dismissDetail() {
let transition = CATransition()
transition.duration = 0.25
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromLeft
self.view.window!.layer.add(transition, forKey: kCATransition)
dismiss(animated: false)
}
}
To clarify, the presentDetail function suppose to present the next View Controller with an animation (From Right), as shown in the code.
You need to instantiate the target ViewController from Storyboard before presenting it.
#IBAction func onSignUp(_ sender: Any) {
presentDetail(
storyboard?.instantiateViewController(withIdentifier: "putyourIdentifierhere"))
}
instantiateViewController function returns the Viewcontroller you want to present.
I want to push a popover on top of my view controller. Currently, this code resides in my subclass of UIView:
func presentGameOver() {
let transition: CATransition = CATransition()
transition.duration = 0.75
transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
transition.type = CATransitionType.fade
let currentController = (getCurrentViewController() as! UINavigationController).topViewController!
currentController.navigationController!.view.layer.add(transition, forKey: nil)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "GameOverViewController") as! GameOverViewController
vc.modalPresentationStyle = UIModalPresentationStyle.popover
vc.highScore = highScore
vc.score = score
vc.FONT_H = FONT_H
vc.delegate = self
currentController.navigationController?.pushViewController(vc, animated: false)
}
This is my class declaration:
class GridView: UIView, ModalHandler, UIPopoverPresentationControllerDelegate {
I have these two methods as well:
func popoverPresentationControllerShouldDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) -> Bool {
return false
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.none
}
In the storyboard, for the view controller I want to be a popover, I set the content size(this and this).
However, when the popover is shown, it is shown full screen. When I previously used popovers, I would present them. Does popover display not work using pushViewController()?
The animation is quite complicated if you have little experience. The following code may give you some clues.
func presentGameOver() {
let transition: CATransition = CATransition()
transition.duration = 0.75
transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
transition.type = CATransitionType.fade
let currentController = (getCurrentViewController() as! UINavigationController).topViewController!
currentController.navigationController!.view.layer.add(transition, forKey: nil)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "GameOverViewController") as! GameOverViewController
vc.modalPresentationStyle = UIModalPresentationStyle.popover
vc.highScore = highScore
vc.score = score
vc.FONT_H = FONT_H
vc.delegate = self
if let popoverPresentationController = vc.popoverPresentationController{
popoverPresentationController.delegate = self;
popoverPresentationController.sourceView = self
popoverPresentationController.sourceRect = CGRect.init(x: 200, y: 200, width: 500, height: 300)}
currentController.present(vc, animated: false) {}
}
I have a navigation bar in my application that I change the navigation to present modally But I want when user goes to the destination view controller the navigation animation from left to right
here is my codes in the first view controller
#IBAction func showProfilePage(_ sender: UIBarButtonItem) {
self.performSegue(withIdentifier: "showprofile", sender: self)
}
I read apple site for this and use this function too
private func navigationController(_ navigationController: UINavigationController,
willShow viewController: ProfileViewController,
animated: Bool) {
}
But it doesn't work so what should I do to change navigation animation from left to right?
Try this.
let storyBoard : UIStoryboard = UIStoryboard(name: "PatientCheckout", bundle:nil)
let controller = storyBoard.instantiateViewController(withIdentifier: "PatientCheckoutViewController") as UIViewController
let transition = CATransition.init()
transition.duration = 0.45
transition.timingFunction = CAMediaTimingFunction.init(name: kCAMediaTimingFunctionDefault)
transition.type = kCATransitionPush //Transition you want like Push, Reveal
transition.subtype = kCATransitionFromLeft // Direction like Left to Right, Right to Left
transition.delegate = self
view.window!.layer.add(transition, forKey: kCATransition)
self.navigationController?.pushViewController(controller, animated: true)
I have portrait only app with one screen in landscape only mode.
What I did:
I created a UINavigationController subclass and overridden the following:
import UIKit
class LandscapeVCNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .landscape
}
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return .landscapeLeft
}
}
Then using this navigationController to present my landscape viewcontroller like this:
if let vc = storyboard?.instantiateViewController(withIdentifier: StoryBoardIdentifiers.playerVCStoryboardIdentifier) as? PlayerViewController {
let navController = LandscapeVCNavigationController.init(rootViewController: vc)
present(navController, animated: true, completion: {
})
}
This works fine.
What I need:I need the landscape VC to be presented from the bottom(home button side) and should dismiss to the bottom(home button side). This is what happens currently.
You have to add a custom transition animation to change this default behaviour.
let transition = CATransition()
transition.duration = 0.3
transition.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromLeft
self.view!.window!.layer.add(transition, forKey: nil)
self.present(navController, animated: false, completion:nil)