Reload data in swift, of a tableview every 30 seconds - swift

So I have a table that takes some data from my data base and displays it in his cells. I made a refresh button that reloads my code so the user can be able to see the new entries that somebody added into the data base, when you tap it.
This is how the code of my refresh button looks like:
#IBAction func refreshButton(sender: AnyObject) {
textArray.removeAllObjects()
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Conversation", predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
publicCloudDatabase?.performQuery(query, inZoneWithID: nil,
completionHandler: ({results, error in
if (error != nil) {
dispatch_async(dispatch_get_main_queue()) {
self.notifyUser("Cloud Access Error",
message: error.localizedDescription)
}
} else {
if results.count > 0 {
var record = results[0] as! CKRecord
self.currentRecord = record
dispatch_async(dispatch_get_main_queue()) {
for x in results{
self.textArray.addObject(x.objectForKey("message") as! String)
self.tableView.reloadData()
}
}
} else {
dispatch_async(dispatch_get_main_queue()) {
self.notifyUser("No Match Found",
message: "No record matching the address was found")
}
}
}
}))
}
I'd like to remove that button and make a refresh of the table every 30 seconds (in the background of the app - i don't want the user to be aware of it), it is that possible ? If yes, how can it be done? Thank you!

Using timers is one way you can achieve,
In ViewDidload
self.myTimer = NSTimer(timeInterval: 30.0, target: self, selector: "refresh", userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(self.myTimer, forMode: NSDefaultRunLoopMode)
func refresh() {
// a refresh of the table
}

1.performQuery every 30s.
2.compare the different between old datas and new datas.
3.Insert the different part by using this Api
(void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
rather than reloadData

Related

Code not finishing the function, it is ending execution halfway through

My code is as follows:
#IBAction func clicked(_ sender: Any) {
let ref = Database.database().reference()
let pass = password.text
var firpass = ""
var bool = false;
ref.child(name.text as! String).child("password").observeSingleEvent(of: .value, with: { dataSnapshot in
firpass = dataSnapshot.value as! String
if firpass == pass {
bool = true
print("in here")
}
})
print(bool)
if bool {
self.sendname = name.text!
let vc = DatabaseTableViewController(nibName: "DatabaseTableViewController", bundle: nil)
vc.finalName = self.sendname
navigationController?.pushViewController(vc, animated: true)
performSegue(withIdentifier: "username", sender: self)
} else {
let alert = UIAlertController(title: "Error", message: "Incorrect username or password", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
"in here" gets printed, but bool is never printed and the alert is showing. Why does my code not enter the if bool block and output the alert?
Data is loaded from Firebase asynchronously, since it may take a while. Instead of making your app wait for the data (which would be a bad user experience), your main code continues while the data is being loaded, and then once the data is available your closure is called.
This explains the behavior you're seeing: by the time your runs, the hasn't run yet.
the solution is as simple as it is initially confusing and annoying: any code that needs the data from the database must be inside the closure, or be called from there.
So for example:
ref.child(name.text as! String).child("password").observeSingleEvent(of: .value, with: { dataSnapshot in
firpass = dataSnapshot.value as! String
if firpass == pass {
bool = true
print("in here")
}
print(bool)
if bool {
self.sendname = name.text!
let vc = DatabaseTableViewController(nibName: "DatabaseTableViewController", bundle: nil)
vc.finalName = self.sendname
navigationController?.pushViewController(vc, animated: true)
performSegue(withIdentifier: "username", sender: self)
} else {
let alert = UIAlertController(title: "Error", message: "Incorrect username or password", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
})
Also see:
Firebase with Swift 3 counting the number of children
Array of struct not updating outside the closure
getting data out of a closure that retrieves data from firebase (showing examples with custom callbacks and delegates)
How to reload data after all Firebase calls finished? (showing how to use a dispatch group)
Finish all asynchronous requests before loading data? (another example using a dispatch group)
Also you have to set variable bool to false when you are navigating to next view controller after login. So that you login again and if password is wrong then you can not navigate to next page and only it shows alert for wrong password.

Converting a Core Data Entity to a CKRecord for sharing using the UICloudSharingController with Seam3

When trying to instantiate a CKRecord from my current datastore, I am unable to initialize it using Seam3. The core data stack and CloudKit are in perfect sync thanks to Seam3. I'm bulding a wishlist application that enables list sharing and I'm trying to implement this feature. Without a CKRecord to send to UICloudSharingController, I can't finish this feature.
I have tried converting to a CKRecord by hand and I don't know what I'm doing wrong.
func loadData() {
let fetchRequest = NSFetchRequest<List>(entityName: "List")
let sortDescriptor = NSSortDescriptor(key: "title", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
if let result = try? dataController.viewContext.fetch(fetchRequest) {
lists = result
tableView.reloadData()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadData()
NotificationCenter.default.addObserver(forName: Notification.Name(rawValue: SMStoreNotification.SyncDidFinish), object: nil, queue: nil) { [weak self] notification in
if notification.userInfo != nil {
self?.dataController.smStore?.triggerSync(complete: true)
}
self?.dataController.viewContext.refreshAllObjects()
DispatchQueue.main.async {
self?.loadData()
}
}
}
These are the ways I am populating the tableView. I'm not sure how Seam3 works in the background to populate with the CloudKit Items. But its a seamless bridging between CloudKit and CoreData. Any help would be appreciated.... Thanks!

Cloudkit error handling not being called if qualityOFService is missing

I am trying to handle Cloudkit errors. I read this post: CloudKit - full and complete error handling example
but i have a few questions:
1.Why is the error handling not working if i dont set .qualityOFService? and is .userInitiated correct, as on the post mentioned it is set to .background?. Also, do i have to set it for the
loadDataAgain()?
2.How can i make the error handling reusable that will take an error and another parameter(according to the viewcontroller that is
called from)?
#objc func loadData() {
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: myRecordType.type, predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let queryOperation = CKQueryOperation(query: query)
queryOperation.resultsLimit = 5
//if the below line is missing errors will not be handled
queryOperation.qualityOfService = .userInitiated
queryOperation.recordFetchedBlock = { item in
self.array.append(item)
}
queryOperation.queryCompletionBlock = { cursor, error in
if error != nil{
if let ckerror = error as? CKError {
if ckerror.code == CKError.requestRateLimited {
let retryInterval = ckerror.userInfo[CKErrorRetryAfterKey] as? TimeInterval
DispatchQueue.main.async {
Timer.scheduledTimer(timeInterval: retryInterval!, target: self, selector: #selector(self.loadData), userInfo: nil, repeats: false)
}
} else if ckerror.code == CKError.zoneBusy {
let retryInterval = ckerror.userInfo[CKErrorRetryAfterKey] as? TimeInterval
DispatchQueue.main.async {
Timer.scheduledTimer(timeInterval: retryInterval!, target: self, selector: #selector(self.loadData), userInfo: nil, repeats: false)
}
} else if ckerror.code == CKError.limitExceeded {
let retryInterval = ckerror.userInfo[CKErrorRetryAfterKey] as? TimeInterval
DispatchQueue.main.async {
Timer.scheduledTimer(timeInterval: retryInterval!, target: self, selector: #selector(self.loadData), userInfo: nil, repeats: false)
}
} else if ckerror.code == CKError.notAuthenticated {
//present relevant alert with action to reload ViewController
} else if ckerror.code == CKError.networkFailure {
//present relevant alert with action to reload ViewController
} else if ckerror.code == CKError.networkUnavailable {
//present relevant alert with action to reload ViewController
} else if ckerror.code == CKError.quotaExceeded {
//present relevant alert with action to reload ViewController
} else if ckerror.code == CKError.partialFailure {
//present relevant alert with action to reload ViewController
} else if (ckerror.code == CKError.internalError || ckerror.code == CKError.serviceUnavailable) {
//present relevant alert with action to reload ViewController
}
}
}
else{
if cursor != nil {
self.loadDataAgain(cursor!)
}
}
OperationQueue.main.addOperation {
self.tableView.reloadData()
}
}
func loadDataAgain(_ cursor: CKQueryOperation.Cursor) {
let queryOperation = CKQueryOperation(cursor: cursor)
queryOperation.resultsLimit = 5
queryOperation.recordFetchedBlock = { item in
self.array.append(item)
}
queryOperation.queryCompletionBlock = { cursor, error in
if error != nil{
//have the same error handling as above
}
else{
if cursor != nil {
self.loadDataAgain(cursor!)
}
}
OperationQueue.main.addOperation {
self.tableView.reloadData()
}
}
Database.share.publicDB.add(queryOperation)
}
With respect to your first question, see this answer that suggests errors aren't reported for lower quality of service settings:
it's because [the] system is waiting for a better network condition, while [if] you set .default or .userInitiated the system expects a "real time" response
If you have a look at Apple's Energy Efficiency Guide for iOS Apps documentation section titled "About CloudKit and Quality of Service," Apple seems to confirm this approach, saying
CKOperation is a subclass of the NSOperation class. Although the NSOperation class has a default QoS level of NSQualityOfServiceBackground, CKOperation objects have a default QoS level of NSQualityOfServiceUtility. At this level, network requests are treated as discretionary when your app isn’t in use. On iPhones, discretionary operations are paused when Low Power Mode is enabled.
You'll need to explicitly set a different QOS for every CKOperation you create if you don't want the default .utility. If you find yourself doing this a lot with related operations, I'd suggest looking at CKOperationGroup and its default configuration property - if you set a QOS in the configuration it'll override the default in any CKOperations in the group.

Prepare for segue data not being loaded

I have a carousel and each carousel item contains a unique tableview. When you tap the add button it takes you to a new view controller so that you can give the tableview a name and associate items to display in each cell. Now when I hit the save button it performs the segue back to the carousel's viewcontroller. My prepareforsegue looks like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! BillSplittersViewController
let passedBill: NSManagedObject = bill as NSManagedObject
destinationVC.allBillSplitters = getUpdatedSplitters()
print(2)
print(destinationVC.allBillSplitters)
destinationVC.billName = billName
destinationVC.bill = passedBill
}
When the carousel view controller is displayed it doesn't show the added item in the carousel which loads from the allBillSplitters array. If I try reloading the carousel in viewdidload, viewwillappear or view willlayoutsubviews nothing changes. It does update if I do it in viewdidlayoutsubviews but you can see this happening which isn't great.
To debug I tried printing the array in prepareforsegue and then in viewdidload and it prints the array in prepareforsegue without the added item and the does the same thing in viewdidload -- but then it prints them again with the added item -- but I can't see it in the loaded view.
I'm using iCarousel if that helps. I am new to this and unsure what's going on so I don't know what code to use. I am using storyboards for both viewcontrollers and the segue is attached to the view and not the button itself. Any help would be great. Thanks!
More information:
#IBAction func saveButtonWasPressed() {
let managedContext = bill.managedObjectContext
if let splitter = splitter {
splitter.mutableSetValue(forKey: "items").removeAllObjects()
setBillSplitterValues(splitter)
setSelectedItemsToBillSplitter(splitter)
} else {
let entity = NSEntityDescription.entity(forEntityName: "BillSplitter", in: managedContext!)
let newBillSplitter = NSManagedObject(entity: entity!, insertInto: managedContext)
setBillSplitterValues(newBillSplitter)
setSelectedItemsToBillSplitter(newBillSplitter)
}
do {
try managedContext!.save()
}
catch let error as NSError {
print("Core Data save failed: \(error)")
}
self.performSegue(withIdentifier: "segueToBillSplitters", sender: self)
}
retrieving the new billSplitters:
func getUpdatedSplitters() -> [BillSplitter] {
var allBillSplitters = [BillSplitter]()
let managedContext = bill.managedObjectContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "BillSplitter")
let predicate = NSPredicate(format: "ANY bills == %#", bill)
fetchRequest.predicate = predicate
do {
let results =
try managedContext!.fetch(fetchRequest)
allBillSplitters = results as! [BillSplitter]
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
return allBillSplitters
}
So when pressing save, it either updates an existing 'billSplitter' to coredata or adds a new one, then calls performSegue. In prepareForSegue it then fetches all the 'billSplitters' from CoreData and passes them to the destination view controller.
I thought a NSManagedObjectContextDidSave observer might help if it called performSegue once the notification was received but nothing changed.
Update:
I have tried the following in my saveButtonWasPressed method. When the viewcontroller loads, it quickly shows the old carousel then flickers and shows the updated carousel. I'm not sure where to go from here.
#IBAction func saveButtonWasPressed() {
DispatchQueue.global(qos: .background).async { [weak weakSelf = self] in
let managedContext = weakSelf?.bill.managedObjectContext
if let splitter = weakSelf?.splitter {
splitter.mutableSetValue(forKey: "items").removeAllObjects()
weakSelf?.setBillSplitterValues(splitter)
weakSelf?.setSelectedItemsToBillSplitter(splitter)
} else {
let entity = NSEntityDescription.entity(forEntityName: "BillSplitter", in: managedContext!)
let newBillSplitter = NSManagedObject(entity: entity!, insertInto: managedContext)
weakSelf?.setBillSplitterValues(newBillSplitter)
weakSelf?.setSelectedItemsToBillSplitter(newBillSplitter)
}
do {
try managedContext!.save()
}
catch let error as NSError {
print("Core Data save failed: \(error)")
}
DispatchQueue.main.async { [weak weakSelf = self] in
guard let weakSelf = weakSelf else { return }
weakSelf.performSegue(withIdentifier: "segueToBillSplitters", sender: self)
}
}
}
I may not understand what you are asking exactly but my only input is that when I use prepare for segue I always use a segue identifier
var valueToPass
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if (segue.identifier == "yourSegue") {
let viewController = segue.destination as! ViewController
viewController.passedValue = valueToPass as String
}
Your declare your identifier in the storyboard, also make sure your segue is an action segue

How to get contact list from device in ios swift?

I have tried to get the contact list from device but something went wrong so I did not get the contact list.
This is my code what am I doing wrong?
let addressBook = APAddressBook()
override func viewDidLoad() {
super.viewDidLoad()
self.registerCellClass(AddressTableViewCell.self, forModelClass: APContact.self)
self.addressBook.fieldsMask = APContactField.Default | APContactField.Thumbnail
self.addressBook.sortDescriptors = [NSSortDescriptor(key: "firstName", ascending: true),
NSSortDescriptor(key: "lastName", ascending: true)]
self.addressBook.filterBlock = {(contact: APContact!) -> Bool in
return contact.phones.count > 0
}
self.addressBook.loadContacts({ (contacts: [AnyObject]!, error: NSError!) in
if (contacts != nil) {
self.memoryStorage().addItems(contacts)
} else if (error != nil) {
let alert = UIAlertView(title: "Error", message: error.localizedDescription, delegate: nil, cancelButtonTitle: "OK")
alert.show()
}
})
}
I assume that you're hitting the addItems line, but that you're not seeing it reflected in the table view? If that's the case, the issue is likely that loadContacts runs asynchronously, so you have to call reloadData on your table view when it's done. Otherwise your table view will never get updated after APAddressBook finishes loading the contacts.