Suppose I have a view model that my controller uses for asking some data or functionality, that view model will Never changes when I instantiated once, right? but in view controllers I navigate with perform segue and so on, I have no access to initializer then I can't use some thing like this:
let myViewModel: MyViewModel
then I have to use this instead:
var myViewModel: MyViewModel!
and I have no good feeling about this, can anyone suggest a good solution? tnx mates :)
That's totally fine, same for your IBOutlets.
From the moment you instantiate the ViewController until you set up the model, it's value is nil. that means it's not a constant even though you don't change it through the lifecycle of the ViewController.
When view controllers are loaded from storyboard, you have no control over initialization (since the controller is not initialized by you), therefore there is nothing else you can do. The other options are similar and there is no real advantage to either of them. It's a subjective decision:
You could declare the variable as a normal optional
var myViewModel: MyViewModel?
but if the controller really cannot work without data being set, I prefer to use ! myself because not setting the data model should be a fatal error.
In some cases you can also go with a default value, e.g.:
var myViewModel = MyViewModel()
My data setters usually look like this:
var model: Model! {
didSet {
loadViewIfNeeded() // to force view be loaded and viewDidLoad called
updateUI() // set UI values from the data model
}
}
Related
I'm following a tutorial on Swift and I noticed that the author uses var instead of let when declaring an #IBOutlet variable. So I became curious as to why I can't use let instead since an object's properties are still mutable even if the object is constant or is this not the case?
The error Xcode shows when using let is
#IBOutlet attribute requires property to be mutable
but I'm confused because questionLabel is a UILabel object and not necessarily a property of an object. Or is the questionLabel object a property of the current viewController?
import UIKit
class ViewController: UIViewController {
#IBOutlet let questionLabel: UILabel!
}
Thank you in advance if I'm over analyzing.
The #IBOulet marked properties are generally properties of a ViewController that are connected using the interface builder. The view you create in the interface builder has to actually connect the interface elements in it to the properties during your actual application runtime.
For that reason it firstly creates a new ViewController using some init without connecting any interface elements. They only get connected at a later stage. For the runtime to be able to hook the properties up to the view elements after the object creation has completed they cannot be constants, they have to be mutable. Because they do not have a value after the initializer has finished they have to be optionals. And to not make using the properties cumbersome afterwards they are implicitly unwrapped optionals, so that you do not have to write label!.property but label.property suffices.
That is why your code crashes as soon as you try to do something with an IBOutlet variable which you failed to connect and that is also the reason why you cannot use / change / manipulate those fields in the initializer.
Regarding your actual var / let confusion. Yes, the object itself that is referenced using let can be changed, e.g. the text of a UILabel BUT the actual object reference cannot be changed. That would mean that if you do not give the constant a specific value in the initializer it would forever remain nil.
For the simple reason that it is not assigned during initialization (in the initXXX methods) but later, when the view is being loaded.
The compiler actually cannot be even sure that the variable is ever assigned because the view loading is comletely dynamic.
In swift, all vars and lets can be thought of as properties.
A property is immutable (a constant) if it's declared with let. It's mutable (a variable) if it's declared using the var keyword. That is the defining difference between let and var.
Outlets must be mutable because their value does not get set until after the object is initialized. (The view controller gets initialized and it's outlets don't get loaded right away.)
You are right questionLabel is an object of type UILabel but used as a property of your class ViewController. That's why you have #IBOutlet attribute requires property to be mutable. If you use var you are saying that a property is mutable. If you use let you are saying that the property is immutable.
Try to create questionLabel without #IBOutletand see what's going on. Probably, you can put let in front.
First the ViewController is created, then the view tree is built. That means that when the ViewController finished it's init these views don't exist yet. They will be added just before viewDidLoad by parsing the XML-like data of the storyboard or XIB.
I know that it's Apple's default way of doing things but I would always write my outlets like:
#IBOutlet let questionLabel: UILabel?
For the simple reason it's absolutely not proven this label will really exist at run time. For example when reusing ViewControllers over multiple screens, changing the layout after setting the connections and so on this outlet might not be set. If you would use the questionLabel defined as UILabel! and it would be nil your application will crash. I don't think applications in production should ever crash over something silly like that.
For real application safety the only way to know really for sure it exists is to build your UI's in code. But the ease of use of Storyboards for one off screens is really tempting, I still use them a lot.
I have a project in which I need to log an analytics event whenever any View Controller (log the name of the View Controller) comes on screen.
I was trying to avoid littering all of my existing View Controller classes with call to the analytics SDK.
I tried making an AnalyticsViewController and all my View Controllers would subclass this View Controller, and then I add analytics event in AnalyticsViewController class's viewDidLoad method. But the problem with this approach is that AnalyticsViewController does not which child View Controller is the call coming from.
I am using Swift 3.0. I believe that Swift with its powerful language features should be able provide me with an abstraction of some sorts.
Is there any way through this problem without littering all the View Controllers?
You were on the right track. Making a UIViewController parent class is a good idea.
In viewDidLoad method you can just add this:
let className = NSStringFromClass(self.classForCoder)
It will give you the name of current loaded view controller and then you can use that name in your event to specify which view controller was actually loaded.
Edit: added example.
So your parent's viewDidLoad would look something like this:
override func viewDidLoad() {
super.viewDidLoad()
let className = NSStringFromClass(self.classForCoder)
sendEvent(withViewControllerName: className)
}
The answer given by #JPetric is an amazing starting point. I just had to do a little modification to get it to work.
I've put this in my AnalyticsViewController to retrieve the name of the current subclass.
private func currentClassName() -> String? {
return NSStringFromClass(self.classForCoder).components(separatedBy: ".").last
}
I think the solution to this is going to need to use delegation, but I'm unfamiliar with how to use them.
So in my project, I have my main viewcontroller/storyboard that contains a UIScrollView. That UIScrollview calls another storyboard (xib file) as a subview. The other storyboard (which is an xib file) is controlled with another swift file.
My question is, when I call an action inside of my other storyboard, how can I call a function from the main viewcontroller. Like say the viewdidload from the first viewcontroller.
I can't make the whole thing a global function, it needs to stay inside its class. So if I try to do ViewController.viewDidLoad() it needs (I think) an instance variable or something.
Thanks.
You can try:
Using weak variable (property) in the other class with type UIViewController
Assign the parent view controller to that property after the other view is initialized
Good reads about weak, strong, unowned references Here And Here
Firstly, if you want to call it with class name as you said above declare your method with "class". So its just like static in Java. It makes it generic to call it anywhere in your project. Make a separate extension.
class func myfunc(){
}
if you want to send data from B to A controller. You use what is called delegation. You give the work of B to A. Make a protocol above B for functions that you want to do or send with them. Call them in B. And then in A write code for those functions. So that you have the data from B to A
Else you demand something like common data. Create a singleton class and initialize properties methods there. You can use objects for that and call it in other controller to modify or make different instances.
You dont call viewDidLoad(). As the name says it loads once. If you want something that modify everytime you screen appears, use viewWillAppear
why is this error happening and what can i do to fix/prevent in the future? thanks!
NOTE: my other class is set up as such:
class Other {
//then all relevant funcs called
}
am i missing some basic setup information in order for this to run?
The formal explanation would be:
You're trying to access the view property before it was initialized. Another way to look at it is that you're trying to access the view property before it was loaded (in viewDidLoad).
Solution:
Depends what you're using that view for. I've never had to access another view controller's property like that. Consider exploring other strategies such as delegation, weak references to another controller, and passing variables in prepareForSegue if you need a reference from a view controller from another.
I have a simple question, why is the main (and only UIWindow) on all of the Apple templates in Xcode declared as a var, rather than a let.
var window: UIWindow?
As I understand it we should use let wherever possible, and especially where the instance won't change.
Also using let with objects still allows you to modify their properties (frame etc).
I'm having a little trouble understanding when to use let with object (class types).
I assumed something like CLLocationManager and UIWindow would be perfect examples of when to use let with objects, but Apple don't seem to use let in their code.
Properties defined with the let keyword must have a value by the time the object is initialized. The window isn't created during initialisation, so it can't be a let.
It's the same situation for view controllers - none of the outlets, or indeed the view properties, can be lets, because they don't exist at initialisation, but are created later when the view is required.
let properties don't really work well for UIKit and view controllers - by design, view controllers don't do a lot of work on initialisation, and you can only set up lets at that point. You are absolutely correct in that let properties would be great for things like a location manager or a managed object context, but as it turns out lazy var is often a better bet.
In my (limited, like everyone!) Swift experience, let properties are great for making immutable model classes, and local lets are the default for creating references in-code.
You can use let properties and define them right there, so for a location manager:
let locationManager = CLLocationManager()
Think more about what information you need to create whatever's going into the property. If it can be made from scratch without any context, do it like the example above. If it needs to have a delegate or any other properties passed in, you'll probably be creating or setting it after initialisation so a let isn't appropriate.
If you define window property as let, then UIKit framework will not be able to set window property of your app delegate when instantiating your default view controller from main storyboard.
So the answer is:
If you want to do everything in code manually, you can make window property to be defined with let. This way you must initialize it in init(...) method of your AppDelegate.
Otherwise, if you want to use storyboards and have them instantiated automatically with default view controller then you must define window as var and enjoy routines that apple perform for you.
It is ought to be initialized inside the application:didFinishLaunchingWithOptions: function. That way you can create different windows that suit your needs. For example:
The application may be being launched in the background to perform a task - no need to initialize a window here.
Application may be being launched by a notification - a different window object may be created.
Also, as pointed out by #Keenle, if you're using interface builder to shape your application, the window is created and assigned at runtime. In no way this could be done if the window property was constant.