how to get array of contact from [(String?, [Contact])]()? - swift

I have var contacts = [(String?, [Contact])]() I need to extract all contact arrays to put it in var filteredContacts = [Contact]() to be use in search logic.
updatee the Account and Accounts object
//Account
struct Account: Codable {
var id, displayName:String
var contacts: [Contact]?
...
}
//Accounts
struct Accounts: Codable {
let accounts: [Account]
...
}
switch result {
case .success(let result):
self.accounts = result.accounts
self.contacts = Array(result.accounts
.compactMap { account in account.contacts.map { (account.displayName , $0) } }.dropFirst())
self.filteredContacts = ??

You can simply use flatMap and use the second element of the tuple as the keyPath:
self.filteredContacts = contacts.flatMap(\.1) // for older swift syntax you can use `flatMap { $1 }`

A little late to the party but if you want to use accounts or result.accounts directly you can do
self.filteredContacts = Array(result.accounts.compactMap(\.contacts).joined())

use a simple for each
contacts.forEach({
self.filteredContacts.append(contentsOf: $0.1)
})

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
}

Retrieving records on a Reference List (hierarchical records) - Swift Concurrency & CloudKit

I am using CloudKit (the default public database) to store information about matches between two teams. I am trying to use the Swift Concurrent approach with async/await to retrieve the match and team data.
The matches are stored as “Match” RecordType with some metadata, like the year the match was played (“matchYear”). The “Match” Record Type also contains a Reference List to a RecordType called “Team” (two teams for each match). The team names are stored in the “Team” Record Type (field “teamName”)
Rough description of my CloudKit Schema
“Match” Record Type
Fields:
matchYear: Int64
teams: Reference (List)
…
“Team” Record Type
Fields:
teamName: String
…
With the following code I am able to read all “Match” records from the CloudKit and store that list to an array of MatchRecords, which I can then use to display a list of matches (using SwiftUI, if that makes any difference). The resulting array also contains references to the teams for each match.
struct MatchRecord {
var recordID: CKRecord.ID
var matchYear: Int
var teams: [CKRecord.Reference]
…
}
…
private lazy var container = CKContainer.default()
private lazy var database = container.publicCloudDatabase
…
#MainActor func loadMatchList() async throws -> [MatchRecord] {
let predicate = NSPredicate(value: true)
let sortDescriptors = [NSSortDescriptor(key: “matchYear", ascending: false),NSSortDescriptor(key: "___createTime", ascending: false)]
let query = CKQuery(recordType: “Match”, predicate: predicate)
query.sortDescriptors = sortDescriptors
let (matchResults, _) = try await database.records(matching: query)
let allMatches: [MatchRecord] = matchResults
.compactMap { _, result in try? result.get() }
.compactMap {
let match = MatchRecord(
recordID: $0.recordID,
matchYear: $0[“matchYear"] as! Int,
teams: $0["teams"] as! [CKRecord.Reference])
// Potentially team record retrieval goes here…
return match
}
return allMatches
}
How do I retrieve the Team records as part of this async function so that I will have also the names of the teams available for the list view?
(I could potentially first fetch the list of matches and then loop through that array and retrieve the detail data for each match but that seems wasteful. I am guessing there should be a way to insert this in the compactMap closure marked down in the code sample above, but my map/reduce and async/await skills fail me…)
The data structure could be something along as described below.
struct MatchRecord {
var recordID: CKRecord.ID
var matchYear: Int
var teams: [TeamRecord]
…
}
struct TeamRecord {
var recordID: CKRecord.ID
var teamName: String
…
}
FWIW I know that as there are only two teams for each game, I could also store the team names as part of the Match record, but In the future I am planning to also include the roster information for each team, so I need to come up with a clean and scalable method to retrieve this type of hierarchical data from CloudKit…
Using CoreData with CloudKit is not an option here.
After some thinking I came up with one somewhat ugly solution using async properties. The Match and Team struct definitions are roughly these
struct MatchRecord {
var recordID: CKRecord.ID
var matchYear: Int
var teams: [TeamRecord]
…
}
struct TeamRecord {
var referenceID: CKRecord.Reference
var name: String {
get async {
try! await CKContainer.default().publicCloudDatabase.record(for: referenceID.recordID)["teamName"] as! String
}
}
}
The compactMap gets small for loop in there to populate the teams structure (only storing the ID as the names are retrieved only later)
.compactMap {
let match = MatchRecord(
recordID: $0.recordID,
matchYear: $0[“matchYear"] as! Int,
teams: [])
for item in ($0["teams"] as! [CKRecord.Reference]) {
match.teams.append(TeamRecord(referenceID: item))
}
return match
}
And when displaying the list, the list rows are defined in a separate view, where the async properties are pulled in using a task. Along these lines
struct MatchRow: View {
...
#State var team0: String = ""
#State var team1: String = ""
var body: some View {
...
Text(verbatim: "\(match.matchYear): \(team0) - \(team2)")
.task{
guard team0.isEmpty else { return }
let (team0Result, team1Result) = await (match.teams[0].name, match.teams[1].name)
team0 = team0Result
team1 = team1Result
}
}
}
}
I am sure there is a more elegant solution for this. I especially do not like the idea of adding the task in the list row view as that splits the logic in many places...

Coinbase API parsing into Swift App returns incorrect formatting

I am using coinmarketcap api to fetch coin prices using the code down below. The data model Coin is also given below after the code as well as the JSON response. I get an error "The data couldn’t be read because it isn’t in the correct format." What should the correct formating look like?
'''
import Foundation
import SwiftUI
import Alamofire
class CryptoViewModel: ObservableObject {
func fetchData() {
let headers: HTTPHeaders = [
"Accepts": "application/json",
"X-CMC_PRO_API_KEY": "5dd693fc-6446-44c4-8aaa-75b1bfa4376f"
]
AF.request("https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest", headers: headers).response { response in
guard let data = response.data else { return }
do {
let coins = try JSONDecoder().decode([Coin].self, from: data)
print(coins)
}
catch {
print(error.localizedDescription)
}
}
}
}
'''
'''
import SwiftUI
struct Coin: Decodable {
var slug: String?
var symbol: String?
enum CodingKeys: String, CodingKey {
case slug = "slug"
case symbol = "symbol"
}
}
'''
'''
success({
data = (
{
"circulating_supply" = 18697137;
"cmc_rank" = 1;
"date_added" = "2013-04-28T00:00:00.000Z";
id = 1;
"last_updated" = "2021-05-02T14:22:02.000Z";
"max_supply" = 21000000;
name = Bitcoin;
"num_market_pairs" = 9549;
platform = "<null>";
quote = {
USD = {
"last_updated" = "2021-05-02T14:22:02.000Z";
"market_cap" = "1063000586851.752";
"percent_change_1h" = "0.09591311";
"percent_change_24h" = "-1.05109813";
"percent_change_30d" = "-4.45794679";
"percent_change_60d" = "11.80459387";
"percent_change_7d" = "14.06195861";
"percent_change_90d" = "69.54985569999999";
price = "56853.65555441735";
"volume_24h" = "40969975368.50657";
};
};
slug = bitcoin;
symbol = BTC;
tags = (
mineable,
pow,
"sha-256",
"store-of-value",
"state-channels",
"coinbase-ventures-portfolio",
"three-arrows-capital-portfolio",
"polychain-capital-portfolio",
"binance-labs-portfolio",
"arrington-xrp-capital",
"blockchain-capital-portfolio",
"boostvc-portfolio",
"cms-holdings-portfolio",
"dcg-portfolio",
"dragonfly-capital-portfolio",
"electric-capital-portfolio",
"fabric-ventures-portfolio",
"framework-ventures",
"galaxy-digital-portfolio",
"huobi-capital",
"alameda-research-portfolio",
"a16z-portfolio",
"1confirmation-portfolio",
"winklevoss-capital",
"usv-portfolio",
"placeholder-ventures-portfolio",
"pantera-capital-portfolio",
"multicoin-capital-portfolio",
"paradigm-xzy-screener"
);
"total_supply" = 18697137;
}, ...
'''
First, I think you might want to remove the API key in your example and reset it.
Regarding your question. Your response starts with a data property. To parse this you would need start your struct there as well.
So something like this should work;
struct Coins: Decodable {
let data: [Coin]
struct Coin: Decodable {
let symbol: String
let slug: String
let quote: [String: Quote]
}
struct Quote: Decodable {
let price: Double
}
}
I'm nesting the structs to preserve the namespace. Everything is pretty related. If you ever need access to one of them on it's own you could pull them out.
Also you can omit the CodingKeys, since the key is the same as your variable name. Also I don't think you need optionals there, but I'm not completely familiar with the API.
Furthermore I think you get only 1 set of data back and not an array of coins. So I would do the following here;
let coins = try JSONDecoder().decode(Coins.self, from: data)
You are parsing an entire response as an array of coins, but the actual data, that you are interested in, is located under data key, therefore you need to add an additional layer
struct Coins: Decodable {
let coins: [Coin]
}
and decode it instead of Coin
let coins = try JSONDecoder().decode(Coins.self, from: data)

Problem with mapping an array of objects in 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 }

Swift filter array of objects

class book{
var nameOfBook: String!
}
var englishBooks=[book(),book(),book()]
var arr = englishBooks.filter {
contains($0.nameOfBook, "rt")
}
I'm using this filter but with error cannot invoke filter with an argument
contains() checks if a sequence contains a given element, e.g.
if a String contains a given Character.
If your intention is to find all books where the name contains the substring "rt", then you can use rangeOfString():
var arr = englishBooks.filter {
$0.nameOfBook.rangeOfString("rt") != nil
}
or for case-insensitive comparison:
var arr = englishBooks.filter {
$0.nameOfBook.rangeOfString("rt", options: .CaseInsensitiveSearch) != nil
}
As of Swift 2, you can use
nameOfBook.containsString("rt") // or
nameOfBook.localizedCaseInsensitiveContainsString("rt")
and in Swift 3 this is
nameOfBook.contains("rt") // or
nameOfBook.localizedStandardContains("rt") // or
nameOfBook.range(of: "rt", options: .caseInsensitive) != nil
Sorry this is an old thread. Change you code slightly to properly init your variable 'nameOfBook'.
class book{
var nameOfBook: String!
init(name: String) {
nameOfBook = name
}
}
Then we can create an array of books.
var englishBooks = [book(name: "Big Nose"), book(name: "English Future
Prime Minister"), book(name: "Phenomenon")]
The array's 'filter' function takes one argument and some logics, 'contains' function can take a simplest form of a string you are searching for.
let list1 = englishBooks.filter { (name) -> Bool in
name.contains("English")
}
You can then print out list1 like so:
let list2 = arr1.map({ (book) -> String in
return book.nameOfBook
})
print(list2)
// print ["English Future Prime Minister"]
Above two snippets can be written short hand like so:
let list3 = englishBooks.filter{ ($0.nameOfBook.contains("English")) }
print(list3.map({"\($0.nameOfBook!)"}))
SWIFT 4.0
In order to filter objects and get resultant array you can use this
self.resultArray = self.upcomingAuctions.filter {
$0.auctionStatus == "waiting"
}
in case you want to delete an interval of object which has specific IDs (matchIDsToDelete) from an array of object (matches)
var matches = [Match]
var matchIDsToDelete = [String]
matches = matches.filter { !matchIDsToDelete.contains($0.matchID) }
2020 | SWIFT 5.1:
short answer:
books.filter { $0.alias.range(of: filterStr, options: .caseInsensitive) != nil }
long sample:
public filterStr = ""
public var books: [Book] = []
public var booksFiltered: [Book] {
get {
(filterStr.isEmpty )
? books
: books.filter { $0.alias.range(of: filterStr, options: .caseInsensitive) != nil }
}
}
I think this is more useful for lack of wrong typing situation.
englishBooks.filter( { $0.nameOfBook.range(of: searchText, options: .caseInsensitive) != nil}
In Swift 4.2 use the remove(where:) functionality. filter isn't doing well with memory, remove(where:) does the job better.
To do what you want:
englishBooks.removeAll { !$0.nameOfBook.contains("English") }