Swift: strange behavior about unwrapping - swift

In my Child model, I have declared the following properties:
var name:String?
var year:String?
var make:String?
var model:String?
and the init:
init(name:String, ... ,year:String, make:String, model:String, ...){
self.name = name
...
self.year = year
self.make = make
self.model = model
}
Here I construct a child:
Child(name:cName,...,year:cYear,make:cMake, model:cModel,...)
In
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell!{
For the child's name I could use it directly without unwrapping:
cell!.textLabel.text = child.name
But I have to unwrap child.year, child.make, child.model if I want to use them. Why is that?
cell!.detailTextLabel.text = child.year! + " " + child.make! + " " + child.model!
I followed this tutorial and build the project successfully. In that project, in a similar situation, I don't need to do unwrapping. Code
cell.text = album.title
cell.image = UIImage(named: "Blank52")
cell.detailTextLabel.text = album.price

You need an explicit unwrap because you are using concatenation operator. Concatenation is not defined for optional strings, so you need to add exclamation point to unwrap the values. You should be able to avoid it if you use string interpolation, like this:
cell!.detailTextLabel.text = "\(child.year) \(child.make) \(child.model)"
Note: if the initializer that you show is the only one available, you should be able to make your strings non-optional.

Related

What are some good ways to avoid repeating array index accessing operation on same Array of struct element?

Consider the following code:
struct Card {
var name0: String
var name1: String
}
var cards = [Card]()
cards.append(Card(name0: "hello", name1: "world"))
// Need to perform array index access,
// every time I want to mutate a struct property :(
cards[0].name0 = "good"
cards[0].name1 = "bye"
// ...
// ...
// "good bye"
print(cards[0].name0 + " " + cards[0].name1)
Instead of having to perform multiple array index accessing every time I want to mutate a property in struct, is there a technique to avoid such repeating array index accessing operation?
// Ok. This is an invalid Swift statement.
var referenceToCardStruct = &(cards[0])
referenceToCardStruct.name0 = "good"
referenceToCardStruct.name1 = "bye"
// ...
// ...
There are a lot of good answers here, and you should not think of value types as "a limitation." The behavior of value types is very intentional and is an important feature. Generally, I'd recommend inout for this problem, like matt suggests.
But it is also certainly possible to get the syntax you're describing. You just need a computed variable (which can be a local variable).
let index = 0 // Just to show it can be externally configurable
var referenceToCardStruct: Card {
get { cards[index] }
set { cards[index] = newValue }
}
referenceToCardStruct.name0 = "good"
referenceToCardStruct.name1 = "bye"
print(cards[0].name0 + " " + cards[0].name1)
struct Card {
var name0: String
var name1: String
}
var cards = [Card]()
// every time I want to mutate a struct property :(
cards[0].name0 = "good"
cards[0].name1 = "bye"
Instead of having to perform multiple array index accessing every time I want to mutate a property in struct, is there a technique to avoid such repeating array index accessing operation?
No. When you have an array of struct, then in order to make a change to a struct within the array, you must refer to that struct by index.
If you don't want to see the repeated use of the index, you can hide it in a function using inout:
func mutate(card: inout Card) {
card.name0 = "good"
card.name1 = "bye"
}
for index in cards.indices {
mutate(card:&cards[index])
}
Some day, Swift may include for inout which will allow you to cycle through an array of struct and mutate each struct instance directly. But that day is not yet here.
In answer to the implied question whether it is worth switching to a class just to avoid this repeated use of the index, my answer would be No. There is a good reason for using structs — they are much easier to reason about than classes, and are one of Swift's best features — and I would keep using them if that reason matters to you.
struct is a value type you can't get a reference to it's object with assignment , you should go that way , use a mutating method like https://stackoverflow.com/a/52497495/5820010 or use a class instead
If you don't want to repeat the index, then create a variable from the value you want.
var cards = [Card]()
cards.append(Card(name0: "hello", name1: "world"))
var card = cards[0]
card.name0 = "good"
card.name1 = "bye"
// ...
// ...
cards[0] = card // update the array with the updated card
// "good bye"
print(card.name0 + " " + card.name1)
I think the mutating method is the way to go, as Sh_Khan points out.
In your case, I would do something like:
1> struct Card {
2. var name0: String
3. var name1: String
4. }
5.
6. var cards = [Card]()
7. cards.append(Card(name0: "hello", name1: "world"))
cards: [Card] = 1 value {
[0] = {
name0 = "hello"
name1 = "world"
}
}
8> extension Card {
9. mutating func setNames(name0: String, name1: String) {
10. self.name0 = name0
11. self.name1 = name1
12. }
13. }
14> cards[0].setNames(name0: "x", name1: "y")
15> cards
$R0: [Card] = 1 value {
[0] = {
name0 = "x"
name1 = "y"
}
}
Another approach is a wholesale update of cards[0].
Kind of makes you wish for record updating syntax (a la Haskell or Elm) or dictionary merging-type functionality. But look on the bright side. Maybe Swift's lack of making this easy is testament to the fact that it has static-typing-and-value-semantics-while-allowing-mutation and that combination of features, I think, makes the mutating func or full array element update all but required. I'm not sure Swift has a syntactic feature for updating multiple properties in one shot (without writing your own function or method).

Can't remove optional("String"). states its non-optional

I'm working on a spendings tracker app. All the logic is now working, but when I want to display transaction data in a UILable, it displays it as optional("String")
I have looked around the Internet and have tried unwrapping the string in 2 different ways, but I'm not able to fix it.
Adding an ! to the end of the string gives an error Cannot force unwrap value of non-optional type "String"
Here is the code I'm using now that displays optional("String")
Here I set up my struct and array
struct Transaction {
var discr = ""
var amount = 0
}
var transactions = [Transaction]()
This is how I add data to the array
transactions.append(Transaction( discr: String(describing: transDescrInput.text), amount: Int(tempAmount)))
This is how I display the data in a tableview
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = transTable.dequeueReusableCell(withIdentifier: "sCell")
let discrText = transactions[indexPath.row].discr.uppercased()
cell?.textLabel?.text = "€\(transactions[indexPath.row].amount)"
cell?.detailTextLabel?.text = "\(discrText)"
return cell!
}
This is how it shows up in the app
Iphone simulator screenshot
The problem is already where you add data to the array.
Assuming that transDescrInput.text is an optional string,
String(describing: transDescrInput.text)
returns a non-optional string "Optional(text...)" and there is
no sensible way to revert that. You should use optional binding
or other unwrapping mechanisms instead, for example
if let text = transDescrInput.text {
transactions.append(Transaction(discr: text, amount: Int(tempAmount)))
}
or with nil-coalescing:
transactions.append(Transaction(discr: transDescrInput.text ?? "", amount: Int(tempAmount)))
As a rule of thumb, String(describing:) almost never the correct
solution (even if the compiler suggest it as a Fix-it), it only hides
the actual problem.
Right after posting this post I realised I have to unwrap the text before I add it to my array. So I changed the way I save the string:
transactions.append(Transaction( discr: String(describing: transDescrInput.text!), amount: Int(tempAmount)))
I added an ! behind the transDescrInput.text to unwrap it before I save it to my array.
May I suggest do something like this?
let discrText = transactions[indexPath.row].discr.uppercased()
cell?.detailTextLabel?.text = "\(discrText!)"

