How to pass data between multiple (in my setup 3) ViewControllers? - ios

I want to make an Flashcards App and the behaviour is that on the CoursesVC, the user can add courses and click on them. Then he gets the list with flashcards. There he can add more flashcards. The storage is managed by CoreData. When its clicked on the cell, I pass the data to the flashcards list with prepareForSegue. To add the flashcard, I had the same idea in mind, but it was not possible because the variable from the second view controller wasn't initialised, when prepareForSegue was created. Question: How can I pass a NSManagedObject from the first ViewController to the third ViewController in an appropriate way? (ugly way would be to let the view render before creating prepareForSegue)
The difference to questions like "how to pass data between ViewControllers" is that I have three ViewControllers. It won't work with using prepareForSegue at the first and at the second view controller, because when the prepareForSegue is created, the variable in the second VC is not defined yet, because the view is not initialised yet! Keep in mind that the segue from the second to the third view controller is "Present Modally" as "Page Sheet"!

This is the solution basically: Swift : prepareForSegue with navigation controller
The problem was that the third view controller is embedded as a navigation controller. That is the reason why the prepareForSegue is different.
Solution is to use following prepareForSegue in the second VC:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let navigationVC = segue.destination as? UINavigationController, let myViewController = navigationVC.topViewController as? DViewController {
myViewController.currentCourse = self.currentCourse
}
}

Related

Pass Data to Detail View by Tapping TableView Cell

I'm trying to make a communication between tableview and it's detail view
Please can you check my way and give me some advices
So, I embedded Navigation Controller to Table view
and I didn't use tableview(_:didSelectedRowAt) method.
Some answers in Stackoverflow, they said override prepare(:) method and write theperformSegue(withIdentifier:) method in the tableview(_:didSelectedRowAt)
but if i write the code like above two screens were shown.
(I think because segue action are triggered twice)
I just drag and drop the segue action(push) to Detail View from table view cell (Friends Name Cell)
By using this segue action, i can pass the data by prepare(:segue) method for editing selected friend name at the Detail View
and if i edit friend name from detail view's text field, there is edit button which trigger the unwind segue
so I override prepare(:segue) method in Detail View Controller
and wrote code below at Table View's ViewController
#IBAction func getEditedNameFromDetailView(_ sender:UIStoryboardSegue){
if sender.source is DetailViewController {
if let senderVC = sender.source as? DetailViewController {
data[(self.someTableView.indexPathForSelectedRow?.row)!] = senderVC.editedData!
}
someTableView.reloadData()
}
}
is this a proper way to communicate table view and its detail view?
From your description it is possible that you have 2 segues. one in the StoryBoard and one in your code.
The prepare method is not performing the segue. it simply gives you a chance to perform actions that are related to the segue, for example pass data to the destinations controller. do not call perform segue if you already created one in the StoryBoard and vice versa.
a common usage of prepare will look like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//get the destination controller and cast it to your detail class
if let destinationController = segue.destination as? YourDetailClassHere {
//set the properties you want
destinationController.someProperty = someValue
}
}
Few notes -
you can use the sender object to pass data from the perform call.
common mistake is to assume that the destination controller is always your detail controller. it can be the navigation controller and then you will have to extract the detail from the navigation controller.

Issue: Transferring View Controllers through segue depending on what button is pushed

My issue is that I have two view controllers, and I need to transfer a string from one view controller to the next. I already have that down from a segue. The actual issue is that I need a label to change in the next view controller to make it match the currentTitle of the button in the previous view. And yes, there are multiple buttons.
My code from view 1 is this:
#IBAction func whichButtonWasClicked(sender: AnyObject) {
clickedButton = sender.currentTitle!!
}
// Segue - Pass the Data of which lesson was chosen
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let DestViewController : Lesson = segue.destinationViewController as! Lesson
DestViewController.lessonPlan = clickedButton
}
I connected all my 7 buttons to that same one action.
The problem is that when I transfer view controllers, and do...
print(lessonPlan)
It does not give me the title of the button from the action, because I guess that action is after the segue. And yes I tested it, and it IS after the segue. SO it does work, it's just a bit delayed, and I need it to be a part of the segue.
Since your IBAction is being called after prepareForSegue, you can use programmatic segues.
Right now, you're probably dragging a segue from your button to the next view controller. Instead, you can drag a segue from the current view controller to the next one (see this answer for a visual guide). This will create a programmatic segue that you can trigger whenever you'd like from code. Remember to give the segue an identifier in Xcode.
Then in whichButtonWasClicked, call self.performSegueWithIdentifier("identifier you setin Xcode", sender: sender) after you set clickedButton in order to trigger the segue. By doing so, you'll ensure that your button action is called before the segue starts.

UINavigationController "acting crazy" after I added a new initial UIViewController

