UITableView reloadData not working when viewcontroller is loaded a 2nd time - ios

I have a UITableView with custom cell displaying a list of files that can be downloaded. The cell displays the filename and download status. Everything working fine except one scenario :
The user downloads a file and navigates back to the home screen while file download in progress...
He comes back to the previous screen. File download still in progress.
File download complete. I am using tableview.reloadData() at this point to refresh the download status to "Download Complete" but reloadData() not working in this scenario. The cell label still shows "Download in progress".
Scrolling the tableview to get the cell out of screen and back refreshes the cell correctly. Anyway to do this programmatically?"
Otherwise, in normal case where user doesn't change screen, reloadData() is working fine.
Any idea how to fix this?
Thanks
I have used alamofire download with progress in the function below which is inside my UIViewController.
func DownloadFile(fileurl: String, indexPath: NSIndexPath, itemPath: String, itemName: String, itemPos: String, ItemSelected:Bool) {
let cell = myTableView.cellForRowAtIndexPath(indexPath) as! myCustomCell
let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask)
Alamofire.download(.GET, fileurl, destination: destination)
.progress {bytesRead, totalBytesRead, totalBytesExpectedToRead in
// This closure is NOT called on the main queue for performance
// reasons. To update your ui, dispatch to the main queue.
dispatch_async(dispatch_get_main_queue()) {
print("Total bytes read on main queue: \(totalBytesRead) / \(totalBytesExpectedToRead)")
let progress = Int((Double(totalBytesRead)/Double(totalBytesExpectedToRead)) * 100)
cell.lblMoreRuqyaFileInfo.text = "Downloading file...(\(progress)%)"
}
}
.response { _, _, _, error in
if let error = error {
print("\(error)")
cell.lblMoreRuqyaFileInfo.text = "Failed to download file. Please try again."
} else {
cell.lblMoreRuqyaFileInfo.text = "File Downloaded sucessfully"
//reloadData() not working from here
self.myTableView.reloadData()
}
}
}
The above func is being called in the tableview's editActionsForRowAtIndexPath below.
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
if myTableView.cellForRowAtIndexPath(indexPath) == nil {
let action = UITableViewRowAction()
return [action]
}
let cell = myTableView.cellForRowAtIndexPath(indexPath) as! myCustomCell
let fileurl = cell.textLabel!.text
let ruqyainfo = cell.lblMoreRuqyaFileInfo.text
let sItemPath = cell.lblCompiledRuqya.text! + "->" + cell.textLabel!.text! + "->\(indexPath.section)->\(indexPath.row)"
let sItemName = cell.lblCompiledRuqya.text!
let sItemPos = "->\(indexPath.section)->\(indexPath.row)"
var bItemSelected:Bool = false
if myTableView.cellForRowAtIndexPath(indexPath)?.accessoryType == UITableViewCellAccessoryType.Checkmark {
bItemSelected = true
} else {
bItemSelected = false
}
//check if file already downloaded,return empty action, else show Download button
if ruqyainfo?.containsString("Download") == false {
let action = UITableViewRowAction()
return [action]
}
let line = AppDelegate.dictCompiledRuqya.mutableArrayValueForKey(AppDelegate.dictCompiledRuqya.allKeys[indexPath.section] as! String)
let name = line[indexPath.row].componentsSeparatedByString("#")
let DownloadAction = UITableViewRowAction(style: .Normal, title: "Download\n(\(name[3]))") { (action: UITableViewRowAction!, indexPath) -> Void in
self.myTableView.editing = false
AppDelegate.arrDownloadInProgressItem.append(name[0])
self.DownloadFile(fileurl!, indexPath: indexPath, itemPath: sItemPath, itemName: sItemName,itemPos: sItemPos, ItemSelected: bItemSelected)
}
DownloadAction.backgroundColor = UIColor.purpleColor()
return [DownloadAction]
}

//Objective C
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.yourTableView reloadData];
}
//Swift
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.yourTableView.reloadData()
}

