Collecting data from multiple ViewControllers in Swift
Asked Answered
S

4

6

I'm having multiple viewcontrollers (in a navigation stack or maybe not) and each controllers collects some data based on users input. In the end I need to use those data in the last controller.

So what would be the best approach/design-pattern to implement this scenario?

Solenoid answered 12/5, 2018 at 6:51 Comment(1)
There is a pattern called Coordinator. You create Coordinator class whose purpose is to interact with multiple viewControllers. There are a lot of tutorials (e.g raywenderlich.com/177538/…) – Encaustic
H
12

If you have (a navigation stack ) and you need to collect data from all this view controllers

the best way Not to use User-defaults , or Singleton

for your case you have to do container of view controllers to handle particular Process . Note (UINavigationController, UITabBarController, and UISplitViewController * is just containers)

For example create account that require 4 steps that collect users inputs , each step is represented by ViewController and at end you need to push all this data to API server ,

So create Parent ContainerViewController of child viewControllers and use Delegation to pass data after every step to ParentViewController , ParentViewController will be leader to allow next step and provide Data for this step . Here is how to begin create Your own Container managing-view-controllers-with-container

Do not use a Singleton

Singletons can be accessed directly from anywhere in the app. You have no control. a-lot coupling in your code and make your objects hard to test in the future

Do not use UserDefaults

UserDefaults is to store user preferences that persist between app executions.Anything stored there will stay until you remove, so it is not a mechanism to pass data between objects.

So you have to use architecture pattern in the future

Architecture

Each ViewController should only take care of their own screen . also viewController shouldn't know about each other. if we do this we will remove a-lot of coupling between our view controller classes.

So You can use Coordniator to be The leader that handle all navigation Coordniator

and check how to Integrate with MVVM at MVVM-C

also Viper use this technique to handle navigation check Viper

Hoem answered 12/5, 2018 at 8:34 Comment(1)
I'm just here to emphasize, for the love of god, do not use Singleton or Userdefaults πŸ€¦πŸ»β€β™‚οΈ. Having coupling between your viewcontrollers to know about each other, is not good, but it's faaar better than singleton or storing values in userdefaults. – Rashidarashidi
D
2

Well, the best approach, in my opinion, is to use MVVM (Model-View-ViewModel) architecture pattern.

1) For each of your UIViewControllers, create a separate class (derive from NSObject if you wish) that's you consider to be the "view model" belonging to that UIViewController. Your view controllers are not allowed to access model classes... that's now a job of your "view model" classes.

2) Create a singleton named, say, DependencyManager. Your view models access it to obtain your models (or at least top-level models if they're hierarchial) and anything else they may need like networking services, etc... The DependencyManager acts as a way your unit tests can inject replacement "mock" versions of your models, network services, etc... when it's time to test each viewModel.

3) Create your model classe(s) that contain the data. Your view controllers had collected various data within the UI controls, and given that raw data to your viewModels. The viewModels may mutate that data (or not) and stick it into the appropriate models they acquire from the DependencyManager.

4) A ViewController is also allowed to ask their viewModel for data. So your last ViewController would obtain whatever data it needs from its viewModel.

Remember: ViewControllers should not directly manipulate your model classes. Also, your ViewModels should never reference any UI objects, nor even have a reference to its ViewController.

Side Note: I recommend having each ViewModel conform to a protocol which derives from an empty protocol named something like "MockableViewModelProtocol". You can add a single property to your DependencyManager of type MockableViewModelProtocol that each of your ViewControllers can check before they create their ViewModel, in case a unit test has assigned a mock ViewModel for the ViewController to substitute. Another benefit of such a protocol is for quick & clear understanding of the relationship between a ViewModel and its ViewController. Often you'll have not just properties and methods but also callback properties (closure properties).

So there ya go. That, in my opinion, is the best way to not only design a way to manage and access data among a bunch of viewControllers, but to also test all your classes and their use of that data.

And unlike all the current claims that Storyboards block dependency injection and thus prevent testing of your ViewControllers, that's just bunk. By using a DependencyManager, such that your view controller tests inject mocks there, the tests get the same benefit as if they injected a mock directly into the ViewController, and yet the ViewController is still instantiated by the Storyboard. I've shipped a large app quite successfully with this approach.

Dour answered 12/5, 2018 at 7:13 Comment(4)
Thanks for the explanation. One question- if there are multiple viewcontrollers that needs the same ViewModel, each will create its own instance of it. but wouldn't this create inconsistency between the controllers? (should we update all the viewcontrollers when there's an update available? – Becerra
Lirik, I've never shared a ViewModel with multiple ViewControllers. I'd always have a separate ViewModel for each VC. If there was shared code needed between ViewModel classes, you could use a swift trait (i.e. protocol + extension), or a "manager" class if there was shared state, or a utility class full of class methods. – Dour
Also, Lirik, another way to share some typical code between ViewModels is to use a ViewData class. That's a class that simply wraps a model (that's passed in during init of the ViewData). The ViewData has methods and/or computed properties that provide the model's data in a form that's needed for one or more specific ViewControllers. For example, maybe it's just to capitalize a model property, or something much more complicated than that. You could write different ViewData classes that wrap the same model, because you have ViewControllers with different UI needs regarding that same model. – Dour
Finally, Lirik, the ViewData class offloads model-related code from the ViewModel that would otherwise often be repeated among several ViewModels. It's also a handy way to wrap the implementation of a model class from the ViewController and even ViewModel... such as if your model is a Realm Object today, but maybe a CoreData model in future... – Dour
G
1

There can be multiple ways to achieve that,

  1. Pass the data along the controllers until the last one where you want to use it.

  2. Use a Singleton to store the data from each controller. The only issue with Singleton is that it will persist in your app.

  3. You can also store the data in UserDefaults. After using the data, you can clear it out from UserDefaults.

Let me know if you still face any issues.

Goal answered 12/5, 2018 at 6:56 Comment(0)
V
0

In my opinion the best way to handle this would be to use a Flux design pattern. There is a framework called ReSwift on github that allows for unidirectional data flow similar to Redux.

Even if you do not use this framework specifically, the concept of unidirectional data flow is perfect for this situation. The state gathered from each view controller will exist outside of any view controllers, the view controllers will stay agnostic of each other, and there will be no need for any parent view controllers to act has higher order coordinators. Then on your last view controller, you access the state and use it however you need.

Another benefit is if you need to break out your view controllers into multiple view controllers through containers, i.e. one screen has 2+ view controllers, you will not need to create more infrastructure to pass data between those, which in my experience gets really messy.

Volumetric answered 16/5, 2018 at 14:6 Comment(0)

© 2022 - 2024 β€” McMap. All rights reserved.