Presenting viewController in navigationController - ios

I am having a problem with my project.
I created a custom transition from one VC to another. Worked fine, but my project is expanding and so I needed a navigation controller.
func itemButtonTapped(item: Item?) {
if let item = item {
let itemVC = storyboard!.instantiateViewControllerWithIdentifier("ItemViewController") as! ItemViewController
itemVC.item = item
itemVC.transitioningDelegate = self
//navigationController?.presentViewController(itemVC, animated: true, completion: nil) // #1
//navigationController?.pushViewController(itemVC, animated: true) //#2
}
}
The code above is meant to add viewController to existing navigation controller.
Option #1 -- it uses my custom transition and presents VC but it does not place it in the existing navigiationController
Option #2 -- it does not use my custom transition, but presents VC embeded in the existing navigationController
What should I do to combine these options, so I can show my VC using custom transition and add it to existing navigationController?

UINavigationController is a special case and you can't just use a transitioning delegate as you do for normal view controllers. Instead, you need to conform to the UINavigationControllerDelegate protocol and through those methods, provide the custom transition animation you want to implement.
Documentation here:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UINavigationControllerDelegate_Protocol/

Related

Best way to present a view controller

i'm working on multiple frameworks and my question is just a "philosophic" one.
I created an utility function to show view controllers
static func presentViewController(identifier: String, storyboardName: String = "Main", presentationStyle: UIModalPresentationStyle = .fullScreen){
let storyboard = UIStoryboard(name: storyboardName, bundle: InternalConstants.bundle)
var viewResult: UIViewController
if #available(iOS 13.0, *) {
viewResult = storyboard.instantiateViewController(identifier: identifier)
} else {
viewResult = storyboard.instantiateViewController(withIdentifier: identifier)
}
viewResult.modalPresentationStyle = presentationStyle
var top = UIApplication.shared.keyWindow?.rootViewController
while top?.presentedViewController != nil {
top = top!.presentedViewController
}
top!.present(viewResult, animated: true, completion: nil)
}
First of all, is this a correct way to present a view controller or is there a better way?
Then, is it better to present a view controller in a navigation controller or not?
First of all, is a correct way to present a view controller or there is a better way?
instead of a utility make it inside
extension UIViewController {
func ......
}
Then, is better to present a view controller in a navigation controller or not?
a nav is oriented for push/pop but it's also not wrong to use it to present another vc
First of all, is this a correct way to present a view controller or is there a better way?
as long as it's working then it's correct it's just your way of doing this specific thing, but is it the right thing to do as for an iOS and UIKit standpoint the answer is no it's usually is a bad thing to present a viewController by looking at the rootViewController's presentedViewController because it's not guaranteed that the last presentedViewController you find is a good thing to present on it and you won't know until it breaks, that presentedViewController could be a UISearchController and if you use UIContentContainer or ContainerView from storyboards, you might have a small viewController that is just a UISlider at the end, this could be bad for viewController appearance and disappearance
another problem that you will face is when you need to pass data to and from the viewController that you presenting by using this approach you don't even have a reference to the viewController you are presenting, because you are only passing an identifier
from an MVC standpoint you should never try to present viewController from a UIView by calling your function from your view directly Thats Bad Practice
if you take a look at the UIKit SDK if you ever try to present any system UIViewController you will find that you have the responsibility of instantiating and presenting the vc for example UIImagePickerController, UIActivityViewController, UIDocumentPickerViewController, UIDocumentMenuViewController, UIPrinterPickerController, UIVideoEditorController
Apple themselves didn't go for providing a function to present theirs system vcs
instead if you are developing a framework and don't want to give users access to your viewControllers you should make you own window and give it a rootViewController
Apple also has many examples for this too, in the AuthenticationServices framework for security reasons you should not have a reference to the safari web browser they have something called ASWebAuthenticationSession that controls the flow of presenting and dismissing the Safari Web ViewController by calling start() and cancel() functions
also the users of your framework will not always want to present your viewController with the default presentation animation they might want to use custom viewContollers animations which they will need access to the transitioningDelegate property on the viewController
imagine every public useful property on UIViewController will not be accessible if you go with this approach
Then, is it better to present a view controller in a navigation controller or not?
as for this part it's totally fine to present anything on a navigationController
Storyboards headaches
as for the storyboards initialization headaches there are plenty of articles out there talking about optimizing the storyboards initialization call site for that I would recommend doing something like this
extension UIStoryboard {
enum AppStoryBoards: String {
case
login,
main,
chat,
cart
}
convenience init(_ storyboard: AppStoryBoards, bundle: Bundle? = nil) {
self.init(name: storyboard.rawValue.prefix(1).capitalized + storyboard.rawValue.dropFirst(), bundle: bundle)
}
}
This way you can initialize a storyboards using enum which improves the call site to be like this
let login = UIStoryboard.init(.login)
then you can have another extension for view controller initialization like this
extension UIStoryboard {
func instantiateInitialVC<T: UIViewController>() -> T {
return self.instantiateInitialViewController() as! T
}
func instantiateVC<T: UIViewController>(_: T.Type) -> T {
return self.instantiateViewController(withIdentifier: String(describing: T.self)) as! T
}
}
and you can then call it like this
let loginVC = UIStoryboard.init(.login).instantiateInitialVC()
or this
let loginVC = UIStoryboard.init(.login).instantiateVC(LoginViewController.self)
by doing that you improve your overall code for presenting any viewController
let dvc = UIStoryboard.init(.login).instantiateVC(LoginViewController.self)
dvc.plaplapla = "whatever"
present(dvc, animated: true)

