I've been trying to figure out how to use the UINavigationBarDelegate method shouldPopItem with a popover in Swift. I've done lots o' digging around and trying this and that, with no success. I'm hopeful someone can point me in the right direction.
I start with a UIViewController that has a number of buttons the user can click. As an example, there is a button that calls back to this method:
#IBAction func manageVerbListsButtonPressed(sender: UIButton) {
vc = ManageListsViewController(nibName: "ManageListsView", bundle: nil)
vc.preferredContentSize = CGSizeMake(600, 600)
vc.modalPresentationStyle = .Popover
let popoverController = vc.popoverPresentationController!
popoverController.sourceView = sender
popoverController.sourceRect = sender.bounds
popoverController.permittedArrowDirections = .Left
popoverController.delegate = self // could you set the popover delegate to the vc, so the vc could control the dismiss?
presentViewController(vc, animated: true, completion: nil)
}
This correctly opens a popover with ManageListsView as the presenting controller. That controller has a view with a table. When the user clicks on a table row, the didSelectRowAtIndexPath method fires:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let vc = EditOneListViewController()
let vl = Lists[indexPath.row] // this is an array of list names
vl.isNew = false
vc.List = vl // give the next controller the list to edit
navigationController?.pushViewController(vc, animated: true)
}
Again, this correctly pushes the EditOneListViewController.
What I want to do, but haven't figured out how, is to use the UINavigationBarDelegate method shouldPopItem to determine whether the navController should pop back to the table view, depending on whether the user has done something on the EditOneListViewController view. That is, if the user has edited something and not saved it, I want to use the shouldPopItem to put up an Alert indicating that and return to the view so the user can save the edits. (The list has an isDirty boolean that I can test for whether it's been saved.) If the user has saved edits, I want the navController to pop back a level.
I have done this in Obj C in earlier iOS's, but I'm darned if I can figure out how to do it with iOS 9 and Swift and presentationControllers. Any help at all will be greatly appreciated.
Related
I try my best to explain what is happening.
I have updated the XCode to Version 10.1 (10B61)
And the iOS on my iPhone and Simulator is v12.1
My app has a TabController with 5 tabs.
First: Posts
Fifth: Profile Posts
These are embedded into a navigation controller (In case someone
clicks on the comments button)
So. I've noticed that if I run my app and I click the comments, it pushes that vc in a weird way to the screen, then clicking back just "bumps" back. Also slide back isn't working.
However, if I switch tabs first then everything works fine.
VIDEO:
https://www.youtube.com/watch?v=fgS3j21L8Js
As you see in the video everything is fine after switching to Profile Posts + back .
UPDATE 1:
So if I start my app, switch to another Tab, then back to the original, it works fine.
Requested code:
func commentsButtonTapped(sender: UIButton) {
let touchPoint:CGPoint = sender.convert(CGPoint.zero, to:self.tableView)
if let indexPath = tableView.indexPathForRow(at: touchPoint) {
openDetails(indexPath: indexPath, shouldShowKeyboard: false)
}
}
func openDetails(indexPath: IndexPath, shouldShowKeyboard : Bool) {
if (self.tableView.cellForRow(at: indexPath) as? WorldMessageCell) != nil {
let storyboard = UIStoryboard(name: "Additional", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "DetailsViewController") as! DetailsViewController
vc.postId = PostIds.shared.nearby.ids[safe: indexPath.row]
vc.shouldShowKeyboard = shouldShowKeyboard
self.navigationController?.pushViewController(vc, animated: true)
}
}
UPDATE 2:
Solved the problem by forcing the TabController to switch between tabs..
override func viewDidAppear(_ animated: Bool) {
self.selectedIndex = 1
self.selectedIndex = 0
}
But that's not how it should work..
UPDATE 3:
I have tested it, if I make the navigation controller->vc the initial vc (so no tab controller) everything works fine.
But as soon as the navigationcontroller is nested inside the tab, it happens.
I made a new project to test if this is a version specific bug but no, everything works fine there. So the issue must be with my app.
What could generate issue like that (in the video)?
Ohh.. I have found the problem & bug:
So if you have navigation controllers nested into a tab controller that calls it's viewDidLoad() function, then the navigation controller will have problems.
The code I had to remove totally:
override func viewDidAppear(_ animated: Bool) {
// here i had some code ... /
}
Now everything works..
I'm trying to hide/disable a button from another view controller once the back button from a nav bar has been pressed, but I can't figure it out. Basically, I have a view controller where a user can add up to 7 days of workouts (days are displayed as buttons). Whenever they press on a day, they are taken to another view where they are able to select the exercises they wish to do during that day. Once they save and press the back button on the nav bar, I want to disable the button for the day they just added workouts for, so they can no longer press it. I would really appreciate your help! Thanks in advance!
You can simply write the following code in the VC with a condition on back
override func viewWillAppear(_ animated: Bool) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
if appDelegate.isFromPreviousScreen == true{
button.isHidden = true
}
}
Note:- fromPrevious make it true when returning from nextVC
If your question is how to make fromPrevious true when leaving the secondVC:
Then you need to pass a value from there, Or to make that stuff easy as a beginner i would suggest you take a global Variable in AppDelegate
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var isFromPreviousScreen:Bool = false
}
And add the following code in SecondVC:
override func viewWillDisappear(_ animated: Bool) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.isFromPreviousScreen = true
}
Hope this helps.
I have a side menu in my iOS app with several entries. Once I click on one of them, I want to segue to the corresponding view controller via pushing them onto the navigation stack. This is currently done in the following way:
// Called on click event on table cell
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.tableView.deselectRow(at: indexPath, animated: true)
// navigate to the corresponding view controller
switch(indexPath.row){
case 0:
let launchScreenNC = self.storyboard?.instantiateViewController(withIdentifier: "LaunchScreenNC") as? UINavigationController
self.navigationController?.pushViewController((self.launchScreenNC?.viewControllers.first!)!, animated: true)
break
case 1:
let connectionNC = self.storyboard?.instantiateViewController(withIdentifier: "ConnectNC") as? UINavigationController
self.navigationController?.pushViewController((self.connectionNC?.viewControllers.first!)!, animated: true)
break
case 2:
let syncNC = self.storyboard?.instantiateViewController(withIdentifier: "SyncNC") as? UINavigationController
self.navigationController?.pushViewController((self.syncNC?.viewControllers.first!)!, animated: true)
break
default:
// nothing to do
break
}
}
It works fine for switching the first time to a new view controller. However, once initiated I want to keep those instances alive to keep a state of variables associated with each view controller.
What is the best way to do this?
I tried with
self.navigationController?.popToViewController((self.syncNC?.viewControllers.first!)!, animated: true)
or saving them as instance variables within the menu view controller, for example. But none worked so far.
I appreciate any advice.
What is the best way to do this?
The best way is don’t. Once a view controller is popped, let it go out of existence. Preserve its state (the values of its instance properties) and use that to reconstruct the interface the next time; don’t preserve the view controller instance itself.
What is the best way to pass a UInavigationController and also pass variables to a new viewController. I know how to do one or the other but not both at the same time. Thank you in advance
this is my current code
func(){
let vc = storyboard?.instantiateViewControllerWithIdentifier("messagesViewController") as! UINavigationController
let posts = self.postList[indexPath.row]
//this is the var that i want to past
//vc.previousViewMessageId = posts.postKey
self.presentViewController(vc, animated: true, completion: nil)
}
If I understand you correctly, you have a view controller that can present a second VC. And this VC is embedded in a UINavigationController. What you don't know how to do, is to pass data from the first VC, to the navigation controller, then to the second VC.
Here is a brute force solution. It's not beautiful, but it works anyway.
Make your own UINavigationController subclass:
class DataPasserController: UINavigationController {
var previousViewMessageId: SomeType?
override func viewDidLoad() {
if let vc = self.topViewController as? YourSecondViewController {
vc.previousViewMessageId = self.previousViewMessageId
}
}
}
Now you can add a navigation controller in the storyboard, set its class to DataPasserController, and connect the second VC to it as its root view controller.
Now suppose you have got an instance of DataPasserController by calling instantiateViewControllerWithIdentifier, you can do this:
yourDataPasserControllerInstance.previousViewMessageId = posts.postKey
And present the instance!
To pass a value to your Navigation Controller's Root View Controller, you access viewControllers[0] and cast it to the class of your Messages View Controller (the controller that has the previousViewMessageId property):
func () {
let messagesNC = storyboard?.instantiateViewControllerWithIdentifier("messagesViewController") as! UINavigationController
let messagesVC = messagesNC.viewControllers.first as! MessagesViewController
messagesVC.previousViewMessageId = postList[indexPath.row].postKey
presentViewController(messagesNC, animated: true, completion: nil)
}
What you have there is simply presenting a view controller... You are skipping the navigation controller.
What you need to do is present the new view controller inside the navigation controller. Once you have done that, it will show correctly. You can also pass the variables after you've created the vc variable.
This presents the new viewController (vc) within the navigation controller...
self.navigationController?.pushViewController(vc, animated: false)
This sets the variable in the new viewController (vc) (you are correct)
vc.previousViewMessageId = posts.postKey
So complete:
func(){
let vc = storyboard?.instantiateViewControllerWithIdentifier("messagesViewController") as! MessagesViewController
let posts = self.postList[indexPath.row]
//this is the var that i want to past
vc.previousViewMessageId = posts.postKey
navigationController?.pushViewController(vc, animated: false)
}
PS. While not part of the question, I feel I should still mention... Use of the word self should be left to necessity only. In other words, don't use it when it isn't needed. for example self.postList[indexPath.row] :)
https://github.com/raywenderlich/swift-style-guide#use-of-self
I am making an iOS app in swift where I have a tableView with cells, what I want is to transit to another view controller when I click on the cell. Here is my code block for the segue source.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "selectedEvent"{
let cellIndex = tableView.indexPathForSelectedRow();
let tempIndex = cellIndex?.row
let name = eventsManager.createdEvents[tempIndex!].name
let address = eventsManager.createdEvents[tempIndex!].address
let latD = eventsManager.createdEvents[tempIndex!].eventLat
let lonD = eventsManager.createdEvents[tempIndex!].eventLon
let coords = CLLocationCoordinate2DMake(latD, lonD)
}
println("segue fired")
}
Here is the code in the segue destination.
#IBAction func showOnMap(segue: UIStoryboardSegue){
println("segue check")
self.performSegueWithIdentifier("selectedEvent", sender: self)
let showEventController = segue.sourceViewController as EventListViewController
let focusAddress = showEventController.address
let position = showEventController.coords
let name = showEventController.name
var marker = GMSMarker(position: position!)
println("is marker working? I hope so")
marker.title = name
marker.map = self.mapView
}
The println statements are to check if the segue is firing and the first statement "segue fired" prints on the console, and the view in the simulator switches to the destination viewController. Unfortunately, the 2nd part of the code does not execute and I am having trouble figuring out why.
The println("segue check") line will only print if the code inside IBAction is triggered by an event. Make sure IBAction is connected to the proper object in your view controller's view.
Why are you calling self.performSegueWithIdentifier("selectedEvent", sender: self) at the destination view controller's showOnMap function and then appear to be getting an instance of the view controller where you came from?
If you want to save data in your segue destination, you should first define variables in your destination's view controller and then save it at the destination in your prepareForSegue above. Something like this:
if segue.identifier == "selectedEvent"{
...
let coords = CLLocationCoordinate2DMake(latD, lonD)
...
let myDestVC = segue.destinationViewController as MyDestinationViewController
let myDestVC.coords = coords
...
}
Once you've got all your data saved, you can use them where ever you want in your destination's view controller. If you want to use them immediately, you can do so by overriding viewDidLoad viewWillAppear or viewDidAppear,