This question already has answers here:
Property initializers run before 'self' is available
(2 answers)
Closed 5 years ago.
I have a SearchURL String which gets a chose variable from previous view controller. And with this gotten variable, SearchURL should be used in callAlamo func. But I have an error:
Should I use async dispatch or something like this? I've tried many things like putting everything in viewDidLoad() but did not work. Anybody could help?
You can use a computed property like so:
var searchURL: String {
return "https://theurlIcantcopybecauseitsascreenshot.com/\(chosed!)"
}
Note that this will be recomputed every time you access it, so if chosed changes, so will searchURL. You can also make it an implicitly unwrapped optional like in Paulo Mattos' answer, or you could make an initializer that takes chosed as a parameter (see comments below, for a caveat). Then you could set searchURL in init.
Also, just a nitpick to let you know, Swift standard is to have variables in camel case (searchURL instead of SearchURL).
The viewDidLoad will not be called everytime you enter your view controller, as such, you may not detect updates to the chosed property (among other tricky issues).
You could do it in viewWillAppear method instead:
var SearchURL: String!
overide func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
SearchURL = "...\(chosed!)..."
...
}
Or you could use a lazy variable:
lazy var SearchURL = {
return "...\(self.chosed!)..."
}()
Or use a computed property as suggested by Connor Neville below.
Related
This question already has an answer here:
Why are my variables empty when I cast them in my iOS application?
(1 answer)
Closed 1 year ago.
I'm coding an app on Xcode for IOS and I'd like to send a variable to another ViewController that hasn't appeared yet. The problem is that when I make a variable of my future ViewController like this :
guard let EndConfiguration = self.storyboard?.instantiateViewController(withIdentifier: EndConfiguration) as? EndConfiguration else
{
fatalError("Impossible d'acceder correctement au cellules des alarmes")
}
And I'm trying to do this:
EndConfiguration.ModeleOutlet.text = Alarme.shared.Alarmes[indexPath.row].Modele
I get this error :
Fatal error: Unexpectedly found nil while implicitly unwrapping an
Optional value
I understood the Optionels but if I add a "?" after ModeleOutlet it will remove the error but the text remains unchanged in my other ViewController. I'm sure that the value I modify is full, don't worry.
I'm replicating my problem because someone close it because there was already a solution when there wasn't one at all.
Thank you in advance.
try this
guard let EndConfiguration = self.storyboard?.instantiateViewController(withIdentifier: EndConfiguration) as? EndConfiguration else
{
fatalError("Impossible d'acceder correctement au cellules des alarmes")
}
_ = EndConfiguration.view
EndConfiguration.ModeleOutlet.text = Alarme.shared.Alarmes[indexPath.row].Modele
I am making the assumption that type of ModeleOutlet is a Label or of another type that may contain a text variable.
If ModeleOutlet is indeed a label that is attached via storyboard, then I urge you to read up on the ViewController lifecycle. Here is a great article describing it : https://medium.com/good-morning-swift/ios-view-controller-life-cycle-2a0f02e74ff5
Now, to answer your question specifically, you'll likely want to create a separate variable within your view controller then set the text variable within the function, viewDidLoad.
class EndConfiguration: UIViewController {
public var sharedText: String?
#IBOutlet var ModeleOutlet: UILabel?
func viewDidLoad() {
super.viewDidLoad()
self.ModeleOutlet?.text = sharedText
}
}
Take an example
Class A {
var a : Int
override func viewDidLoad() {
super.viewDidLoad()
a=0
}
when it says variable is not initialized, even when i already declared in class first.
viewDidLoad is not the equivalent of init
I suggest you either use optionals:
var a:Int?
or you can initialize your variable directly in its declaration
var a:Int = 0
Last but not least, if you want to initialize any other way, do it in the init
override init() {
super.init()
a = 0
}
when you declare property in the class with Int it indicates it has no initializers. either give value at initialize time var a : Int = 0
or give value using init block which is use initialize the property of class. or you can also give declare as optional with ? var x : Int?
override init() {
super.init()
a = 0
}
Swift performs Two phase initialization :
Two-Phase Initialization
Class initialization in Swift is a two-phase
process. In the first phase, each stored property is assigned an
initial value by the class that introduced it. Once the initial state
for every stored property has been determined, the second phase
begins, and each class is given the opportunity to customize its
stored properties further before the new instance is considered ready
for use.”
Basically, this means that a property is not ready for use till it is provided an initial value.
In Objective-C, this was handled internally as the properties were set nil or 0 (depending on the data type) until initialized.
This behavior is provided by Optionals in Swift.
“You use optionals in situations where a value may be absent. An
optional says:
There is a value, and it equals x or
There isn’t a value at all”
As mentioned by other answers, you can declare an optional using a "?"
eg: var a : Int?
For details refer : The Swift Programming Language (Swift 2.2).
I'm having trouble grasping the proper way of instantiating variables that always need to be set before an object is fully functional but may need to be instantiated after the constructor. Based on Swift's other conventions and restrictions it seems like there is a design pattern I'm unaware of.
Here is my use case:
I have a class that inherits from UIViewController and will programmatically create views based on user actions
I need to attach these views to this class, but to do so I need to retrieve their content based on configuration data supplied by another controller
I don't care if this configuration data is passed to the constructor (in which case it would always be required) or supplied by a secondary call to this object before it is used
My problem seems to be that both of the approaches in bullet 3 seem flawed.
In the first case, there is only one legitimate constructor this class can be called with, yet I'm forced to override other constructors and initialize member variables with fake values even if the other constructors are never intended to be used (I'm also trying to keep these variables as let types based on Swift's best practices).
In the second case, I'm effectively splitting my constructor into two parts and introduce an additional point of failure in case the second part fails to be called prior to class being used. I also can't move this second part to a method that's guaranteed to be called prior to usage (such as viewDidLoad) because I still need to pass in additional arguments from the config. While I can make sure to call the initPartTwo manually, I'd prefer to have a mechanism that better groups it with the actual constructor. I can't be the first one to run into this and it seems like there is a pattern I'm not seeing to make this cleaner.
UPDATE:
I ended up going with a modified version of the pattern matt suggested:
struct Thing {
let item1: String
let item2: String
struct Config {
let item3: String
let item4: String
}
var config:Config! {
willSet {
if self.config != nil {
fatalError("tried to initialize config twice")
}
}
}
init() {
self.item1 = ...
self.item2 = ...
...
}
public func phaseTwoInit(item3: String, item4: String) {
self.item3 = item3
self.item4 = item4
...
}
}
var t = Thing()
...
t.phaseTwoInit(...)
...
// start using t
If an initial instance variable property value can't be supplied at object initialization time, the usual thing is to declare it as an Optional. That way it doesn't need to be initialized by the class's initializers (it has a value - it is nil automatically), plus your code subsequently can distinguished uninitialized (nil) from initialized (not nil).
If the Optional if an implicitly unwrapped Optional, this arrangement need have no particular effect on your code (i.e. it won't have to be peppered with unwrappings).
If your objection is that you are forced to open the door to multiple settings of this instance variable because now it must be declared with var, then close the door with a setter observer:
struct Thing {
var name:String! {
willSet {
if self.name != nil {
fatalError("tried to set name twice")
}
}
}
}
var t = Thing()
t.name = "Matt" // no problem
t.name = "Rumplestiltskin" // crash
I am new to Swift/iOS, so please bear with me:
I am trying to access a function in one class from another class, and update an UIImage name.
Within my viewcontroller class I have
class Documents: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var UpdateImage: UIImageView
override func viewDidLoad() {
super.viewDidLoad()
UpdateImage()
}
func UpdateImage() {
UpdateImage.image = UIImage(named: "NewImage")
}
}
Everything works, the Image gets updated to "NewImage"
Question: I can access the UpdateImage func from another class, but why is it generating an error when trying to change the image in the Documents class?
class GetChanges {
var success = { operation:AFHTTPRequestOperation!, response:AnyObject!) -> Void in
var MakeChange = Documents()
MakeChange.UpdateImage()
}
}
This generates an error on the "UpdateImage.image = UIImage(named: "NewImage")" in the Documents Class; "fatal error: unexpectedly found nil while unwrapping an Optional value"
When you call it within the class itself, it is operating on itself and it has already been created from a nib/storyboard. This means that UpdateImage exists.
When you call the method from another class, when you call this line:
var MakeChange = Documents()
You are creating a new instance of Documents. This is not initialized through the nib/storyboard, and thus it never populated the IBOutlet value UpdateImage. Because this value doesn't exist, it unexpectedly finds nil and throws an error.
You need to somehow retain a reference to the instance of Documents you're trying to display. I'd need more information to tell you how to do that.
Also, because you mentioned that you're new, I'd like to point out a few issues I notice with your code that is making it very difficult to read.
Capitalized names are reserved for Types variable names should (almost) never begin with a capital letter.
Variable names should reflect the object they represent. UpdateImage sounds like it is an image. It would be better to name this updateImageView
Functions should be lowercase as well. It is strange to see capitalization this way and makes the code a bit uncomfortable to read.
Good luck!
Read about View Contoller's lifecycle, it's very important knowledge for iOS developer.
As Logan said:
You are creating a new instance of Documents. This is not initialized through the nib/storyboard, and thus it never populated the IBOutlet value UpdateImage
This means that after call init for ViewController (i.e. Documents()) nib isn't loaded. You can use outlets of viewController in another code only after viewDidLoad stage. Apple docs:
The nib file you specify is not loaded right away. It is loaded the first time the view controller's view is accessed. If you want to perform additional initialization after the nib file is loaded, override the viewDidLoad() method and perform your tasks there.
You can remove MakeChange.UpdateImage(), because it will be called in viewDidLoad. Or, if you want pass specific image name to view controller:
class Documents: UIViewController, UITableViewDataSource,
UITableViewDelegate {
#IBOutlet var UpdateImage: UIImageView
var imageName: String?
override func viewDidLoad() {
super.viewDidLoad()
updateImageView()
}
func updateImageView() {
if let imageName = imageName {
UpdateImage.image = UIImage(named: imageName)
}
}
}
After that, you can use
let documentsViewController = Documents
documentsViewController.imageName = "newImage"
When you load documentsViewController, newImage will be presented
I came across a strange behaviour in Swift while programming a Master-Detail application.
Here's the scenario:
It's a simple Task Manager application. I have two text controls (TaskName, TaskDescription) on the TaskDetailView and two string variables with the same name but in lowerCamelCase (taskName, taskDescription) declared in the TaskDetailViewController.
#IBOutlet var TaskName:UITextField! //UpperCamelCase
#IBOutlet var TaskDescription:UITextView! //UpperCamelCase
var taskName:String? //lowerCamelCase
var taskDescription:String? //lowerCamelCase
I am setting the values of Text controls on ViewDidLoad() as usual:
override func viewDidLoad() {
super.viewDidLoad()
TaskName.text = taskName
TaskDescription.text = taskDescription
}
And I am passing the data in prepareForSegue (from TaskListViewController) as usual:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if(segue.identifier == "TaskListSegue"){
let detailViewController = segue.destinationViewController as ToDoTaskViewController
let (task, desc) = m_ToDoListManager.GetTask(TaskListView.indexPathForSelectedRow().row)
println("selected \(task) \(desc)")
detailViewController.taskName = task
detailViewController.taskDescription = desc
}
}
The way everything is implemented is correct.
But now when you run the application, the values of text controls are not set.
In fact, the values of the variables also are not set.
What must be happening here?
I have already investigated this problem and also came up with a solution (see my answer below). Please also see Martin R's answer below for a detailed explanation. I just wanted to share this with everyone. I am not sure if anyone has come across this issue.
Update:
Here's the actual code:https://github.com/Abbyjeet/Swift-ToDoList
Here is an explanation:
Your Swift class is (ultimately) a subclass of NSObject.
Therefore the properties are Objective-C properties with getter and setter method.
The name of the setter method for a property is built by capitalizing the first
letter of the property name, e.g. property "foo" has the setter method setFoo:
As a consequence, the setter method for both properties TaskName and taskName is called setTaskName:.
In an Objective-C file, you would get a compiler error
synthesized properties 'taskName' and 'TaskName' both claim setter 'setTaskName:' - use of this setter will cause unexpected behavior
but the Swift compiler does not notice the conflict.
A small demo of the problem:
class MyClass : NSObject {
var prop : String?
var Prop : String?
}
let mc = MyClass()
mc.prop = "foo"
mc.Prop = "bar"
println(mc.prop) // bar
println(mc.Prop) // nil
In your case
TaskName.text = ...
sets the "taskName" property, not the "TaskName". The properties have different type,
so that the behavior is undefined.
Note that the problem does only occur for "Objective-C compatible" properties. If you remove the
NSObject superclass in above example, the output is as expected.
Conclusion: You cannot have two Objective-C properties that differ only in the
case of the first letter. The Swift compiler should fail with an error here (as the
Objective-C compiler does).
The problem you were facing with was not connected to the swift language. Method prepareForSegue is called before loadView. That mean UITextField and UITextView are not initialized yet. That's why fields were not initialized.
You also asked: Why compiler doesn't show any error? That's because any selector performed on nil object doesn't throw an exception. So for example (sorry for obj-c):
UITextField *tf = nil;
[tf setText:#"NewText"];
Will not show any error.
As you said on your own answer to solve your problem you need to add additional fields to your destination controller (copy-paste):
var tAskName:String? //cUstomCamelCase
var tAskDescription:String? //cUstomCamelCase
Why is it happening?
I believe that internally Swift is using lowerCamelCase for text controls names which are not yet initialized and thus failing to set the values. But it is also strange that I didn't get any kind of error.
How did I solve it?
I know that the Swift is case-sensitive. So that was not the issue. So I just changed the case of one letter and named the variables as (tAskName, tAskDescription) and the values were set as expected.
#IBOutlet var TaskName:UITextField! //UpperCamelCase
#IBOutlet var TaskDescription:UITextView! //UpperCamelCase
var tAskName:String? //cUstomCamelCase
var tAskDescription:String? //cUstomCamelCase
So the conclusion is that if I have a control named TaskName, I cannot have a variable named as taskName