How I could clean UINavigationBar transitions history?

I currently have parental "menu" TableView with UINavigationBar and from each cell there is a segues by reference outlet to 3 similar Views with different information.
In each View there is a buttons to other 2 Views.
With every button's segue opens another View.
The problem:
From every View UINavigationBar's back button returns me to previous View but i tries to make back button to "menu".
Additional Bar Button Item and segue from it makes very close effect but segue animation is not like in UINavigationController.
How I could clean UINavigationBar transitions history in segue to initial View?
You can try pop to root view controller or You can edit navigation controller viewControllers property and remove/add some VC in between.
You can try Unwind Segue mechanism too.
Here are some methods(function) that navigation controller providing for pop operations. They are returning optional UIViewController (intance) from it’s navigation stack, that is popped.
open func popViewController(animated: Bool) -> UIViewController? // Returns the popped controller.
open func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? // Pops view controllers until the one specified is on top. Returns the popped controllers.
open func popToRootViewController(animated: Bool) -> [UIViewController]?
Here is sample code as a solution to your query::
// if you want to back to root of your app
if let rootNavigationController = self.window?.rootViewController as? UINavigationController {
rootNavigationController.popToRootViewControllerAnimated(true)
}
// But if you want to back to root of your current navigation
if let viewcontroller = self.storyboard?.instantiateViewController(withIdentifier: "NewViewController") as? NewViewController { // or instantiate view controller using any other method
viewcontroller.navigationController?.popToRootViewControllerAnimated(true)
}

Trigger events when ViewController covered by a presented ViewController

I would like to process code when a ViewController is no longer visible due to presenting a new ViewController.
I cannot use ViewWillDisappear etc since the controller is not technically ever dismissed from the stack - you just can't see it.
What process can I use so that code runs when the controller is no longer visible (i.e. topmost) and when it becomes visible again?
EDIT:
Seems some confusion here - not sure why.
I have a viewcontroller.
I use the following code to present another controller
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navController = storyboard.instantiateViewControllerWithIdentifier("NavController") as! UINavigationController
let thisController = navController.viewControllers[0] as! MyController
self.presentViewController(navController, animated: true, completion: nil)
This controller does not trigger a viewWillDisappear on the previous controller since the previous view is not removed - just hidden.
I need to process code when this view is hidden (i.e. not visible) and, more importantly, process code when it becomes visible again.
When presenting a UIViewController if the presentation style has been set to UIModalPresentationOverCurrentContext it doesn't call the viewWillDisappear and related methods as the view never disappears or gets hidden.
A simple test to check if thats the case would be to set the NavController that you are using to have a clear background color. If you do this and present the NavController and you can still view the first UIViewController below your NavController content. Then you are using UIModalPresentationOverCurrentContext and that is why the viewDidDisappear isn't called.
Have a look at the answer referenced by Serghei Catraniuc (https://stackoverflow.com/a/30787112/4539192).
EDIT: This is in Swift 3, you can adjust your method accordingly if you're using an older version of Swift
If you won't be able to figure out why viewDidAppear and viewDidDisappear are not called, here's a workaround
protocol MyControllerDelegate {
func myControllerWillDismiss()
}
class MyController: UIViewController {
var delegate: MyControllerDelegate?
// your controller logic here
func dismiss() { // call this method when you want to dismiss your view controller
// inform delegate on dismiss that you're about to dismiss
delegate?.myControllerWillDismiss()
dismiss(animated: true, completion: nil)
}
}
class PresentingController: UIViewController, MyControllerDelegate {
func functionInWhichYouPresentMyController() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navController = storyboard.instantiateViewController(withIdentifier: "NavController") as! UINavigationController
let thisController = navController.viewControllers[0] as! MyController
thisController.delegate = self // assign self as delegate
present(navController, animated: true, completion: {
// place your code that you want executed when it disappears here
})
}
func myControllerWillDismiss() {
// this method will be called now when MyController will dismiss
// place your code that you want executed when it re-appears here
}
}
Firstly, thanks to Serghei for his time in helping work through this.
To clarify, both my potential presented controllers were set to Full Screen presentation style in the storyboard, however one was being set to Custom via a piece of pasted code dealing with the presentation. I can't find the error with the other.
However, if I force a presentation style of Full Screen as part of the presenting process then all is ok.
Hopefully my frustrating afternoon can help to save someone else's - always try to understand the implications and processes involved in pasted snippets.

