Ive downloaded Apple's PageControl Sample Code to try to learn how to create lazily loaded pages as the user scrolls. However right out of the box as I scroll the picutres disappear and I get a black screen. Is that supposed to happen is there an error in the code.
Did you edit the code before running the app?
I was able to reproduce your problem by commenting out the _ = setupInitialPages call in the viewDidLayoutSubviews method call. Uncommented, the app works fine.
The thing to remember with lazy loading vars is they are not populated until called, that's the whole point of lazy loading. If there's no reference to the lazy loaded variable, it won't be instantiated and will result in what you saw in your test.
The syntax for a lazy loaded var is:
lazy var someVarName: the variable type e.g. Int, UIIMage, String... whatever = {
// Code in here to populate the variable
return variable with the declared type
}()
You need to make certain the return of the call to the lazy var matches the type. Also keep in mind, a lazy var while called a variable, once set doesn't change and is more like a constant in that regard. There are some work arounds, but they've already been addressed here... Re-initialize a lazy initialized variable in Swift
Related
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
}
}
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 done lots of research, yet I am stilling having the same problem. I am trying to load a website onto a web view in swift, although every time I run the project, it is giving me the following error message:
EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP,subcode=0x0)
The following two lines of code are what I am attempting to use to load the url onto the web view. I want to let you know that these 2 lines of code came from the following video: https://www.youtube.com/watch?v=rcVv1N1hReQ. Here are the lines of code:
var URL = NSURL(string: "https://www.google.com")
petInfo.loadRequest(NSURLRequest(URL: URL!))
In case you are wondering what the outlet for petInfo looks like, it is displayed in the following block of code:
#IBOutlet weak var petInfo: UIWebView!
The outlet is getting initialized immediately before the viewDidLoad() function. The url is being loaded inside the viewDidLoad() function. Thank you in advance for any help.
I finally figured the answer out. The problem was that my WebView was equal to nil, although my function was requesting a value, so the app crashed. Instead of using the ! to unwrap the outlet, I changed it to the following:
if let pet = petInfo{
//do normal function stuff like loading the web views.
}
I want to thank you for the answer, although it was not what I was looking for. If anybody doesn't understand the code below, just type up a comment (make sure to add +Andrew_Wilson214) and then I will explain it to you further.
I suspect that you have not connected the WebView to the IBOutlet in your ViewController. In the screenshot below you will see that I have defined #IBOutlet weak var petInfo: UIWebView but the grey circle in the gutter next to that line is not filled. It is not connected to the UI element yet. Also check your console to see if get the same fatal error 'unexpectedly found nil while unwrapping an Optional value'.
I'm trying to write a simple to-do list in Swift that will store the list as an array of Strings and then call it back from memory when the app loads.
I've got the following code:
var itemList = [String]()
func loadData() -> [String] {
var arr = [String]()
if NSUserDefaults.standardUserDefaults().objectForKey("storedData") != nil {
arr = NSUserDefaults.standardUserDefaults().objectForKey("storedData")! as! [String]
}
else {
arr = ["Nothing to do..."]
}
return arr
}
func saveData(arr: [String]) {
NSUserDefaults.standardUserDefaults().setObject(arr, forKey: "storedData")
}
Where I'm getting stuck is in where to place the call to loadData(). This is an app that has two view controllers (one for the list, one for an add item setup), so if I place the loadData() call in viewDidLoad() for the main view controller, the array is called back in from memory (and over-written) every time I switch back to the main view controller.
Where is the best place to call this so that it will load once only, upon the app starting up?
the array is called back in from memory (and over-written) every time I switch back to the main view controller.
No. viewDidLoad only loads once, when the app starts. Only viewWillApprear and viewDidAppear get called everytime the viewcontroller changes.
Also you could make your code a bit more compact by using if let:
if let storedData = NSUserDefaults.standardUserDefaults().objectForKey("storedData") as! [String]{
arr = storedData
}
But if you want to make sure to load this only once, you can put it in your AppDelegate file in your applicationDidFinishWithOptions method.
But you'd have to make a variable in your AppDelegate file which you can access from your viewController.
viewDidLoad() only happens when the View Controller is first instantiated. If it is your root view controller you can have it in viewDidLoad().
The other goes, viewDidLoad > viewWillAppear > viewDidAppear. After the view is first loaded only the latter 2 methods are called whenever you navigate.
you can also always register for a NSApplicationDidFinishLaunchingNotification notification at the notification center
check it out here:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSApplication_Class/index.html#//apple_ref/c/data/NSApplicationDidFinishLaunchingNotification
Use or overwrite respectively
application(_:didFinishLaunchingWithOptions:)
of your application delegate.
That is called only once upon application launch.
See
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/#//apple_ref/occ/intfm/UIApplicationDelegate/application:didFinishLaunchingWithOptions:
viewDidLoad is called after the view for a single controller is first loaded. It shouldn't be called more than once for the life-cycle of a single viewController. Maybe it is possible that, if you are not calling "super.viewDidLoad()" in your own viewDidLoad method, then it may be called again? While you can generally assume that the rootViewController for an application is only created once, I think it's theoretically possible that it might be cleared out of memory by the app if required and then recreated again - so I would never assume it's only called once.
One thing you could do is just set a boolean (default false) to true whenever you load the data and then not call it again if the flag is already set to true.
Alternatively, it's a good idea to separate the data management from your viewControllers. A relatively simple solution would be to have a class called "AppData" say, which might be a singleton (so you can only ever have one instance of it) or a member of your AppDelegate. Then, in your app delegate's "applicationDidFinishLaunchingWithOptions method, you could create the one instance of the AppData class and call the loadData method on it. This class would then live independently of whichever view is currently showing, and the current view could call methods on this object to load/save/update data as required.
I'm getting stuck on some code with the dreaded "Can't unwrap Optional.None" error in my code.
I'm following the Shutterbug code from the iTunes U Stanford university course.
This is the code given in Objective-C for one of the classes.
http://pastebin.com/LG2k3BBW
and what I've come up with in Swift;
http://pastebin.com/pGtSzu6z
After tracing the errors these lines in particular seem to be giving me the problems
self.scrollView.zoomScale = 1.0
and
self.image = nil
Any advice on what's going wrong here?
I had originally put all the setters in the ViewDidLoad function and was receiving the same error.
This line is called when you are preparing for segue:
ivc.imageURL = flickerFetcher.URLforPhoto(photo, format: FlickrFetcher.FlickrPhotoFormat.Large)
Which calls the setter on imageURL:
set {
self.startDownloadingImage()
}
startDownloadingImage() calls the setter on image which is where you get all of your errors:
set{
self.imageView.image = image
self.scrollView.zoomScale = 1.0
self.spinner.stopAnimating()
self.imageView.frame = CGRectMake(0, 0, image!.size.width, image!.size.height)
}
Since all of this is happening in prepareForSegue, the view controller hasn't been loaded at this point and all of the outlets are nil hence the can't unwrap Optional.none.
You need to restructure your program's logic so that it isn't trying to do stuff with outlets before they are loaded.
Check your outlets are connected. Outlets are defined as implicitly unwrapped Optionals, so you'd expect to see that error if you referred to a property that wasn't set (e.g. If the outlet was not connected)
I've downloaded your project and there was a problem with the outlets - the scrollview and spinner were kind of grayed out in Xcode. Here's how the scrollview and spinner showed up for me:
This means that the items are not installed for the currently selected size class. If you go to the attributes inspector for that view, make sure the Installed option is checked for the size classes you care about:
In your project, the top box (representing any size class) was not checked.
However, there are many more problems within the code, too numerous to go into in full detail in this answer. You have several issues which are causing problems, including (I did give up after a while):
Infinite loops in your property accessors - for example, the get closure for imageURL
Implementing set or willSet closures when you actually want didSet - for example, the willSet on the scrollView would be better as a didSet, or you should be using newValue instead of scrollView, because at the point of willSet, scrollView is still nil.
Setting a value of nil to your image property, then accessing it in the setter block for that property
Something odd going on in your downloading logic (at this point, I decided to call it a day, sorry)