index (of: element) does not exist

I have a really strange error. Have been learning Swift and was reading the documentation on finding the index of an object in an array here: https://developer.apple.com/reference/swift/array/1689674-index
However this method does not exist in my Xcode.
Here is an example:
var items : [TaskItem]
items = [TaskItem] ()
let rowitem = TaskItem()
rowitem.text = "helloworld"
items.append(rowitem)
//Attempting to find the index of an object
items.Index(of: rowitem)
//error - Xcode cannot find the specific func I am looking for
I have attached an image of the methods that appear and have not been able to find an answer for why this might happen anywhere.
I cannot find the use of the func index(of:)method although I know it exists!
I get the following compiler error:
ChecklistViewController.swift:186:26: Argument labels '(of:, _:)' do
not match any available overloads
ChecklistViewController.swift:186:26: Overloads for 'index' exist with
these partially matching parameter lists: (Int, offsetBy: Int),
(Self.Index, offsetBy: Self.IndexDistance)
TaskItem needs to conform to the Equatable protocol in order to use index(of:).
I am totally agree with Tim vermeulen but we can get the index of array using the below code and its working perfectly.
class Name {
var firstName:String = String()
var lastName:String = String()
var fullName:String{
return firstName+lastName
}
}
var items:[Name] = [Name]()
let rowitem = Name()
let name:Name = Name()
name.firstName = "Vala"
name.lastName = "bbbb"
items.append(rowitem)
items.append(name)
//Attempting to find the index of an object
print(items.index{$0 === name}!)

Compiler errors for struct init in Swift

