I have a controller that chooses one of 2 segues and executes them immediately at viewDidAppear:. One of them leads to a UINavigationController and the other leads to a UITabBarController and both of them implement preferredStatusBarStyle.
At some point the user can open a overlay controller that will check for the presented view controller and replicate the preferredStatusBarStyle
My problem is that I can never get the current view controller being displayed. I'm using the code bellow to get the current controller but it is always returning the first controller ever showed (the storyboard root view controller) and not the current one.
internal override func preferredStatusBarStyle() -> UIStatusBarStyle {
if let rootViewController = UIApplication.sharedApplication().delegate?.window??.rootViewController {
return rootViewController.preferredStatusBarStyle()
} else {
return .Default
}
}
Am I doing something wrong?
Related
I have a view controller that calls another view controller with present(newViewController.... However, whenever the function that calls this present function is called, the new view controller pops up on the screen for a second before being dismissed again. There are no functions in this new view controller to dismiss itself.
The only thing I can think of that may be causing this is my use of SnapKit and overriding of viewDidLayoutSubviews:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let data = data {
return
} else { setupAVFoundation() }
}
data is an optional value that is set by the time I present the new view controller, so viewDidLayoutSubviews returns at the if let statement. However I have confirmed that upon presenting the new view controller, viewDidLayoutSubviews gets called like four times. I can't figure out why this is happening or why it would interrupt presentation of a new view controller.
Here is where the new view is presented:
func found(code: String) {
print(code)
data = code
present(ItemSelectViewController(), animated: true, completion: nil)
}
My app has a navigation controller where the first view controller is a login screen and all other screens are pushed on top.
I would like to unwind to the login screen whenever any http request to the backend returns a 401 error.
What I had in mind was to add an extension to the ViewController class with something like this:
extension UIViewController {
func unwindToLoginScreen() {
performSegue(withIdentifier: loginScreen)
}
}
And the segue would be an unwind segue. Then, whenever the request fails I call the view controller's unwindToLoginScreen method.
However, the problem with this approach is that I would have to remember to create said unwind segues on the storyboard for all new view controllers that I added to the project.
So far I think the ideal plan would be to be able to create the unwind segue programatically instead of using the storyboard. So, my unwindToLoginScreen() extension method would work in any new view controller by default. Something like:
extension UIViewController {
func unwindToLoginScreen() {
let segue = UnwindSegue(identifier: "blablah", segue: LoginViewController.unwindToLoginViewController)
segue.perform()
}
}
Is it possible to do something like this?
You can't create segues in code, but you can pop to the root of the UINavigationController's stack:
func returnToLoginScreen() {
self.navigationController?.popToRootViewController(animated: true)
}
If you want to pop to a viewController that isn't the root, you can find it in the array of viewController's managed by the UINavigationController and then call popToViewController:
// pop to second viewController in the stack
self.navigationController?.popToViewController(self.navigationController!.viewControllers[1], animated: true)
... or search for the ViewController by type:
if let loginVC = self.navigationController?.viewControllers.first(where: { $0 is LoginViewController }) {
self.navigationController?.popToViewController(loginVC, animated: true)
}
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)
}
I have 'n' ViewControllers, all taking a common variable 'cv1', I would like to set this cv1 before the controllers are loaded.
Everything is fine till I set only 4 controllers, (i.e) without More tab item. and once I introduce more controllers, delegate "tabBarController:didSelectViewController" doesn't trigger for the modules in UIMoreNavigationController.
So I tried setting the UINavigationController delegate for my TabBarController Class and handled the other module tabItem selection like
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
if navigationController != self.moreNavigationController
{
//navigation happening in some other tab Item
return
}
if navigationController.viewControllers.count != 2
{
//The first controller of the more tabItem will be MoreViewController, followed by our desired controller in the navigation stack
return
}
guard let selectedController:MyTabItemViewController = self.getTopViewController(from: viewController) else {
print("***WARNING*** Selected controller doesn't seem to expect info :\(String(describing: selectedViewController))")
return
}
selectedController.sp = self.sp
selectedController.sb = self.sb
}
I am able to get the ViewController instance, but the viewDidLoad method gets called before UINavigationController's willShow method, I would like to set the variables before the viewDidLoad method as the UI structuring and data fetch depends on this variable.
I'm running into an issue where after changing the rootViewController on my UINavigationController and changing it back to my original UINavigationController, a UISplitViewController begins to show both it's master and detail view in a phone device on compact/portrait orientation (so not only on plus size phones, but also others).
Basic overview of architecture:
A TabBarController houses several tabs. One of these tabs is a UISplitViewController. I currently override the following to ensure that the MasterViewController is shown on compact orientations:
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
// this prevents phone from going straight to detail on showing the split view controller
return true
}
This works fine and displays the master on portrait as expected. At any point pressing a button on another tab can create a new UINavigationController instance and display it, in which I'm doing the below to change the rootViewController to the newly created UINavigationController to display:
let appDelegate = UIApplication.shared.delegate
appDelegate?.window??.rootViewController = newNavVC
On dismiss, I'm just swapping the UINavigationController back to the original one through the same code above. However, once I do this one time (create nav/display/dismiss), and I switch my tab back to the one with the UISplitViewController, it changes itself to show a side-by-side master detail view. I didn't know this was possible in portrait mode for compact sizing. I tried changing to any of the 4 preferred display modes in the UISplitViewController, but that didn't fix it.
Below is what it looks like (iPhone 6 simulator), am I missing delegates or misunderstanding collapsing?
Before:
After:
You can replace the the logic that assigned the rootViewController with the code snippet found at this link:
Leaking views when changing rootViewController inside transitionWithView
Basically you just create an extension for the UIWindow class that will set the root view controller correctly.
extension UIWindow {
/// Fix for https://stackoverflow.com/a/27153956/849645
func set(rootViewController newRootViewController: UIViewController, withTransition transition: CATransition? = nil) {
let previousViewController = rootViewController
if let transition = transition {
// Add the transition
layer.add(transition, forKey: kCATransition)
}
rootViewController = newRootViewController
// Update status bar appearance using the new view controllers appearance - animate if needed
if UIView.areAnimationsEnabled {
UIView.animate(withDuration: CATransaction.animationDuration()) {
newRootViewController.setNeedsStatusBarAppearanceUpdate()
}
} else {
newRootViewController.setNeedsStatusBarAppearanceUpdate()
}
/// The presenting view controllers view doesn't get removed from the window as its currently transistioning and presenting a view controller
if let transitionViewClass = NSClassFromString("UITransitionView") {
for subview in subviews where subview.isKind(of: transitionViewClass) {
subview.removeFromSuperview()
}
}
if let previousViewController = previousViewController {
// Allow the view controller to be deallocated
previousViewController.dismiss(animated: false) {
// Remove the root view in case its still showing
previousViewController.view.removeFromSuperview()
}
}
}