Problem with mapping an array of objects in Swift - swift

I have the following 2 class / structs:
class ConversationDetails {
var messages: [ChatMessage]?
var participants: [User]?
}
class User: Codable {
init (email: String) {
self.email = email
}
// system
var id: String?
var verifiedaccount: Int?
var rejected: Int?
...
}
I've further got the var conversationDetails = ConversationDetails () and I'm populating it with an API call. That all works fine.
I'd like to map the participants array inconversationDetailsand access the id property of each participant like so:
let recipient_ids = self.conversationDetails.participants.map( { (participant) -> String in
return participant.id
})
In my understanding, map iterates over the entire participants array, which is an array of User objects and I can access each item via participant.
However, I get Value of type '[User]' has no member 'id' for return participant.id.
Where is my misunderstanding?

Your participants var is optional, so you need to add question mark to access array. Without it, you try to call map on optional.
This is working code:
let recipientIds = conversationDetails.participants?.map( { (participant) -> String in
return participant.id
})
or shorter:
let recipientIds = conversationDetails.participants?.map { $0.id }
Also, you can use compactMap to remove nils from recipientIds array and have [String] array instead of [String?]:
let recipientIds = conversationDetails.participants?.compactMap { $0.id }

The first problem is that participants is optional so you need to add a ? when accessing it
conversationDetails.participants?
then when mapping you should use compactMap since id is also an optional property
let recipient_ids = conversationDetails.participants?.compactMap { $0.id }
Another variant is to not have an optional array but instead initialize it to an empty array. This is actually a much better way to handle collection properties because you can have a cleaner code by initializing them to an empty collection
var participants = [User]()
and then do
let recipient_ids = conversationDetails.participants.compactMap { $0.id }

Related

Swift Combine: Separate published array and then assign

I have a publisher which returns an array of RetailStoreSlotDay objects. I need to separate these out based on a certain property and then assign to separate publishers within the view model.
So, my publisher is:
#Published var selectedDaySlot: RetailStoreSlotDay?
Within the RetailStoreSlotDay object I have a property called 'daytime' which is set to either:
"morning"
"afternoon"
"evening"
I then have these separate publishers that I need to assign values to when the selectedDaySlot is amended:
#Published var morningTimeSlots = [RetailStoreSlotDayTimeSlot]()
#Published var afternoonTimeSlots = [RetailStoreSlotDayTimeSlot]()
#Published var eveningTimeSlots = [RetailStoreSlotDayTimeSlot]()
At the moment, I have the following subscription set up and declared in the init of the view model:
private func setupDeliveryDaytimeSectionSlots() {
$selectedDaySlot
.map { timeSlot in
return timeSlot?.slots
}
.replaceNil(with: [])
.sink(receiveValue: { slots in
self.morningTimeSlots = slots.filter { $0.daytime == "morning" }
self.afternoonTimeSlots = slots.filter { $0.daytime == "afternoon" }
self.eveningTimeSlots = slots.filter { $0.daytime == "evening" }
})
.store(in: &cancellables)
}
This works fine, but I'm sure there must be an operator which will perform this in a more sophisticated way, whereby I can assign the values without using sink. Wondering if there is a better way around this.
You could group these slots into a dictionary, using Dictionary(grouping:by:):
let dictionaryPublisher = $selectedDaySlot
.map { timeSlot in
Dictionary(grouping: timeSlot?.slots ?? [], by: \.daytime)
}
Then you can assign the values associated with the different keys of the dictionary to the different properties:
dictionaryPublisher.map { $0["morning"] ?? [] }.assign(to: &self.$morningTimeSlots)
dictionaryPublisher.map { $0["afternoon"] ?? [] }.assign(to: &self.$afternoonTimeSlots)
dictionaryPublisher.map { $0["evening"] ?? [] }.assign(to: &self.$eveningTimeSlots)
Rather than using strings as the dayTime values, consider using an enum instead:
enum TimeOfDay: Hashable {
case morning, afternoon, evening
}

How can i extract the array of the objects in Swift?

i have json objects like and parsed in an array
let objects = [Object]()
struct Object {
name: String
id: Int
}
Suppose like
let objects [Object(name:oscar, id: 11), Object(name:sanchez, id: 12),Object(name:emily, id: 15),Object(name:clarck, id: 31) ... ]
How can i take the string array as below also with this name which object belongs to ? ( so i can use object easily)
let stringPropertyArray = [oscar, sanchez,emily,clarck ... ]
Thanks
how i will find the object ? if you have "emily" and i want to item.id which emily belongs to ?
Perhaps you want something like
if let ob = objects.first {$0.name == "emily"} {
print(ob.id)
}
But if your goal is to search quickly, it would be better to have a dictionary keyed by the value you will be searching on.
I think this is what you want
let stringPropertyArray: [String] = objects.map {$0.name}
There are 2 approaches you can use:
by looping (traditional approach)
var listName: [String] = []
for item in objects {
listName.append(item.name)
}
by using higher order function
let listName = objects.map{ $0.name }
There would be a case if your name property is optional and for some object, name property value is nil then we should use compactMap higher order function in order to avoid nil object in the list
let listName = objects.compactMap{ $0.name }
To find any specific object we can use filter like below:
let object = objects.filter{
$0.name == "sanchez" }.first
// OR
let object = objects.first { object -> Bool in
object.name == "emily" }