Changing root controller of a tab bar's navigation controller

I have a tab bar in my app and one of the buttons is "Profile" which should present one VC if user not authorized and another if user already authorized.
So... Things work, but not correctly. Looks like I messed up some root controllers. Sometimes I cant pop up vc or change the color of a nav bar title.
I create a custom navigation controller with that code inside
if NSUserDefaults.standardUserDefaults().objectForKey("userId") != nil {
let newController: UserViewController = self.storyboard?.instantiateViewControllerWithIdentifier("userViewController") as! UserViewController
self.setViewControllers([newController], animated: false)
} else {
let newController: LoginViewController = self.storyboard?.instantiateViewControllerWithIdentifier("loginViewController") as! LoginViewController
self.setViewControllers([newController], animated: false)
}
Is there a way to implement in correctly?
You're trying to instantiate a Navigation Controller - which (as #PierreMB had mentioned) won't work. You should instead create two regular View Controllers with storyboard ID's "userViewController" and "loginViewController", and instantiate them.
Create a UINavigationController subclass, and put this code (which you wrote, slightly modified) in its viewWillAppear() method (as a bonus, you can pass the 'animated' parameter free of charge):
if NSUserDefaults.standardUserDefaults().objectForKey("userId") != nil {
let newController = self.storyboard?.instantiateViewControllerWithIdentifier("userViewController") // drop the 'as! UINavigationController'
self.setViewControllers([newController], animated: animated)
} else {
let newController = self.storyboard?.instantiateViewControllerWithIdentifier("loginViewController") // drop the 'as! UINavigationController'
self.setViewControllers([newController], animated: animated)
}
The function instantiateViewControllerWithIdentifier() by default returns a UIViewController, which is what you really want to display.
Are you trying to put a UINavigationController as a root of another UINavigationController ? Because it is prohibited and doesn't work well.

Swift can't fixed navigation bar for UITableView

I am trying to display a fix navigation bar for my UiTableViewController, I have a first ViewController and when I click on it, this will open my UITableViewController Here is the code of the click :
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("MyTableViewController") as! MyTableViewController
vc.myObject = object // I pass some data
presentViewController(vc, animated: true, completion: nil)
the UItableView is correctly display but not navigation bar appear, if I add one, the navigation bar scroll with the table view and I don't want this behavior.
I tried this without success :
Go to the Editor menu, and click on the Embed In submenu, and choose
Navigation Controller
And tried to change some settings here :
Actually, in your case you want to show navigation and for navigation you have to push your view controller to a UINavigationController thats why the solution is :
let vc = storyboard.instantiateViewControllerWithIdentifier("MyTableViewController") as! MyTableViewController
vc.myObject = object // I pass some data
self.navigationController?.pushViewController(vc, animated: true)
presentViewController offers a mechanism to display a modal view controller; i.e., a view controller that will take full control of your UI by being superimposed on top of a parent controller & establish a parent child relation b/w presenting & presented view controllers.
where as
pushViewController offers a much more flexible navigation process where you can push & pop a new controller to UINavigationController, so to go back to the previous one, in a ordered way. Imagine that controllers in a navigation controller will just build a sequence from left to right like building a stack of view controllers stacking upon each other.
Do it this way:
let vc = storyboard.instantiateViewControllerWithIdentifier("MyTableViewController") as! MyTableViewController
vc.myObject = object // I pass some data
self.navigationController?.pushViewController(vc, animated: true)

Resources