I am on Swift 4. The goal is to load all the data in an address book, before render the address book in view. In a different language such as js
, I may use await
in each item in the loop, before telling the view to render the rows. I am looking for the canonical way to solve this issue in Swift 4
with UITableViewController
.
Right now the address book is stored in backend with Amplify
and GraphQL
. I have a User
model of form
type User @Model {
id: ID!
name: String!
bio : String!
}
and Contact
of form
type Contact @model {
ownerId: ID!
userId: ID!
lastOpened: String
}
In ContactController: UITableViewController.viewDidLoad
I fetch all Contact
in database where the ownerId
is my user's id-token, I then create an object using this contact
information. And then for each Contact
object instance, I get its corresponding User
in database when the object is initialized. Per this post: Wait until swift for loop with asynchronous network requests finishes executing, I am using Dispatch
group, and then reload the UITableView
after the loop completes and the Dispatch
group has ended. But when I print
to console, I see that the loop completes before the Contact
object has loaded its User
information.
Code snippets:
class ContactsController: UITableViewController, UISearchResultsUpdating {
var dataSource : [Contact] = []
override func viewDidLoad() {
super.viewDidLoad()
let fetchContactGrp = DispatchGroup()
fetchContactGrp.enter()
self.getMyContacts(){ contacts in
for contact in contacts {
let _contactData = Contact(
userId : contact.userId
, contactId : contact.id
, timeStamp : contact.timeStamp
, lastOpened : contact.lastOpened
, haveAccount: true
)
_contactData.loadData()
self.dataSource.append(_contactData)
}
}
fetchContactGrp.leave()
DispatchQueue.main.async{
self.tableView.reloadData()
}
}
}
The function self.getMyContacts
is just a standard GraphQL
query:
func getMyContacts( callBack: @escaping ([Contact]) -> Void ){
let my_token = AWSMobileClient.default().username
let contact = Contact.keys
let predicate = contact.ownerId == my_token!
_ = Amplify.API.query(from: Contact.self, where: predicate) { (event) in
switch event {
case .completed(let result):
switch result {
case .success(let cts):
/// @On success, output a user list
callBack(cts)
case .failure(let error):
break
}
case .failed(let error):
break
default:
break
}
}
}
And the Contact
object loads the User
data from database:
class Contact {
let userId: String!
let contactId: String!
var name : String
var bio : String
var website: String
let timeStamp: String
let lastOpened: String
init( userId: String, contactId: String, timeStamp: String, lastOpened: String, haveAccount: Bool){
self.userId = userId
self.contactId = contactId
self.timeStamp = timeStamp
self.lastOpened = lastOpened
self.haveAccount = haveAccount
self.name = ""
self.bio = ""
self.website = ""
}
func loadData(){
/// @use: fetch user data from db and populate field on initation
let _ = Amplify.API.query(from: User.self, byId: self.userId) { (event) in
switch event {
case .completed(let res):
switch res{
case .success (let musr):
if (musr != nil){
let userData = musr!
let em = genEmptyString()
self.name = (userData.name == em) ? "" : userData.name
self.bio = (userData.bio == em) ? "" : userData.bio
self.website = (userData.website == em) ? "" : userData.website
print(">> amplify.query: \(self.name)")
} else {
break
}
default:
break
}
default:
print("failed")
}
}
}
}