Is there any way of passing data from 1st view controller to (say) 3rd view controller without passing the data through the 2nd view controller?
I actually have a final submit button on the 4th view controller which gathers all the data right from the 1st view controller.
I want the data of each view controller to be directly transferred to the 4th view controller where the submit button is, without going through the view controllers to reach there.
I have already tried passing data through view controllers think there can be a more clear way of directly transferring data specially images as these are the main part of my data.
You could use a "Model" for this purpose with a delegate pattern.
A model is a class (or struct) which can be accessible by several VCs.
The delegate is going to be used to "notify" that a property value has changed.
/// Should be implemented by your VC
protocol MyModelDelegate: AnyObject {
func dataToShareChanged(to data: dataToShare)
}
/// Use the same instance for the VC1 and VC4
final class MyModel {
weak var delegate: MyModelDelegate?
var dataToShare: Foo {
didSet { delegate?.dataToShareChanged(to: dataToShare) }
}
}
In your case by the 1th and the 4th. Each of those VC should have the same instance of the model. You can achive this by giving the model object to the VCs if you initialize them.
If you are working with storyboards, you have to assging the models in the "viewDidLoad" for instance.
So you VC would look like:
class MyController: UIViewController, MyModelDelegate {
var model: MyModel?
func viewDidLoad() {
...
model.delegate = self
}
// Implementation of the delegate function.
func dataToShareChanged(to data: dataToShare) {
/// assign the new data value here
}
}
If you use this approach, you would not need to pass data though the VCs at all. Simple assign the new value in the model and the other VC is going to receive those data changes through the model delegate function.
Passing data forward from one view controller to the next isn't necessarily a bad thing. However when dealing with large amounts of data especially images you can easily run into memory pressure via this method.
Delegate way looks promising if all you needed was to inform the current viewcontroller neighbour (forward or backward) about data change.
Let me suggest an alternative set of solutions.
First off, don't manage image objects in memory. If you don't need it for anything else, write it to your apps temporary directory, keep hold of the URL and let go of the UIImage object. The snippet below lets you save your UIImage object to NSTemporaryDirectory with a name and return a URL object.
func saveImageToTempDirectory(image: UIImage, withName: String) -> URL? {
let url = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
.appendingPathComponent(withName, isDirectory: false)
.appendingPathExtension("jpg")
// Then write to disk
if let data = image.jpegData(compressionQuality: 0.8) {
do {
try data.write(to: url)
return url
} catch {
//handle error
return nil
}
}
return nil
}
You can choose to pass the URL from this method from one view controller to the other. Have this method in a Util class for better organization.
Method 1
Write the image urls from specific viewcontrollers into some local storage. You could use UserDefaults for this as its the easiest. You could also create separate folders for each viewcontroller while saving temp directory.
Method 2
Singletons. While singletons are frowned upon since they always hold state and becomes hard to test and/or debug, you could make use of a Singleton class that holds all your local URLs as part of arrays.
final class ImagePathManager {
static let shared = ImagePathManager()
var firstViewControllerImages: [URL] = []
//Initializer access level change now
private init(){}
}
You can append urls from first viewcontroller to ImagePathManager.shared.firstViewControllerImages and access them the same way from anywhere else in your application.
That being said, Singleton pattern usage is a slippery slope and you should always be very careful while using it in your apps.
Related
First of all a small introduction, im relatively new to Swift and to programming in general, been doing it for the last year and loving every and each new thing of this vast world.
My post is about some technical advices and to know if the decisions that are being made in my company make some sense.
I will first address the design that is being suggested and next my conclusions.
The design that's being implemented;
We are working in a big app, this app has some flows where they follow a sequence of 5 to 8 controllers, our team leader insists in this design pattern;
Let’s call the first controller a holder and the holder(black border) is responsible to have a container, this container has a proper navigation controller(red border), also, the holder hold all the data(orange) that those secondary controllers are generating.
Diagram of what this pattern is trying to achieve
To do this every red border controller has a method:
private func getParent() -> HolderViewController? {
if let parent = navigationController?.parent as? HolderViewController {
return parent
}
return nil
}
And to write in holder we call the method;
getParent().someModelInstance.someModelProperty = "some data”
Conclusion;
Passing data through navigation controller seems to go against to the single responsibility principle.
Creating strong references in every controller, even if I ensure that the navigationController is properly deinit when flow ends, does not seem a good option, this could lead to memory leaks and retain cycles.
I cannot ensure that, for some hod reason, two controllers try to access the same property at the same time.
This seems the Singleton Design pattern but with a limited “scope”
Resolutions;
Since I am new and I’m working in a company, and, every company has a hierarchy my objective above all is to learn if my conclusions are wrong and have better and more concise explanation about this.
First of all, to address the problem of memory leaks I created a concurrentQueue.
Instead of accessing the model directly to write in it I tried to address it through a method that will use a keyPath instead of the model directly, this is the method I’m using to write in the model;
In holder:
class HolderViewController: UIViewController {
#IBOutlet weak var bottomNavigationContainer: UIView!
private var bottomNavigationController: UINavigationController!
private var someModel: SomeModel!
private let concurrentQueue: DispatchQueue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent)
override func viewDidLoad() {
super.viewDidLoad()
setupBottomNavigation()
}
private func setupBottomNavigation() {
rootController = SecondayViewController()
if let root = rootController {
bottomNavigationController = UINavigationController(rootViewController: root)
addChild(bottomNavigationController)
bottomNavigationController.view.frame = bottomNavigationContainer.bounds
bottomNavigationContainer.addSubview(bottomNavigationController.view)
}
}
}
extension HolderViewController {
public func setValueInModel<Value>(_ value: Value, forKey path: WritableKeyPath<SomeModel, Value>) {
concurrentQueue.async(flags: .barrier) { [weak someModelInstance] in
if var obj = someModelInstance {
obj[keyPath: path] = value
}
}
}
public func readFromHolder() -> SomeModel {
concurrentQueue.sync {
return self.someModelInstance
}
}
}
This methods will be called like this;
class SecondayViewController: UIViewController {
var someString: String = "some data"
private func getParent() -> HolderViewController? {
if let parent = navigationController?.parent as? HolderViewController {
return parent
}
return nil
}
private func setValueInHolder(string: String) {
getParent().setValueInModel(string, forKey: \.someModelProperty)
}
private func readFromHolder() -> String {
return getParent().readFromHolder().someModelProperty
}
}
This look like some messy code to do a simple thing, we could use closures, delegates, segues etc… but my team leader does not like the other, simpler and more common solutions. Forgot to mention, every of our controllers has a xib and we do not use storyboards.
I know the basics of how to use the other options, what I’m trying is to understand if this is or it isn’t a good solution and why, and if my way of thinking and my methods make any sense.
Remember, every conclusion I took or every solution I've implemented could be wrong, that’s why I’m sharing with you my thoughts in order to learn from your advices and experience
Thanks in advance. :)
Stay safe!
As a preface: this type of question may be more fitting for the code review community
I can tell just by looking at the first diagram where your concern starts. Seeing the data flow as a graph you notice that there is a cycle. There is a time an a place where this may be use full (for performance more than anything) and memory management is extremely important to keep in mind in that case.
You may notice that after the call to addChild(_:) adds the contained controller to children: [UIViewController] and sets its parent property. But this done for you by the library.
Similar to the concept of the ViewController where views are dumb and the logic is contained in the view controller. I would similarly decouple the children from the parent view controllers moving most of the logic away from the children and coupling using the appropriate mechanism.
With that said I rarely find much value in using KVO with swift there are other mechanism that accomplish the same thing.
It really does depend on what the relationships are between the controllers are and what functions they have. There isn't much there to go off of. I'll leave that up to you to discover what solution you really need and the best guidance I found for this was on NSHipster's blog describing the communication mechanism to use for loose/strong coupling vs one-to-one and one-to-many relationships.
Also should point out that:
if let root = rootController {
bottomNavigationController =
UINavigationController(rootViewController: root)
addChild(bottomNavigationController)
bottomNavigationController.view.frame =
bottomNavigationContainer.bounds
bottomNavigationContainer.addSubview(bottomNavigationController.view)
}
You should be using willMove and didMove and move any set up there.
The value of the variable 'switcheroo' in the view controller below is always the same when I attempt to access it via a singleton. I am trying to access its value from a custom label class that prints the characters of the label one by one. When the label is set, I try to get the updated value of switcheroo in the Viewcontroller singleton. However it always returns the initial value of switcheroo, not the updated value (which I can trace in the viewcontroller). Am I doing something wrong?
class TheViewController: UITableViewController, UIGestureRecognizerDelegate, UITabBarControllerDelegate {
static let shared = TheViewController()
var switcheroo = 0
... various operations that change the value of switcheroo...
}
class CustomLabel: UILabel {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override var attributedText: NSAttributedString? {
didSet {
DispatchQueue.main.async {
let characterDelay = TimeInterval(0.01 + Float(arc4random()) / Float(UInt32.max)) / 100
for (index, _) in attributedText.string.enumerated() {
DispatchQueue.main.asyncAfter(deadline: .now() + characterDelay * Double(index)) {
print("switcheroo value in TheViewController is now: \(TheViewController.shared.switcheroo)")
super.attributedText = attributedText.attributedSubstring(from: NSRange(location: 0, length: index+1))
}
}
}
}
I would not suggest making a view controller a singleton solely for the purpose of some shared state or model property. View controllers have their own life cycle patterns (e.g. instantiated from storyboard scenes, released when they are dismissed, recreated when presented again, etc.), and you’re likely to have issues arising from dealing with separate instances of your view controller(s).
Instead, don’t try to fight the standard view controller life cycle, but rather just move this property into a separate object, e.g.
final class StateManager {
static let shared = StateManager()
var switcheroo = 0
private init() { }
}
Then your view controllers can use that:
class ViewController: UIViewController {
...
func examineSwitcheroo() {
print(StateManager.shared.switcheroo)
}
func updateSwitcheroo(to value: Int) {
StateManager.shared.switcheroo = value
}
}
This way, you enjoy this shared state, without entangling normal view controller life cycles in this process.
Now, what the right name for this singleton, StateManager in my example, depends entirely upon what this shared property is. But there’s not enough information about what this switcheroo object really is to offer better counsel on this matter.
And, probably needless to say, it’s a separate question as to whether you really should be using singletons at all for state variables and model objects, but that’s beyond the scope of this question.
If you have determined that having a ViewController singleton is the right decision, the likely answer is that you are not using that shared instance every time, instead accidentally calling the initializer at some point in your project (possibly Xcode is doing it automatically through interfaces).
To search through your entire project, you can use cmd + shift + F and then type TheViewController(). There should only be one occurrence (the shared instance). Be sure to also check for TheViewController.init(). That will find any time you do it.
If the issue persists, perhaps try setting the shared instance to self in the viewDidLoad method of TheViewController?
Hope this helps!
Don't manage your application's data in your view controller(s). The Cocoa and Cocoa Touch frameworks use the MVC paradigm, where the M is meant to stand for model, i.e. the application's data model. Any data that needs to be preserved, or that's relevant beyond the scope of the view controller, should be stored and managed in a model object. If you give your view controller's a reference to the model when you create them, you never need to worry about passing data from one view controller to another; instead, they each operate on the model, and any data they need comes from the model.
I am implementing my project as per VIPER for the first time and I have some doubts regarding its implementation.This is what I have done so far:
1)Implement Login page
STEPS
i)User taps login button(on view controller).
ii)I have a request model where I store 'username' and 'password'.This is the structure of model:
struct Login{
struct Request{
var txt_email:String!
var txt_password:String!
}
struct Response {
var homeData:Dictionary<String,Any>
}
}
So I pass this Request object to the Interactor.
iii)In Interactor,I have different Workers(Worker class object methods) assigned to perform different tasks such as email validation,empty textFields validation etc.If all is well,the worker api method hits login API and passes the response back to Interactor via delegation.
iv)Update the 'Response' model in the above structure.
v)Now that I have the response in Interactor,I pass this response to the Presenter to do some manipulations according to what controller needs to display to the user.
vi)Pass the data to the Controller and it presents it to user.
Question 1:Am I doing everything right.If no , please show me the right way.If yes , please tell me if there is some room for improvement.
Question 2:I need to implement UITableView and UICollectionView on the home page and I think extensions is the way to go for them.I will follow the same strategy for Home page as well.But suppose , in 'didSelectRowAtIndexPath' , I need to show a pop up to user and I think it will be better for ViewController to ask Presenter directly about the data.But is it the correct way?If not what is the correct way?
Question 3:should I pass data from cellForRowAtIndexPath: to actual cell(MyCell:UITableViewCell) class methods and then assign values to UIElements?Yes or no?
Reference: https://medium.com/#javedmultani16/viper-architecture-viper-64f6cd91e6ec
We developer basically uses the MVC,MVP or MVVM architecture for the development as per the requirement. It is mattering which architecture you are choose to develop the application. Many factors affecting for selection of the software architecture like system designs, requirements, time-lines etc.
In Viper architecture, each block corresponds to an object with specific tasks, inputs and outputs. It is very similar to workers in an assembly line: once the worker completes its job on an object, the object is passed along to the next worker, until the product is finished.
V (View): View is responsible for the UI updates and show whatever the presenter tells it.
I (Interactor) : The Interactor is responsible for fetching data from the model layer, and its implementation is totally independent of the user interface.All the business logic written inside the Interactor. E.g. Get User Data API call written in the Interactor.
P (Presenter): Presenter performing role as intermediator it gets data from interaction and passes to View. (It may be data or any user action)
E (Entity): Basically it is contains the Object Model which is used by Interactor. E.g. Student,Friend,College etc.
R (Router): It contains navigation logic for the application. E.g. Next button action shows second screen.
Morever, I’ve use the PROTOCOL, which contains all the rules and work-flow for the particular module of the application. In iOS all the protocols written in the separate protocol swift file for each module.
Morever, I’ve use the PROTOCOL, which contains all the rules and work-flow for the particular module of the application. In iOS all the protocols written in the separate protocol swift file for each module.
Let’s see the file structure of it:
enter image description here
Benefits:
-All the modules are independent so VIPER is really good for large teams.
-It makes the source code cleaner, more compact and reusable
-It easier to adopt TDD (Test Driven Development)
-You can add easily new features to the existing application without changing other modules possibly.
-It can be applies SOLID principles.
-Reduced number of merge conflicts.
-It Makes it easy to write automated tests since your UI logic is separated from the business logic
struct Person { // Entity (usually more complex e.g. NSManagedObject)
let firstName: String
let lastName: String
}
struct GreetingData { // Transport data structure (not Entity)
let greeting: String
let subject: String
}
protocol GreetingProvider {
func provideGreetingData()
}
protocol GreetingOutput: class {
func receiveGreetingData(greetingData: GreetingData)
}
class GreetingInteractor : GreetingProvider {
weak var output: GreetingOutput!
func provideGreetingData() {
let person = Person(firstName: "David", lastName: "Blaine") // usually comes from data access layer
let subject = person.firstName + " " + person.lastName
let greeting = GreetingData(greeting: "Hello", subject: subject)
self.output.receiveGreetingData(greeting)
}
}
protocol GreetingViewEventHandler {
func didTapShowGreetingButton()
}
protocol GreetingView: class {
func setGreeting(greeting: String)
}
class GreetingPresenter : GreetingOutput, GreetingViewEventHandler {
weak var view: GreetingView!
var greetingProvider: GreetingProvider!
func didTapShowGreetingButton() {
self.greetingProvider.provideGreetingData()
}
func receiveGreetingData(greetingData: GreetingData) {
let greeting = greetingData.greeting + " " + greetingData.subject
self.view.setGreeting(greeting)
}
}
class GreetingViewController : UIViewController, GreetingView {
var eventHandler: GreetingViewEventHandler!
let showGreetingButton = UIButton()
let greetingLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents:.TouchUpInside)
}
func didTapButton(button: UIButton) {
self.eventHandler.didTapShowGreetingButton()
}
func setGreeting(greeting: String) {
self.greetingLabel.text = greeting
}
// layout code goes here
}
// Assembling of VIPER module, without Router
let view = GreetingViewController()
let presenter = GreetingPresenter()
let interactor = GreetingInteractor()
view.eventHandler = presenter
presenter.view = view
presenter.greetingProvider = interactor
interactor.output = presenter
Regarding Question 2 and 3, here's answer with my simple example of Viper app using UITableView: https://stackoverflow.com/a/45709546/3990005.
The ViewController does not speak to the Interactor, but to presenter, so on cellForRowAtIndexPath: you should call the Presenter with information, which IndexPath was selected.
Regarding cell creation, what I like to do is have a setup method in UITableViewCell class, and in ViewController ask Presenter for data and pass it to the cell. Some of it is visible in the above example, and here's cell's setupCell method:
func setupCell(withSong: song) {
titleLabel.text = song.title
descriptionLabel.text = song.description
}
For any object created I generally use two two scopes 1) Singleton 2) {local scope}. I am looking for something in between.
Say I have one object that 5 view controllers are editing. I want to share an object between view controllers without having to pass it between view controllers. But it should not also live throughout application since once I am done editing the object i don't need it anymore.
I don't want to inherit all view controller from another class an create a variable there. Since view controller are reusable for different objects. I want to create an object that comes to life before launch of first view controller, lives throughout the scope of 5 view controllers and then dies after I have saved it someway. Is there anyways I could do this in iOS.
An alternative is to use your AppDelegate. Within it you can declare a global var than 2 functions, a first one to get the current value and another one to set the value.
It might give something like this:
// Get AppDelegate instance
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate;
// Use your getter to get the current value
var something = appDelegate.getYourStuff();
// Or use a setter to set it, a modifier to modify oit
appDelegate.setYourStuff(yourStuff);
appDelegate.modifiyYourStuffAttribute(newAttributeValue);
Don't realize if such a method is a bad practice or not, but it works for me.
Open to other suggestions!
As Mat said you can do is in that what. For me better is to create specific class for that that will do one particular job.
class EditingSession {
class Factory {
private static let session = EditingSession() //do it lazy
static func create() -> EditingSession {
return session
}
}
func openSession() {
}
func endSession {
}
func getData () -> AnyObject {
...
}
}
In editing session create private initializer. Factory should give the shared instance.
You can pass this class to your ViewControllers and manipulate with the data. You can inject it or just set as property in your VC or VM.
If you are done with editing you should end session and clear data or other stuff.
When using Objective-C I would pass a NSMutableArray from one view controller VC_A to another VC_B by simply assigning a property in VC_B as
VC_B.list = self.list
where self is VC_A
It allows the changes done in VC_B on the list to be seen in the list in VC_A when the view controller was say popped off the navigation stack.
However in Swift as arrays are passed by value, assigning as above does not work so I am stuck how to solve this. What would be the correct way to handle this now?
You can still do this in Swift by making the property an NSMutableArray, just as before. Foundation types still exist if you ask for them. But this is bad design in both ObjC and Swift. It creates spooky action at a distance (when things magically change values that were not part of the call) and likely breaks MVC (if the data is persistent, it should live in the model, not in the view controllers).
There are two common patterns in Cocoa: store the data in the model, or pass it via delegation.
If this array represents some kind of persistent state (such as a list of items in the system), then that belongs in the model layer, and both view controllers should read and manipulate it there rather than by communicating with each other. (See Model-View-Controller.)
If this array is a transient piece of data (such as selections from a list), then the calling VC should set itself as the delegate to the receiving VC, and when the receiving VC finishes, it should pass the data back to its delegate. (See Delegates and Data Sources.)
If you use the standard Swift Array which is a value type you have to use a wrapper or a untyped NSArray.
// a generic wrapper class
class Reference<T> {
var value: T
init(_ val: T) { value = val }
}
// Usage
class ViewController1 {
static var list = Reference<[Int]>([])
}
class ViewController2 {
static var list = Reference([3, 5, 7, 9, 11])
func passToOtherVC() {
ViewController1.list = self.list
}
}
If you want to mutate the array you should always change the value property of the Reference object.
In Swift, objects are automatically passed by reference. NSArray is an Objective C class (pass by reference), where as Array is a struct (pass by value).
So if you are working with NSMutableArray the array is already being passed by reference.
Just as a potential proof of concept that complements my comment on the question - it is possible to use the Objective-C NSMutableArray to accomplish this task:
class A {
var x: NSMutableArray = NSMutableArray(capacity: 12)
}
class B {
var y: NSMutableArray!
}
let a = A()
let b = B()
b.y = a.x
b.y[0] = 123
assert(a.x[0] === b.y[0])
Still, this is approach is not following the Swift style of handling data structures IMO.