Swift - update object in place - ios

If I want to update existing class object is there any easier way than duplicating init to update method? (my Person object has about ten properties which are defined firstly or can be edited at later app usage)
problems listed in example below:
person1 reinitialization does not update reference to a person1 object in a parent-child array - child array does not contain updated object
person2 object behaves as wanted (child array contains updated object) but uses duplicated initializer code in update method to make “edit in place” which looks really bad in terms of DRY
any hints how to do it Swiftly?
import UIKit
class Person {
var name: String
var surname: String
var age: Int?
init(name: String, surname: String, age: Int? = nil) {
self.name = name
self.surname = surname
self.age = age
}
func update(name: String, surname: String, age: Int? = nil) {
self.name = name
self.surname = surname
self.age = age
}
}
class Parent {
var child = [Person]()
}
var parent = Parent()
var person1 = Person(name: "John", surname: "Doe")
print(person1.name)
parent.child.append(person1)
person1 = Person(name: "Jack", surname: "Doe")
print(person1.name)
print(parent.child.first!.name)
var person2 = Person(name: "Tom", surname: "Cruise")
print(person2.name)
parent.child.append(person2)
person2.update(name: "Jim", surname: "Cruise")
print(person2.name)
print(parent.child.last!.name)
to make it more clear think about the array of user profiles where you can add a new profile or edit an existing one. and above problem is about editing existing user profile

Here is the explanation for both of your queries,
1. parent child array contains the reference to the person1 object, i.e. person1 and parent child array store the reference to same object.
var person1 = Person(name: "John", surname: "Doe")
parent.child.append(person1)
Now, you're assigning a new object to person1 instead of making changes to the same object, i.e.
person1 = Person(name: "Jack", surname: "Doe")
This won't reflect in the parent child array. It will still contain the reference to the previous Person object.
This is the reason you are getting different values of name in person1 and parent child array
print(person1.name) //Jack
print(parent.child.first!.name) //John
2. Instead of adding a separate method for updating the object - update(name:surname:age:), you can simply update the values individually using the . operator on object's properties, i.e
var person2 = Person(name: "Tom", surname: "Cruise")
parent.child.append(person2)
person2.name = "Jim"
Since in the above code, instead of assigning a new Person object to person2, we're just updating its values.
So, person2 and parent child array's object will still reference the same object and hence will print the same name values.
print(person2.name) //Tom
print(parent.child.last!.name) //Tom

Related

Mapping array of objects into new array of array by value

Hey i have array of objects, my goal is to split them into several arrays by common property value for example:
struct Person {
let name: String
let city: String
}
let persons: [Person] = [Person(name: "John", city: "San Francisco"),
Person(name: "Tim", city: "San Francisco"),
Person(name: "Bob", city: "Atlanta")]
my goal is to get arrays that contain only persons from same city.
In that example result will be two arrays first contain John and Tim's objects, and 2nd contain only Bob's object.
Thanks
If I got you correctly, you should use a dictionary and group items by the city property:
let grouped = Dictionary(grouping: persons) { $0.city }
If you don't need the keys, then you can map them on values like:
let groupedWOKeys = grouped.map { $1 }

Swift: Associative Arrays in UITableView

Pretty new so apologies if this is noobish.
I'm trying to get the key and the value from an associative array to print out as the label text in a cell.
At the moment I have the array:
let users = ["John","James","Liam"]
and I am getting the value for the cell text like so:
cell.textLabel!.text = self.users[indexPath.row]
Which will give me rows of the names in. I am struggling when I add in user ages like so
lets users = ["John" : 36, "James": 12, "Liam": 30]
I get this error:
Ambiguous reference to member 'subscript'
How do I get the cell text to display both the name and age?
If you really need to refer to the data via an array, there are at least two ways you can easily accomplish this. Create a Person object like in the comments, or you can quickly use a tuple structure. The following code works in Xcode Playground (Swift 3, 4).
// - First Approach
struct Person {
var name: String
var age: Int
}
let users = [Person(name: "John", age: 36), Person(name: "James", age: 12), Person(name: "Liam", age: 30)]
// say indexPath.row = 0
print("name: \(users[0].name), age: \(users[0].age)") // name: John, age: 36
// - Second Approach
let usersTuple = [("John", 36), ("James", 12), ("Liam", 30)]
print("name: \(usersTuple[0].0), age: \(usersTuple[0].1)") // name: John, age: 36
You need to define a dictionary and with ages and names and in each cell get from the dictionary the age with the name as key, or you can define a model with name and age like Person and put and use as your datasource Array, which I think is better
class Person{
var age : Int = 0
var name : String = ""
init(name:String,age:Int){
self.name = name
self.age = age
}
}
declare an array of Person in your ViewController
var users = [Person(name: "John", age: 36),Person(name: "James", age: 12),Person(name: "Liam", age: 30)]
In the cellForRow method
let currUser = self.users[indexPath.row]
cell.textLabel!.text = currUser.name + "Age: \(currUser.age)"
If you're coming from a PHP style background, it can be confusing that dictionaries look like associated arrays but there's no way to reference them by index like in PHP. You'll need to reference by the key (the string) or convert them into something which you can retrieve later.
Luckily in Swift you can just use something called a tuple, which looks like this:
let user = ("John", 36)
And you reference the values like this:
let name = user.0
let age = user.1
You can store tuples in arrays, so you can do this:
let users = [("John", 36), ("James", 12)]
let johnAge = users[0].1
You can also give the tuple named parameters but by this point you might as well create a struct. Here's what it looks like anyway:
let user = (name: "John", age: 36)
let johnAge = user.age
Another neat trick with tuples is that if you typealias it you can create it the same way you would a struct, without having to device the properties individually, like this:
typealias User = (name: String, age: Int)
let john = User(name: "John", age: 36)
By this point it's just a case of saving a few lines of code but it really depends on how lightweight you need the user object to be. You wouldn't be able to make it conform to anything in the future, which is why a struct can be a better option. If all you ever need is a lightweight object, tuples are great.