I have the following struct:
struct Person {
let name: String
let age: Int
let assets: Double
}
To initialize a person, I would like to pass it a dictionary that contains the name and age, as well as info to calculate the assets:
public init(info: [String : AnyObject]) {
if let name = info["name"] as? String {
self.name = name
}
if let age = info["age"] as? Int {
self.age = age
}
if let assets = info["assets"] as? [String : AnyObject] {
calculateAssets(assets: assets)
}
}
mutating func calculateAssets(assets: [String : AnyObject]) {
self.assets = 1+2+3.4 // really do this with info from the dictionary
}
With this setup, I get two compiler errors:
'self' used before all stored properties are initialized
Return from initializer without initializing all stored properties
Following the compiler suggestions, I added a default value for each property, and changed them to be a var:
struct Person {
var name: String = ""
var age: Int = 0
var assets: Double = 0.0
// init ...
}
And indeed, the compiler errors are gone.
But am I on the right track by making these corrections?
The problem is that in your init function you might initialize the variables but you can't be sure, since the conditions of the if statements can be false. This is why the compiler is giving you the errors (also because you are trying to call self.assets in another function when the variable might still not be initialized - so you at least need to change this one to a var).
If you can't be sure that the values in the info dictionary are valid you should change your variables to var.
Now you have two choices:
Give your variables a default value (like you did in your example)
Declare your variables as Optionals (like suggested by #Özgür).
Your choice depends on what makes more sense. If your default values make sense for your situation and you can work with the variables having those values then I'd go with that.
Otherwise, you should go with the Optionals. Optionals obviously have the plus that you don't need to initialize them but then the con is that you'll need to implicitly or explicitly unwrap them later when you want to use them (using ? or !).
If - for whatever reason - you can be sure that the values in the info dict are valid - you could also leave your name and age as constants (let) and just explicitly unwrap the dictionary values when you assign them:
self.name = info["name"] as! String
self.age = info["age"] as! Int
But as I said this would only work if info["name"] and info["age"] contain valid values, otherwise you will get a runtime exception. So the "default value" or "Optional" choices are your safe and clean options, whereas this is the quick and dirty way.
You got compiler error because your variables are not inited when you set them.(you define them as inited). That's why for second case when you init them to nil values, error is gone. For this conditions, Swift has optionals.Can be nil and a value at the same time. Check here. You can add ? and that makes the variable optional.
struct Person {
var name: String?
var age: Int?
var assets: Double?
// init ...
}

Swift String Interpolation displaying optional?

When i use the following code and have nameTextField be "Jeffrey" (or any other name)
#IBAction func helloWorldAction(nameTextField: UITextField) {
nameLabel.text = "Hello, \(nameTextField.text)"
}
nameLabel displays... Hello, Optional("Jeffrey")
But, when I change the previous code to include a "!" like this:
#IBAction func helloWorldAction(nameTextField: UITextField) {
nameLabel.text = "Hello, \(nameTextField.text!)"
}
The code works as expected and nameLabel displays.... Hello, Jeffrey
Why is the "!" required, in the video tutorial I used to create this simple program he did not use the "!" and the program worked as expected.
Another alternative is to use the null coalescing operator within the interpolated string for prettier text without the need for if let.
nameLabel.text = "Hello, \(nameTextField.text ?? "")"
It's less readable in this case, but if there were a lot of strings it might be preferable.
Optionals must be unwrapped. You must check for it or force unwrap as you do. Imagine the optional as a box where you put a value. Before you can access it, you need to put it out.
if let name = nameTextField.text {
nameLabel.text = "Hello, \(name)"
}
Here's a handy extension to unwrap Any? to String.
Set a default value for nil values.
extension String {
init(_ any: Any?) {
self = any == nil ? "My Default Value" : "\(any!)"
}
}
// Example
let count: Int? = 3
let index: Int? = nil
String(count)
String(index)
// Output
// 3
// My Default Value
You can also use optional map.
This is where I learned of how to use it.
Basically, map will take an optional and return a value if there's a value and return nil if there's no value.
It think this makes more sense in code, so here's the code I found useful:
func getUserAge() -> Int? {
return 38
}
let age = getUserAge()
let ageString = age.map { "Your age is \($0)" }
print(ageString ?? "We don't know your age.")
I guess this may not be super helpful in the case where you're passing in an optional string, (nil coalescing works just fine in that case), but this is super helpful for when you need to use some value that isn't a string.
It's even more useful when you want to run logic on the given value since map is a closure and you can use $0 multiple times.