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.
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.
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.
Having my first crack at POP. In this case I want to decorate some UIViewControllers so that any that they automatically raise a 'Page viewed' analytics event.
So I created a protocol, and and extension for that protocol:
protocol ReportPageViewedEvent {
func reportPageViewed()
var pageName : String? { get set }
}
extension ReportPageViewedEvent where Self: UIViewController
{
func reportPageViewed()
{
guard let pageName = self.pageName else
{
fatalError("UIViewController implements ReportPageViewEvent protocol but did not set pageName property")
}
let eventBusiness = EventBusiness.sharedInstance
eventBusiness.logUserViewedPage(pageName)
}
}
This works as I want, if I decorate a UIViewController with ReportPageViewedEvent like this:
class HomeView: UIViewController, ReportPageViewedEvent {
I get a compiler error unless I set 'pageName' which is exactly what I want.
Where I am getting unstuck is where and how to call the actual reportPageViewed() method. I really want it to be called from viewDidLoad which means I either have to modify every 'viewDidLoad' in every controller that uses it, or subclass and call the method in the super class which defies the point of using POP in the first place.
Is there a nice way to achieve this. I can't find an example like this in any tutorial/blog.
Basically, there is always some behaviour shared by all the screens of your app. So it is appropriate to create a class called (for example) BaseViewController so all the other view controllers will inherit from it.
In BaseViewController's viewDidLoad you can call the reportPageViewed() method.
However, this approach makes the Protocol Oriented Programming not needed. Protocols are useful when you need to assign some same behaviour to objects that have nothing in common (which is not the case for app screens).
I'm creating an app in Swift 2.0 xCode7 using the Tabbed-Application template, with each screen having a separate ViewController. I have a struct to manage a variable I want to be accessed by all view controllers. I created the instance of the struct in the first view controller. I'm able to access the struct data and methods in the other views, but if update the data in one view, it doesn't change for all... It's acting as if each View Controller is creating its own instance on its own. I don't want that. I want each ViewController to share the same updated data in the struct. Does this mean that I should be creating a Singleton Pattern? Or, something else? I'm quite new at this, so thanks for your patience.
I'm not sure how exactly you access the structure but it might be that you only need to change struct to class because structs are value types so if you assign it or pass into a method it is copied whereas an instance of a class will avoid copying
Because you didn't give me any code, this is just my guess.
Structs are different from classes. The former stores values and the latter stores references. Let's look at this code:
var obj = SomethingCool()
obj.somethingCooler = 20
var obj2 = obj
obj2.somethingCooler = 10
If SomethingCool were a struct, obj.somethingCooler would still be 20 but obj2.somethingCooler would be 10. On the other hand, if SomethingCool were a class, both obj.somethingCooler and obj2.somethingCooler would be 20.
This is because the third line. The third line is VERY important. If SomethingCool were a struct, the values stored in obj will be copied to obj2. i.e. Two set of independent values would be created. If it were a class, the object that obj will also be referenced by obj2. i.e. There would still be just one object.
Now that you know the difference, I can tell you that you must have done something like the third line in your view controllers, haven't you?
To solve this problem, you can change from a struct to a class. Or you can create something like this:
public class SomeName {
static var myData: SomeTypeOfStruct {
return something
}
}
If you are so hellbent on keeping it as a struct you could do something that swift actually helps u out with.....AppDelegate!
The appdelegate.swift is a single instance object for any application. So in case you want to save a value that you need to access throughout the application or update throughtout the application, you might want to use AppDelegate.
E.g.
In FirstViewController.swift set the AppDelegate variable that you want to reflect on the remaining screens:
(UIApplication.sharedApplication().delegate as! AppDelegate).commonVariableName = NewValueYouWant;
In the SecondViewController.swift, take up that value from the AppDelegate
var updatedValue = (UIApplication.sharedApplication().delegate as! AppDelegate).commonVariableName;
Again...as #Sweeper said, you can always switch to class which is more reliable and used to achieve something like this.
It's acting as if each View Controller is creating its own instance on
its own.
It's all explained in Apple's Swift guide:
Structs:
struct Dog {
var name: String
}
var d1 = Dog(name: "Rover")
var d2 = d1
d2.name = "Sally"
print(d1.name)
print(d2.name)
--output:--
Rover
Sally
Classes:
class Cat {
var name: String = ""
}
var c1 = Cat()
c1.name = "Kitty"
var c2 = c1
c2.name = "Gerald"
print(c1.name)
print(c2.name)
--output:--
Gerald
Gerald
See the difference?