I had a UINavigationController set as initial view controller. The root view controller for that navigation controller shows a menu where users can see product categories (In case that's interesting for anyone :)). The root view controller loads JSON data from an URL, so it takes a little while until the view shows up (which is fine). That data will be use in all the app, so it's an important step.
Well, since the data load takes a while, I added a new UIViewController where I show an image with an UIActivityIndicator. Of course, the data load takes place in this UIViewController now instead of the root view controller.
So, the idea is that after the data is loaded, the root view controller takes the place. To accomplish this I put an "invisible" UIButton on the new view controller and added a UIStoryboardSegue to the navigation controller, so I trigger that segue programmatically after the data is loaded.
After the data gets loaded, the navigation controller actually appears and shows (of course) the root view controller. The problem is when I click any button on the root view controller, the segue performs correctly, but from the "second" view controller (UITableViewController) if I press any cell, it takes me back to the new view controller (the one that shows an image and loads the data). Well not actually any cell, because sometimes it takes me actually forwards to the "third" view controller (also UITableViewController), but the same problem occurs in this view controller. If I click the back button on the navigation bar in any view controller, it takes me back to the new view controller and not really back in the navigation stack.
Here is ViewDidLoad:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
load.loadAllProducts()
load.loadCategories()
self.performSegueWithIdentifier("mainStoryboardSegue", sender: self)
}
And PrepareForSegue:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destination = segue.destinationViewController as? UINavigationController
let homeViewController = destination!.viewControllers.first as? HomeViewController
homeViewController!.categories = load.categories
homeViewController!.products = load.products
}
I really appreciate any help, thanks a lot!
The solution that worked for me was to change the current active view controller by setting this:
let newController = storyboard.instantiateViewControllerWithIdentifier(storyboardId) as? UINavigationController
UIApplication.sharedApplication().keyWindow!.rootViewController = newController

segue.identifier = nil in second view

I have IOS Swift program, using Storyboards with NavigationController.
There are two Views, lets call them mainView, secondView.
From the mainView I have BarButtonItem to go to secondView. When pressing that button, it triggers prepareForSegue function in the mainView with segue.identifier = "secondView"
When I have opend e.g. the secondView, I have two BarButtonItems for Cancel and Save. When pressing either of them the prepareForSegue function in that view is triggered, but now the segue.identifier = nil.
I would have expected to have the segue.identifier = "cancel" or "save" depended on the button pressed in that view.
Am I misunderstanding the segue functionality? Can anyone try to enlight me about this, as this looks like a very important and useful part of storyboards and navigation - but somehow I am not getting it right.
Have you created actions for the cancel and save buttons on your second view?
Right click and drag from your storyboard to the view controller code and select action from the dropdown.
Then in the action method, perform your segue.
#Garret, #rdelmar, #syed-tariq - thank you for pointing me into the right direction.
It turned out that Unwind Segue got me on track: Xcode Swift Go back to previous viewController (TableViewController)
But I also found one error I was doing in my storyboard, as I had Navigation Controller on all views (yes, I know - stupid when you know better): How do I segue values when my ViewController is embedded in an UINavigationController?
The final puzzle was to learn about the Protocols and Deligates to get this all to work.
Putting this together, then in short:
I created a protocol in my second view (above the class)
protocol MyDelegate: class {
func getMyList(sender: MySecondView) -> [NSManagedObject] //returns a CoreData list
}
Then I created a delegate variable in my second view
weak var datasource: MyDelegate!
In my first view I implemented the protocol, which was just one simple function returning a list that I needed in my second view
In my first view I have prepareForSegue where I catch the correct segue.identifier and there I set the delegate by going through segue.destinationViewController, like this
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
if segue.identifier == "mySegue" {
let vc = segue.destinationViewController as! MySecondView
vc.delegate = self
}
}
and that was about it - now magically the flow is correct, segues happening, deligates passing correctly, and all good :)

Is possibile to use the same view controller to show different content for each?

I explain my question: in my iOS app, written in Swift, I have a mapview, using mkmapkit, where i added my mkpoint annotations, seven in total. When i tap on each annotation callout, it opens a new view controller using performSegueWithIdentifier.
My question is: Do I need to use seven different view controllers, one for each annotation callout tap, or can I use just one view controller? Because the view controller design must be the same for all annotation callout taps, but with different content data for each one.
Sorry for my bad english. I hope to figure out to this problem
When you perform the segue via performSegueWithIdentifier, prepareForSegue is called. Override this method to pass data to the destination ViewController:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "mySegueIdentifier" {
let destinationViewController = segue.destinationViewController as MyDestinationViewController
// pass data to destinationViewController
destinationViewController.myProperty = ...
}
}
Use prepareForSegue to pass individual information to the same view.
Of course you should use only one ViewController. You just need to pass data from your MapViewController into ViewController through performSegue. And display it.
This pattern called Master-Detail and you can find more here:
An iOS 8 Swift Split View Master-Detail Example

Resources