I am trying to use InAppSettingsKit from my Swift app (via Swift package dependency to version 3.3.3), and I would like to be able to use the settingsViewControllerDidEnd delegate callback to determine when the user has dismissed the settings dialog, so that I can check for certain conditions that may require additional actions on the user's part.
The Done button was showing up if I pushed the view controller onto a navigation controller, but the code indicates that this method will not fire the Done button delegate callback, so I have been trying to use the present method to show the view controller.
Here is the code that I am using to instantiate and present the settings view controller:
func authenticationSettings(alert: UIAlertAction!) {
let viewController = IASKAppSettingsViewController()
viewController.delegate = self
self.present(viewController, animated: true, completion: nil)
}
And here is what I get, notice no Done button:
I have tried this card method of presenting, and also the full screen method, with no avail.
I tried stepping into the Objective-C code and and from what I could tell, the UIBarButtonItem navigation item was being created and added. Anyone have any ideas on what to try next?
As you may have noticed in the source code, UIBarButtonItem gets added on navigationItem. This item is used only if view controller is part of a navigation controller stack
When you're presenting a new view controller modally it doesn't have a navigation controller in the stack, so to make it work you need to wrap your controller with a UINavigationController:
func authenticationSettings(alert: UIAlertAction!) {
let viewController = IASKAppSettingsViewController()
viewController.delegate = self
let navigationController = UINavigationController(rootViewController: viewController)
self.present(navigationController, animated: true, completion: nil)
}
Related
I'm coding a simple app with swift and I'm stuck at the following point, I have two Controllers that lead to another one, and when I click on the cancel button, it always lead to the root Controller, no matter from where I come.
I have a first controller (UIViewController), that go to the Navigation Controller of my target Controller (the one from which I would like to go back to the right calling Controller).
I have a second controller (UITableViewController), which go directly to my target Controller.
Here's the code of my Cancel button:
// MARK: - Navigation
#IBAction func lendingCancelButton(_ sender: UIBarButtonItem) {
// Depending on style of presentation (modal or push presentation), this view controller needs to be dismissed in two different ways
let isPresentingInAddLendingMode = presentingViewController is UINavigationController
if isPresentingInAddLendingMode {
dismiss(animated: true, completion: nil)
} else if let owningNavigationController = navigationController {
owningNavigationController.popViewController(animated: true)
} else {
fatalError("the LendingViewController is not inside a navigation controller.")
}
}
If I correctly understood (you could then correct me if I'm wrong, I would learn something), it's testing if the ViewController that's presenting my target ViewController is a NavigationController.
So maybe that, as the second Controller (my UITableViewController) is not going through a NavigationController, so the last one calling my target view with a NavigationController is always the UIViewController.
Don't hesitate to tell me if it's not clear enough (too many times the word "Controller" in my post) or if you need additional code.
Try something like this
if let navigationController = presentingViewController as UINavigationController {
navigationController.popViewController(animated: true)
} else if let viewController = presentingViewController as UIViewController {
dismiss(animated: true, completion: nil)
} else {
fatalError("the LendingViewController is not inside a navigation controller.")
}
If i understood you want to use dismiss when you find a UIViewController and to pop the navigation when you find a UINavigationController right?
Ok so I finally found a way to make it working.
My tableViewController was embedded into a NavigationController. I removed it (since I could do without it, according to my need). From this View Controller, I draw a segue that "Show" my target view.
From my other ViewController (this one is embedded into a NavigationController), I draw a segue put that present modally my target view.
With the code provided in my initial post, it's working.
The only thing I didn't understand is why the NavigationController from my TableViewController was likely to cause it not working properly.
Following Apple's documentation for adding and editing information Apple guide here I have a Viewcontroller with a tableview. The tableview contains a header with a "Add new" Button. If a table row is selected the detailViewController is pushed onto the stack. The detailViewController is also embedded in a UINavigationController, as in Apple's docs. If "Add new" is pressed, another segue is performed which presents the UINavigationController modally, which in turns shows the detailViewController. This works fine and the animation clearly shows a modally presented ViewController.
The detailViewController contains a Cancel Button in the NavigationBar. If it is pressed the following code is run:
#IBAction func cancel(_ sender: UIBarButtonItem) {
// Depending on style of presentation (modal or push presentation), this view controller needs to be dismissed in two different ways.
var isPresentingInAddActionMode = false
if let presentingVC = self.presentingViewController{
isPresentingInAddActionMode = presentingVC is UINavigationController
}
streekgidsModel.undoManager.endUndoGrouping()
print("undo grouping ended and undone")
streekgidsModel.undoManager.undo()
if isPresentingInAddActionMode {
dismiss(animated: true, completion: nil)
}
else if let owningNavigationController = navigationController{
owningNavigationController.popViewController(animated: true)
}
else {
fatalError("The MealViewController is not inside a navigation controller.")
}
}
The first if-statement checks if the property presentingViewController is present, and if so if it is of type UINavigationController. If so, the viewController is presented modally and should be dismissed. If not it is pushed onto the stack and the owningNavigationController should pop the detailViewController.
Running this code does not work as described by Apple. The check on the presentingViewController shows it is present, but the type check gives back "invalid". This is treated as false. The test on the owningNavigationController succeeds (I think it should fail) and the popViewController is executed. As there was no push, the view controller is not popped or dismissed and is still visible. A second press on Cancel executes the func cancel again, which results in an error as there is no longer a group started in the undo manager.
Baffling thing is that I have the same code in another viewcontroller, with similar UIViewTable and navigation and it works fine.
So to frame the question: why does this not work the way Apple describes it, why does my other view controller work as it is supposed to? Any input is appreciated.
BTW, the fatal error text is straight from the docs so the naming is not relevant and it is never executed.
I would start with checking who is presenter.
According to Apple docs on this:
When you present a view controller modally (either explicitly or implicitly) using the present(_:animated:completion:) method, the view controller that was presented has this property set to the view controller that presented it. If the view controller was not presented modally, but one of its ancestors was, this property contains the view controller that presented the ancestor. If neither the current view controller or any of its ancestors were presented modally, the value in this property is nil.
If the docs are correct then your presenter should be your "Viewcontroller with a tableview" which, I guess, is not UINavigationController. If that is the case then you should understand why your code fails.
It depends on your context of course, but I would just simplify a check this way:
var isPresentingInAddActionMode = self.presentingViewController != nil
... // your other code
if isPresentingInAddActionMode {
dismiss(animated: true, completion: nil)
}
else if let owningNavigationController = navigationController{
owningNavigationController.popViewController(animated: true)
}
If I understood your question and intent correctly then it doesn't matter for you who (which class) presented your detailVC and you care only about how your detailVC was presented - either pushed in navigation view controller or presented modally. I think just by checking presentingViewController property you can get that information.
As you can see below, the notificationsVC is a part of the TabBarController which is embedded in a navigationContoller(lets call it first nC). Then theres a segue from notificationsVC to the second navigationController which will show the messagesVC.
There's a back button in messagesVC which when pressed should go back to notificationsVC
func backbutton() {
navigationController?.popViewControllerAnimated(true)
}
Now this is obviously not working because the navigationController will get the nearest NC and pop the VC in its stack but it won't let me go back to the notificationsVC.
Any other alternative?, although I've tried this with no success as well.
self.dismissViewControllerAnimated(true, completion: nil);
More detailed view
Also I'm using the JSQMessagesViewController library to show the messages in messagesVC which shouldn't matter but still worth mentioning. Thanks for your time!
You can access first NavigationViewController by asking it from TabBarViewController like in code below:
tabBarController?.navigationController?.popViewControllerAnimated(true)
Also asking navigation controller from you second navigation controller should work:
navigationController?.navigationController?.popViewControllerAnimated(true)
Your Navigation controller has only one VC i.e MessagesVc. So when you pop it,there is no other VC in the Navigation Controller's stack which can be presented. Your NotificationsVC is not in the Navigation controller's stack.
So I suggest you to do like this on back button click:
tabBarController?.selectedIndex = Index_Of_NotificationsVC
Try : -
let nVC = self.navigationController?.tabBarController?.navigationController?.storyboard?.instantiateViewControllerWithIdentifier("NotificationStoryboardVC_ID") as! NotificationVC
navigationController?.tabBarController?.navigationController?.pushViewController(nVC, animated: true)
I have a question that probably already has been answered but I just can't find it.
I have a view that calls some webservice and when the result comes back I want to open another view to present the result. Right after the call to WS I close that window.
Now my problem is that when I try to open it with presentViewController with
UIApplication.sharedApplication().keyWindow?.visibleViewController!.presentViewController or
UIApplication.sharedApplication().keyWindow?.visibleViewController!.childViewControllers[0].presentViewController
the same is if I access to rootViewControler.
I get the error that the view is not in the hierarchy any more since it already called viewWillDisappear.
So is there any way to show a view over all views no matter what is the current top view?
EDIT:
The solution was really ugly and stupid. I have a static variable which gets changed every time viewDidApear is called to that view. This way I have the last view that is shown. I know this isn't good way to do it or maybe even not safe way, but I don't have any other idea.
Yes, there is a way to show ViewController on top of another, but for this you should not touch your window at all.
In your view controller class right after your call completed call
let viewController = ViewController() //change this to your view controller class that you want to present
self.presentViewController(viewController, animated: true, completion: nil)
Call web service and navigate your view controller in main thread using below code.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do some background task
dispatch_async(dispatch_get_main_queue(), ^{
// update UI here
// navigate your view controllers
let vc: UIViewController = (self.storyboard?.instantiateViewControllerWithIdentifier("viewContIdentifire"))!
self.presentViewController(vc, animated: true, completion: nil)
// **OR**
self.navigationController?.pushViewController(vc, animated: true)
});
);
Navigate back to VC in more convenient way using
// using this navigate your app to root view of ui stack
self.navigationController?.popToRootViewControllerAnimated(true)
// using this line of code navigate to any view
self.navigationController?.popToViewController(viewContName, animated: true)
// this navigate back to last view
self.navigationController?.popViewControllerAnimated(true)
I am new to Swift and have been struggling with this for a while.
I am trying to redirect users based on login status to either "Login" viewcontroller or "Home" viewcontroller.
"Home" viewcontroller has NavigationBar and TabBar and it shows a table. But, when i use the following code, i see only the table. NavigationBar and TabBar are not shown at all.
var view = self.storyboard?.instantiateViewControllerWithIdentifier("HomeViewController") as UIViewController
self.presentViewController(view, animated: true, completion: nil)
Here is my storyboard : https://www.dropbox.com/s/dkcz45n8000gua6/storyboard.png?dl=0
Any help would be appreciated!
Edit (used TabBarController):
var view = self.storyboard?.instantiateViewControllerWithIdentifier("MainTabController") as UITabBarController
self.presentViewController(view, animated: true, completion: nil)
Your problem has nothing to do with being new to swift; it has to do with not understanding storyboards. If you instantiate HomeViewController, and present it, then that's all you will get. The fact that it's embedded in a tab bar controller and navigation controller in the storyboard is not "known" to this bare controller you just instantiated. You should instantiate the tab bar controller instead, and present it. It will take care of instantiating any of its children, and the navigation controller will take care of instantiating its root view controller.