Swift: Associative Arrays in UITableView - ios

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.

Related

Presenting a nested array in a TableViewController

Sorry if this is just a basic concept of Tables in Swift, but I'm quite new to Swift and have spent quite some time trying to figure this out with no luck.
I have an array named 'Data' which is structured like this:
var Data = [
["Alex","25","Male","258475"],
["Sean","29","Male","145737"],
["Grace","34","Female","295049"],
...
]
I'd like to present this in a TableViewController such that each cell contains the name, age, gender and an ID. Most examples that I've found use data structured in a different way:
var Data = [
["Alex","Sean","Grace"],
["25","29","34"],
["Male","Male","Female"],
...
]
Is there a way to use the data structure that I have and present it in rows of a table view? Or would I be better off restructuring my data? Thanks.
As I see it there are a couple of ways you can do this. You could create a struct which contains each person and have an array of those. Somthing like this.
struct Person {
var name: String
var age: String
var gender: String
var id: String
}
Then store your data in an array
var data = [Person]
Then in your table cell you can set up to have a label for each of the items you want to display.
Or a down and dirty way you can get the info you want to show in the defualt cell text area is to concatonate the strings togther as s single string.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: textCellIdentifier, for: indexPath)
var cellText = ""
let row = indexPath.row
for x in data[row]{
cellText = cellText + ", " + x
}
cell.textLabel?.text = cellText
return cell
}
In the above we find the array inside the data array which corresponds to the row number, then we loop through that array adding all the strings together to get 1 big string and then display that string in the table.
Here is a tutorial which gives a good overivew of table views which may help.
https://www.codingexplorer.com/getting-started-uitableview-swift/
Nested multiple arrays as table view data source is the worst choice. Never use those.
The usual and recommended choice is a custom struct.
enum Gender {
case male, female
}
struct Person {
let name, id : String
let age : Int
let gender : Gender
}
let people = [Person(name:"Alex", id: "258475", age: 25, gender: .male),
Person(name:"Sean", id: "145737", age: 29, gender: .male),
Person(name:"Grace", id: "295049", age: 34, gender: .female)]
...

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 - update object in place

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

Elegant way to combine [[(Int, String)]] into [(Int, String)]?

I am using Swift 4, Xcode 9.
Specifically, I have an array of arrays [[(Int, String)]] where Int is a rank and String is a Name + Items... that are combined using .joined(separator: ";").
The data might look like this:
[[1,"My Name;Item1;Item2"], [(5,"My Name;Item2;Item3"), (3,"My Second Name;Item1")]]
I want to combine the inner arrays so that:
Int adds for matching items based on the name (up to ";")
Strings add subsequent items if not already present
Combining my above example should lead to this:
[(6,"My Name;Item1;Item2;Item3"), (3,"My Second Name;Item1")]
I.e. input is [[(Int, String)]] and output is [(Int, String)]
Currently, I can achieve this through a fairly complex set of loops. With a large dataset, this results in a noticeable performance drop. Is there an elegant/simple way to combine these arrays as I am requesting?
Thank you for any guidance!
(Generally I would make this a comment because it doesn't answer the question, but it feels worth the trouble to explain exactly how you should change this.)
This is certainly possible, but don't. The answer is to replace this with an array of structs. Based on your data description:
struct Element {
let rank: Int
let name: String
let items: Set<String> // Since you seem to want them to be unique and unordered
}
let elements: [[Element]] =
[[Element(rank: 1, name: "My Name", items: ["Item1", "Item2"])],
[Element(rank: 5, name: "My Name", items: ["Item2", "Item3"]),
Element(rank: 3, name: "My Second Name", items: ["Item1"])]]
// You want to manage these by name, so let's make key/value pairs of all the elements
// as (Name, Element)
let namedElements = elements.joined().map { ($0.name, $0) }
// Now combine them as you describe. Add the ranks, and merge the items
let uniqueElements =
Dictionary<String, Element>(namedElements,
uniquingKeysWith: { (lhs, rhs) -> Element in
return Element(rank: lhs.rank + rhs.rank,
name: lhs.name,
items: lhs.items.union(rhs.items))
})
// The result is the values of the dictionary
let result = uniqueElements.values
// Element(rank: 6, name: "My Name", items: Set(["Item3", "Item2", "Item1"]))
// Element(rank: 3, name: "My Second Name", items: Set(["Item1"]))

Compare objects and edit array [duplicate]

This question already has answers here:
How to group by the elements of an array in Swift
(16 answers)
Closed 6 years ago.
I want to create array of unique elements by specific property.
Ex:
I have array of objects (Person) :
struct Person {
var name: String?
var secondName: String?
init (name: String, secondName: String) {
self.name = name
self.secondName = secondName
}
}
let person1 = Person(name: "name1", secondName: "secondName1")
let person2 = Person(name: "name2", secondName: "secondName2")
let person3 = Person(name: "name1", secondName: "secondName3")
let personsArray = [person1, person2, person3]
I want to get new array, that will contain person objects with unique name
something like this $0.name == $1.name
What is the best way to achieve that ?
Result should be arrays of objects with unique name param = [[person1, person3], [person2]]
This is my personal interpretation of your question
Given an array of Person(s) you want in output several dictionaries where the key is the name of a person and the value is a list of persons with that name.
Here's the code
let dict = persons.reduce([String:[Person]]()) { (dict, person) -> [String:[Person]] in
var dict = dict
dict[person.name] = (dict[person.name] ?? []) + [person]
return dict
}
One approach: You could add them one by one to a dictionary where "name" is the key (consider using lowercase for it), and "array of Persons" is the value. When done, the keys array will have all your unique "name" values, and each key's value will be the array of Persons with that "name". You could then "trim" your dictionary by removing any key with an array that has a count less than 2.
Alternative: Sort the array by "name", then you can easily remove any that don't appear twice (if an element doesn't match one of it's neighbors, then remove it).

Resources