you can use delegates so that the download controller can tell the first controller that the download is complete, and that it should redraw the tableView - or at least the indexPath that has just completed.
set up the download controller like this
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
let destinationController : DownloadController = segue.destinationViewController as! DownloadController
destinationController.delegate = self
destinationController.indexRowToRefresh = currentlySelectedIndexRow
}
and then in the download completion closure, add something like this
delegate.refreshTableRow(indexRowToRefresh)
and in your first controller, implement the delegate method to refresh this row (or the whole table)
func refreshTableRow(indexRowToRefresh : Int)
{
var indexPath = NSIndexPath(forRow: indexRowToRefresh, inSection: 0)
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Top)
}

Hey #John Make sure your TableView datasource is reflected with file upload status permanently.I think, when you complete file uploading you change status in tableview datasource
1. First scenario as you are switching to second view controller, and coming back to previous view, it might be reinitializing your datasource. Data source might wasn't permanently reflected.2. In normal scenario, as you are on same view controller(not switching to other). Tableview datasource might be not reinitialized holding updated file upload status.
I suggest to save user file download status saved at some persistant storage or on web server. That doesn't leads to inconsistency in data.

It could be a thread related issue (aka you're coming back from the download thread, not on the UI thread, hence the data is refreshed but not displayed).
Try using this on selfand pass it a selector that refreshes your tableview.
performSelectorOnMainThread:

You used :
let cell = myTableView.cellForRowAtIndexPath(indexPath) as! myCustomCell
and set text for lblMoreRuqyaFileInfo :
cell.lblMoreRuqyaFileInfo.text = "File Downloaded sucessfully"
so, you don't have to call self.myTableView.reloadData()
Try:
dispatch_async(dispatch_get_main_queue()) {
cell.lblMoreRuqyaFileInfo.text = "File Downloaded sucessfully"
}
p/s and where did you call functionDownloadFile , show it, plz!

Related

How to ensure the order of list shown in UITableView when getting data for cell from UIDocument

My app fetches data via FileManager for each cell in UITableView. The cells need data from a file which requires open UIDocument objects. However, it seems like code inside open completion handler get executed non predictably, so the cells don't get displayed in the order I wish.
How do I solve this problem? I appreciate if anyone gives any clue.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
fetchCellsData()
}
func fetchCellsData() {
do {
let files = getUrlsOfFiles() //files in a certain order
for file in files {
let document = MyDocument(fileURL: file) //MyDocument subclassing UIDocument
//open to get data for cell
document.open { (success) in
if !success {
print("File not open at %#", file)
return
}
let data = populateCellData(document.fileInfo)
self.cellsData.append(data)
self.tableView.reloadData()
/// tried below also
// DispatchQueue.main.async {
// self.cellsData.append(data)
// self.tableView.reloadData()
// }
}
document.close(completionHandler: nil)
}
} catch {
print(error)
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! MyCell
let fileInfo = cellsData[indexPath.row].fileInfo
//assign fileInfo into MyCell property
return cell
}
I expect cells get rendered in the order of 'files', however, it seems like the order is a bit unpredictable presuming that it's due to the fact that 'cellForRowAt' gets called before it knows about the full set of 'cellsData'.
From Apple documentation on UIdocument . Apple doc:
open(completionHandler:)
Opens a document asynchronously.
Which means that even if you trigger document.open in the right order, nothing guarantees that the completionHandler sequence will be in the same order, this is why the order is unpredictible.
However, you know that they will eventually all get done.
What you could do is :
1 - place all your datas from opened document into another list
2 - order this list in accordance to your need
3 - place this list into cellsData (which I assume is bound to your tableViesDatasource)
var allDatas: [/*your data type*/] = []
...
do {
// reset allDatas
allDatas = []
let files = getUrlsOfFiles()
for file in files {
let document = MyDocument(fileURL: file)
document.open { (success) in
if !success {
print("File not open at %#", file)
return
}
let data = populateCellData(document.fileInfo)
self.allDatas.append(data) // "buffer" list
self.tableView.reloadData()
}
document.close(completionHandler: nil)
}
} catch { ...
Then you change cellsData to a computed property like so :
var cellsData: [/*your data type*/] {
get {
return allDatas.order( /* order in accordance to your needs */ )
}
}
this way, each time a new data is added, the list is orderer before being redisplayed.
Discussion
this is the easier solution regarding the state of your code, however this may not be the overall prefered solution. For instance in your code, you reload your tableview each time you add a new value, knowing that there will be more data added after, which is not optimised.
I suggest you to read on Dispatch Group, this is a way to wait until all asynchronous operation your triggered are finished before executing certain actions (such as reloading your tableview in this case) (Readings: Raywenderlich tuts)

Kingfisher image render recycling cells

I'm using Kingfisher to download and cache my images. I'm using the custom class below CustomImageView that extends ImageView. I also am using my custom UITableViewCell to call the loadImage method within the didSet property in my CustomCell class file.
The below method loadImage is where all the magic happens.
class CustomImageView : UIImageView {
var lastUrlLoaded: String?
func loadImage(url:String) {
lastUrlLoaded = url
ImageCache.default.retrieveImage(forKey: url, options: nil) { (image, cacheType) in
if let image = image {
self.image = image
return
}
else {
guard let url = URL(string: url) else { return }
self.kf.setImage(with: url, placeholder: nil, options: nil, progressBlock: nil) {
(image, error, cacheTye, _) in
if let err = error {
self.kf.indicatorType = .none
DispatchQueue.main.async {
self.image = #imageLiteral(resourceName: "no_image_available")
}
print ("Error loading Image:", err)
return
}
if url.absoluteString != self.lastUrlLoaded { return }
if let image = image {
ImageCache.default.store(image, forKey: url.absoluteString)
DispatchQueue.main.async {
self.image = image
}
}
}
}
}
}
// Custom TableViewCell -- NewsCell.file
class NewsCell: UITableViewCell {
var article: Article? {
didSet{
guard let image_url = article?.image_url else { return }
_image.kf.indicatorType = .activity
_image.loadImage(url: image_url, type: "news")
}
}
}
// METHOD in my NewsController
override func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! NewsCell
cell.article = articles[indexPath.item]
return cell
}
I'm having an issue when scrolling fast through my cells, some images that have loaded are being used for the wrong cells. I've researched and read other post but no answers helped me. I understanding it's dealing with the background task but just not sure how to fix it. If someone can help or point me in the right direction it would be much appreciated.
Solutions I've seen on stackoverflow and tried:
prepareForReuse in custom cell setting the image to nil.
setting the image to nil at cellforrowatindexpath.
override func prepareForReuse() {
super.prepareForReuse()
imageView.kf.cancelDownloadTask() // first, cancel currenct download task
imageView.kf.setImage(with: URL(string: "")) // second, prevent kingfisher from setting previous image
imageView.image = nil
}
Problem
When you scroll fast, you are reusing cells. The reused cells contain KF images which may have a running download task (initiated from the call to kf.setImage), for the image in the recycled article. If that task is allowed to finish, it will update your new cell with the old image. I will suggest two ways to fix it.
Answer 1 - Kill Recycled Download Tasks
Refer to the Kingfisher cheat sheet, Canceling a downloading task.
The cheat sheet shows how to stop those tasks in didEndDisplaying of your table view. I quote it here:
// In table view delegate
func tableView(_ tableView: UITableView, didEndDisplaying cell:
UITableViewCell, forRowAt indexPath: IndexPath) {
//...
cell.imageView.kf.cancelDownloadTask()
}
You could do it there or in the didSet observer in your cell class. You get the idea.
Answer 2 - Don't kill them, just ignore the result
The other approach would be to allow the download task to finish but only update the cell image if the downloaded URL matches the currently desired URL. Perhaps you were trying to do something like that with your lastUrlLoaded instance variable.
You have this in the completion handler of kf.setImage:
if url.absoluteString != self.lastUrlLoaded { return }
This is kind of what I mean, except you are not setting that variable properly to make it work. Let's imagine it was called "desiredURL" instead, for sake of explanation. Then you would set it in the didSet observer:
_image.desiredURL = image_url
And test it in the completion handler of kf.setImage:
// phew, finished downloading, is this still the image that I want?
if url != self.desiredURL { return }
Notes
If it were me, I would definitely go with the second approach (it has the positive side effect of caching the downloaded image, but also I don't trust canceling tasks).
Also, it seems that you are trying to do the hard way what kf.setImage does for you, i.e. loading the cache etc. I'm sure that you have your reasons for that, and either way you would still need to deal with this issue.
in your model check if the url not exist set it as nil, then in cell check your url if it is exist set it's image, else set a placeholder for that.
if article?.image_url != nil{
_image.kf.indicatorType = .activity
_image.loadImage(url: image_url, type: "news")
}else{
_image = UIImage(named: "placeholder")
}

image and label in interface builder overlap my data in the TableView cell

I am a beginner in iOS development, and I want to make an instagram clone app, and I have a problem when making the news feed of the instagram clone app.
So I am using Firebase to store the image and the database. after posting the image (uploading the data to Firebase), I want to populate the table view using the uploaded data from my firebase.
But when I run the app, the dummy image and label from my storyboard overlaps the downloaded data that I put in the table view. the data that I download will eventually show after I scroll down.
Here is the gif when I run the app:
http://g.recordit.co/iGIybD9Pur.gif
There are 3 users that show in the .gif
username (the dummy from the storyboard)
JokowiRI
MegawatiRI
After asynchronously downloading the image from Firebase (after the loading indicator is dismissed), I expect MegawatiRI will show on the top of the table, but the dummy will show up first, but after I scroll down and back to the top, MegawatiRI will eventually shows up.
I believe that MegawatiRI is successfully downloaded, but I don't know why the dummy image seems overlaping the actual data. I don't want the dummy to show when my app running.
Here is the screenshot of the prototype cell:
And here is the simplified codes of the table view controller:
class NewsFeedTableViewController: UITableViewController {
var currentUser : User!
var media = [Media]()
override func viewDidLoad() {
super.viewDidLoad()
tabBarController?.delegate = self
// to set the dynamic height of table view
tableView.estimatedRowHeight = StoryBoard.mediaCellDefaultHeight
tableView.rowHeight = UITableViewAutomaticDimension
// to erase the separator in the table view
tableView.separatorColor = UIColor.clear
}
override func viewWillAppear(_ animated: Bool) {
// check wheter the user has already logged in or not
Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
RealTimeDatabaseReference.users(uid: user.uid).reference().observeSingleEvent(of: .value, with: { (snapshot) in
if let userDict = snapshot.value as? [String:Any] {
self.currentUser = User(dictionary: userDict)
}
})
} else {
// user not logged in
self.performSegue(withIdentifier: StoryBoard.showWelcomeScreen, sender: nil)
}
}
tableView.reloadData()
fetchMedia()
}
func fetchMedia() {
SVProgressHUD.show()
Media.observeNewMedia { (mediaData) in
if !self.media.contains(mediaData) {
self.media.insert(mediaData, at: 0)
self.tableView.reloadData()
SVProgressHUD.dismiss()
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: StoryBoard.mediaCell, for: indexPath) as! MediaTableViewCell
cell.currentUser = currentUser
cell.media = media[indexPath.section]
// to remove table view highlight style
cell.selectionStyle = .none
return cell
}
}
And here is the simplified code of the table view cell:
class MediaTableViewCell: UITableViewCell {
var currentUser: User!
var media: Media! {
didSet {
if currentUser != nil {
updateUI()
}
}
}
var cache = SAMCache.shared()
func updateUI () {
// check, if the image has already been downloaded and cached then just used the image, otherwise download from firebase storage
self.mediaImageView.image = nil
let cacheKey = "\(self.media.mediaUID))-postImage"
if let image = cache?.object(forKey: cacheKey) as? UIImage {
mediaImageView.image = image
} else {
media.downloadMediaImage { [weak self] (image, error) in
if error != nil {
print(error!)
}
if let image = image {
self?.mediaImageView.image = image
self?.cache?.setObject(image, forKey: cacheKey)
}
}
}
So what makes the dummy image overlaps my downloaded data?
Answer
The dummy images appear because your table view controller starts rendering cells before your current user is properly set on the tableViewController.
Thus, on the first call to cellForRowAtIndexPath, you probably have a nil currentUser in your controller, which gets passed to the cell. Hence the didSet property observer in your cell class does not call updateUI():
didSet {
if currentUser != nil {
updateUI()
}
}
Later, you reload the data and the current user has now been set, so things start to work as expected.
This line from your updateUI() should hide your dummy image. However, updateUI is not always being called as explained above:
self.mediaImageView.image = nil
I don't really see a reason why updateUI needs the current user to be not nil. So you could just eliminate the nil test in your didSet observer, and always call updateUI:
var media: Media! {
didSet {
updateUI()
}
Alternatively, you could rearrange your table view controller to actually wait for the current user to be set before loading the data source. The login-related code in your viewWillAppear has nested completion handers to set the current user. Those are likely executed asynchronously .. so you either have to wait for them to finish or deal with current user being nil.
Auth.auth etc {
// completes asynchronously, setting currentUser
}
// Unless you do something to wait, the rest starts IMMEDIATELY
// currentUser is not set yet
tableView.reloadData()
fetchMedia()
Other Notes
(1) I think it would be good form to reload the cell (using reloadRows) when the image downloads and has been inserted into your shared cache. You can refer to the answers in this question to see how an asynch task initiated from a cell can contact the tableViewController using NotificationCenter or delegation.
(2) I suspect that your image download tasks currently are running in the main thread, which is probably not what you intended. When you fix that, you will need to switch back to the main thread to either update the image (as you are doing now) or reload the row (as I recommend above).
Update your UI in main thread.
if let image = image {
DispatchQueue.main.async {
self?.mediaImageView.image = image
}
self?.cache?.setObject(image, forKey: cacheKey)
}

How do I async load a text file from URL, then async load images from URLs in the text file? (in swift)

I'm new to iOS development, so any help would be appreciated.
The project: I am developing an Emoji-style keyboard for iOS. When the keyboard first loads it downloads a .txt file from the Internet with image URLs (this way I can add/delete/reorder the images simply by updating that .txt file). The image URLs are entered into an Array, then the array of image URLs is used to populate a collection view.
The code I have written works, but when I switch to my keyboard the system waits until everything is loaded (typically 2-3 seconds) before the keyboard pops up. What I want is for the keyboard to popup instantly without images, then load the images as they become available - this way the user can see the keyboard working.
How can I get the keyboard to load instantly, then work on downloading the text file followed by the images? I am finding a lot of information on asynchronous downloading for images, but my problem is a little more complex with the addition of the text download and I can't quite figure it out.
more info:
I'm using SDWebImage to load the images so they are caching nicely and should be loading asynchronously, but the way I have it wrapped up the keyboard is waiting until everything is downloaded to display.
I tried wrapping the "do { } catch { }" portion of the code below inside - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { //do-catch code }) - when I do this the keyboard pops up instantly like I want, but instead of 2-3 seconds to load the images, it takes 8-10 seconds and it still waits for all images before showing any images.
Below is my code. I simplified the code by removing buttons and other code that is working. There are two .xib files associated with this code: KeyboardView and ImageCollectionViewCell
import UIKit
import MobileCoreServices
import SDWebImage
class ImageCollectionViewCell:UICollectionViewCell {
#IBOutlet var imgView:UIImageView!
}
class KeyboardViewController: UIInputViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet var collectionView: UICollectionView!
let blueMojiUrl = NSURL(string: "http://example.com/list/BlueMojiList.txt")
var blueMojiArray = NSMutableArray()
func isOpenAccessGranted() -> Bool {
// Function to test if Open Access is granted
return UIPasteboard.generalPasteboard().isKindOfClass(UIPasteboard)
}
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName: "KeyboardView", bundle: nil)
let objects = nib.instantiateWithOwner(self, options: nil)
self.view = objects[0] as! UIView;
self.collectionView.registerNib(UINib(nibName: "ImageCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "ImageCollectionViewCell")
if (isOpenAccessGranted()) == false {
// Open Access is NOT granted - display error instructions
} else {
// Open Access IS granted
do {
self.blueMojiArray = NSMutableArray(array: try NSString(contentsOfURL: self.blueMojiUrl!, usedEncoding: nil).componentsSeparatedByString("\n"));
self.collectionView.reloadData()
} catch {
// Error connecting to server - display error instructions
}
}
} // End ViewDidLoad
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.blueMojiArray.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let iCell = collectionView.dequeueReusableCellWithReuseIdentifier("ImageCollectionViewCell", forIndexPath: indexPath) as! ImageCollectionViewCell
let url = NSURL(string: self.blueMojiArray.objectAtIndex(indexPath.row) as! String)
iCell.imgView.sd_setImageWithURL(url, placeholderImage: UIImage(named: "loading"))
iCell.layer.cornerRadius = 10.0
return iCell
}
}
UPDATE:
Thanks for the answers. I was able to fix my problem with Alamofire. I installed the Alamofire pod to my project. The specific code I used to fix my issue: first I added import Alamofire at the top, then I removed the do { } catch { } code above and replaced it with
Alamofire.request(.GET, blueMojiUrl!)
.validate()
.responseString { response in
switch response.result {
case .Success:
let contents = try? NSString(contentsOfURL: self.blueMojiUrl!, usedEncoding: nil)
self.blueMojiArray = NSMutableArray(array: contents!.componentsSeparatedByString("\n"))
self.collectionView.reloadData()
case .Failure:
// Error connecting to server - display error instructions
}
}
Now the keyboard loads instantly, followed by the images loading within 2-3 seconds.
I would do something like that:
On cellForItemAtIndexPath call method to download image for current cell with completion handler. On download completion set cell's image to downloaded image and call method reloadItemsAtIndexPaths for this cell.
Example code:
func downloadPlaceholderImage(url: String, completionHandler: (image: UIImage) -> ()) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
/// download logic
completionHandler(image: downloadedImage)
})
}
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
///
downloadPlaceHolderImage("url", completionHandler: { image in
cell.image = image
self.collectionView.reloadItemsAtIndexPaths([indexPath])
})
///
}
The recommend way to do this after iOS 8 is with NSURLSession.dataTaskWithURL
See:
sendAsynchronousRequest was deprecated in iOS 9, How to alter code to fix