how to read data into object array in Swift 4

class Employee{
var id:Int
var name:String
var salary:Int
init(){
self.id=0
self.name=""
self.salary=0
}
func getInfo(){
self.name=readLine()!
self.id=Int(readLine()!)!
self.salary=Int(readLine()!)!
}
}
var count=0
var flag="y"
var empData:[Employee]=[]
repeat{
count+=1
empData[count]=Employee()
empData[count].getInfo()
flag=readLine()!
}while(flag=="y") `
I have a class Employee with properties id , nam and salary. The function getInfo() is used to get information from user. I want to read data until the flag!="y" . I am getting index out of range error.
What is the right way of inputting data? Can we index the objects ?
You need to append to your array to make it increase in size. Replace
empData[count]=Employee()
with
empData.append(Employee())
to avoid index out of range error
Update
To make your code a little less horrible I would do
repeat {
var employee = Employee()
employee.getInfo()
empData.append(employee)
flag=readLine()!
}while( flag == "y" )
The subscript operator cannot be used to add elements to an array index which doesn't exist yet. You either need to initialize the array with an element count if you know at the time of initialization how many elements your array will have or use the append operator to add new elements to the array after the last index.
You don't even need the count variable, as you can simply access empData.last safely after calling append and adding a new Employee to the Array.
var flag="y"
var empData:[Employee]=[]
repeat {
empData.append(Employee())
empData.last!.getInfo()
flag=readLine()!
} while(flag=="y")
I would advise you to seriously reconsider your implementation as it is really unsafe at the moment. You are not validating user input in any way, hence your getInfo function can easily cause runtime errors if the user input is not in the expected form. Moreover, creating an empty initializer for Employee doesn't make sense, you could simply create a failable initializer, where you read the input and if the input is not of the correct form, make the initializer return nil.
class Employee{
let id:Int
let name:String
let salary:Int
init?(){
guard let name = readLine() else { return nil }
self.name = name
guard let idString = readLine(), let id = Int(idString) else { return nil }
self.id = id
guard let salaryString = readLine(), let salary = Int(salaryString) else { return nil}
self.salary = salary
}
}
var flag="y"
var empData:[Employee]=[]
repeat {
if let employee = Employee() {
empData.append(employee)
} else {
// Display error message to the user
}
flag=readLine() ?? ""
} while(flag=="y")

How to retrieve the ‘List<myObject>' realm array to array from realm model class?

My realm model class look like
class RoomRealmModel : Object {
dynamic var id: String = ""
var details = List<RoomDetailRealmModel>()
func saveItem() {
do {
let realm = try Realm()
realm.beginWrite()
realm.add(self, update: true)
try realm.commitWrite()
} catch{}
}
}
class RoomDetailRealmModel : Object{
dynamic var detailId: String = ""
dynamic var displayText: String = ""
}
I want to retrieve 'details' from the following.
details = RLMArray<RoomDetailRealmModel> <0x600000114f40> (
[0] RoomDetailRealmModel {
text = hello;
Counters = 9;
ParentID = ;
detailId = 33;
displayText = hello ;
}
);
I always get empty like in my console
(lldb) po (destinationData?[index]?.details)!
List<RoomDetailRealmModel> <0x600000853620> (
)
I am updating ‘details’ list via realm update command. I always get realm array.But I want to retrieve array type from realm array.Please help me, how to solve this issue
If you want to obtain [myObject] instead of List you can do something like this:
var array: [myObject] = [myObject]()
for object in myObjectList {
array.append(object)
}
Where myObjectList is List.
You can simply create a regular Swift Array from a Realm List by calling the initializer of Array accepting a Sequence, since List conforms to the sequence protocol.
So you can simply do
let room = RoomRealmModel()
let roomDetailsArray = Array(room.details)

Swift diff realm.io without fetching it in advance

I was wondering if there is a possibility in realm.io (swift) to select all items from one "table" that are not in the other one.
Lets say you have 2 classes:
class A: Object {
dynamic var id: Int = 0
dynamic var text: String = ""
}
class B: Object {
dynamic var id: Int = 0
dynamic var value: Bool = false
}
Is it possible to get an result of items from A who's id is not present in B?
There is actually a very simple way to do this using NSPredicate on Realm filter API.
func fetch() throws -> [A] {
do {
// Create Realm
let realm = try Realm()
// Get B objects from Realm and put their IDs to [Int] array
let IdB: [Int] = realm.objects(B).map { $0.id }
// Create predicate
// Filter all items where property id is not present in array IdB
let predicateFilter = NSPredicate(format: "NOT (id IN %#)", IdB)
// Get all A objects from array using predicateFilter
let objectsA = realm.objects(A).filter(predicateFilter)
// Return the [A] array
return objectsA.map { $0 }
} catch {
// Throw an error if any
throw error
}
}
Also note that all objects from fetched using Realm are lazy loaded which means that this method is also very fast. From the documentation:
All queries (including queries and property access) are lazy in Realm. Data is only read when the properties are accessed.