In swift, when one property has no default value, the default values held by other properties become useless?

In swift, I have the following code:
struct Person {
var name: String
var nose: Int = 1
}
var mike = Person(name: "mike", nose: 1)
var john = Person(name: "john") //error: missing argument for parameter 'nose' in call
Although nose has a default value, I have to specify a value for it. My question is, does default value of nose become totally useless and I can simply write the declaration as the following?
struct Person {
var name: String
var nose: Int
}
Thanks!
From The Swift Programming Language
Structure types automatically receive a memberwise initializer if they
do not define any of their own custom initializers. This is true even
if the structure’s stored properties do not have default values.
The memberwise initializer is a shorthand way to initialize the member
properties of new structure instances. Initial values for the
properties of the new instance can be passed to the memberwise
initializer by name.
As you haven't declared any initialisers for your struct, the only initialiser that is available is the memberwise initialiser.
You can declare an initialiser that just takes a name in order to use the default value for nose. This then requires you to explicitly declare the memberwise initialiser -
struct Person {
var name: String
var nose: Int = 1
init(_ name:String) {
self.name=name;
}
init (name:String, nose:Int){
self.name=name;
self.nose=nose;
}
}
var mike = Person(name: "mike", nose: 1)
var john = Person("john")
I like this form because it is so flexible:
struct Person {
var name: String
var nose: Int
init(name: String = "Anon", nose: Int = 1) {
self.name=name;
self.nose=nose;
}
}
var sally = Person(nose: 2, name: "Sally")
var anon1 = Person(nose: 1)
var anon2 = Person()
var mike = Person(name: "mike", nose: 1)
var john = Person(name: "john")
Note 5 different initialiser combinations possible. Essentially it allows you to treat Person like a person builder (Builder pattern).

Changing the value of a constant in a class

Working through Apple's Swift Programming Guide I came across this example in the explanation of ARC;
class Person {
let name: String
init(name: String) {
self.name = name
println("\(name) is being initialized")
}
deinit {
println("\(name) is being deinitialized")
}
}
var reference1: Person?
var reference2: Person?
var reference3: Person?
I understand the idea that because the variables are of the option type they are initialized with a value of nil and do not reference a Person instance. So the following makes sense to me;
reference1 = Person(name: "John Appleseed")
However I was experimenting and was surprised to see I could also do this;
reference1 = Person(name: "Johnny Appleseed")
I was expecting the code to error since I was trying to change the constant "name" property. Not only can I change this property I also got the message "Johnny Appleseed is being initialized". How can I be initializing a constant twice?
You're not actually changing the name property of your existing Person instance.
What you're doing is creating a new Person, and giving him the name "Johnny". Your old Person with the name "John" will be deallocated automatically:
reference1 = Person(name: "John Appleseed")
reference1 = Person(name: "Johnny Appleseed") // "John" is now gone.
Unless you have some other variable pointing to "John", that instance will be deallocated.
This would cause a compilation error:
reference1 = Person(name: "John Appleseed")
reference1.name = "Johnny Appleseed"
Because you'd be trying to change the value of a property defined using let.
By calling Person(name: "Johnny Appleseed") you creating a new Person object that replaces the old Person object referenced by reference1. The constant property name of the old Person object is not changed at all, thus no error is issued.

How Do I Create An Array With Multiple Variables In Swift

I am new to iOS development, and for some reason I can't figure out how to do this even though I realize it has to be simple. I want to create a collection with 2 string values. For instance:
var firstName = "Bob"
var lastName = "Smith"
So the first index would contain ("Bob", "Smith") and so one for each first and last name I want to add to the collection. What object do I use? I tried a dictionary but it seems you have to add your values to that up front, and I need to add my later on programmatically.
You could use a dictionary but I'd create a Person struct that contains a firstName and a lastName and put those into an array.
struct Person {
var firstName: String
var lastName: String
}
var firstName = "Bob"
var lastName = "Smith"
var array = [Person(firstName: firstName, lastName: lastName)]
It also has the benefit of being able to access the parts using .firstName and .lastName
array[0].firstName
As opposed to a dictionary that that requires a string
array[0]["firstName"]
To create an array with only string you use
var shoppingList: [String] = ["Eggs", "Milk"]
And to add to the end of the array (push) you use
shoppingList.append("Bread")
The struct way is preferred, but if you want to do something more similar to the initial request you could do an array of tuples

Resources