Pull to Refresh: data refresh is delayed

I've got Pull to Refresh working great, except when the table reloads there is a split second delay before the data in the table reloads.
Do I just have some small thing out of place? Any ideas?
viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
self.refreshControl?.addTarget(self, action: "handleRefresh:", forControlEvents: UIControlEvents.ValueChanged)
self.getCloudKit()
}
handleRefresh for Pull to Refresh:
func handleRefresh(refreshControl: UIRefreshControl) {
self.objects.removeAll()
self.getCloudKit()
dispatch_async(dispatch_get_main_queue(), { () -> Void in
refreshControl.endRefreshing()
})
}
Need the data in two places, so created a function for it getCloudKit:
func getCloudKit() {
publicData.performQuery(query, inZoneWithID: nil) { results, error in
if error == nil { // There is no error
for play in results! {
let newPlay = Play()
newPlay.color = play["Color"] as! String
self.objects.append(newPlay)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
} else {
print(error)
}
}
}
tableView:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath)
let object = objects[indexPath.row]
if let label = cell.textLabel{
label.text = object.matchup
}
return cell
}
This is how you should do this:
In your handleRefresh function, add a bool to track the refresh operation in process - say isLoading.
In your getCloudKit function just before reloading the table view call endRefreshing function if isLoading was true.
Reset isLoading to false.
Importantly - Do not remove your model data before refresh operation is even instantiated. What if there is error in fetching the data? Delete it only after you get response back in getCloudKit function.
Also, as a side note, if I would you, I would implement a timestamp based approach where I would pass my last service data timestamp (time at which last update was taken from server) to server and server side would return me complete data only there were changes post that timestamp else I would expect them to tell me no change. In such a case I would simple call endRefreshing function and would not reload data on table. Trust me - this saves a lot and gives a good end user experience as most of time there is no change in data!

Resources