1

Xcode 6 - beta 6/7: I've created a iOS 8 (Swift) blog reader app with an overview (table view) and a detail view (web view) screen. If there's internet connection the app, when starting up, loads the actual json data from the blog and saves it as entity objects into core data. The table view that is shown first then fills its cells with those objects from core data. The cells, containing title and subtitle, are fully loaded like expected. Everything seems to work fine but...

  1. If I don't interact and just wait, the table view quickly refreshes after around 10 seconds (cells get blank and are filled with the same text again within half a second). After that everything works just fine, I can click the named cells and being forwarded to detail view, I can go back and chose any other.

  2. If I start interacting immediately, like one normally does, I have to click a cell twice before being forwarded to the detail view. When going back to the overview within little time I find my table view filled just with the one cell I just tapped before, the rest are blanks.

Case where I go back within no time and then go for one of the blank cells

  • Either I can then wait for another 5-15 seconds before everything refreshes automatically (and from that point on work fine) or
  • I can tap those blank cells which get then visible again immediately, whereas - of course - I'm being forwarded again to the detail view or
  • If I had stood for longer on the detail view, let's say reading some of the blog, and then go back, all cells are visible as there was enough time for this strange refresh taking place in the meanwhile

  • As a note: If there is no internet connection the app successfully loads the objects from core data but the problem mentioned remains. So, anyway, I have to wait till this "second refresh" took place before the app runs like I want it too.

If it's a completely unknown problem and you have to see some of the code for further investigation, I'm happy to share it. But right now I haven't seen a point as it's more or less the basic code implemented when using table views. I'm fairly new to core data and may have overseen something but not noticed so far. I'd be very thankful for some help as I usually investigate quite long time before asking but this time really have no clue what the reason could be.

EDIT:

It may be important to add that I started the app with the "Master-Detail Application" template in Xcode and make us of the methods it provides to fill the cells automatically with the proper entity objects from core data (NSFetchedResultsController). Some part of the code that could give a hint where to investigate further (please tell me, if more code is needed):

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
    self.configureCell(cell, atIndexPath: indexPath)
    return cell 
}    

func configureCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath) {
    let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as NSManagedObject
    cell.textLabel?.text = object.valueForKey("title")?.description
    let published = object.valueForKey("published")?.description
    let author = object.valueForKey("author")?.description
    let spaceholder = "   "
    cell.detailTextLabel?.text = published! + spaceholder + author!
}

EDIT 2:

Thanks to the input nzeltzer I think I've found the point: The problem above occurs just when some parts of the code run on global (instead of main) thread. So I did some testing: The console output at the bottom shows the NSThread.isMainThread() results when the app builds for the first time.

When I rerun the app a second time and more, the process is the following: Core data already has some values so the cells are configured directly (Log: _ is Main Thread). Then, in case there is internet connection it is deleting (in the viewDidLoad) all the core data objects, loads the json data again, puts the objects to core data and creates the cells a second time. This second part of the Log is now again _ is NOT Main Thread. If I run the app now without internet connection, the second part doesn't get triggered and just the first cell configuration with _ is Main Thread takes place.

Any straight forward explanation for that behavior?

What would be the right (elegant) approach to dispatch those parts that might be responsible for the problem on main thread in any case? Does it have anything to do with the template's built-in NSFetchedResultsController or is it just normal that some tasks are dispatched to global queues automatically?

Log (app builds for the first time, 10 cells):

2014-09-08 13:04:54.821 Blog Reader[3682:193346] 17545849:_UIScreenEdgePanRecognizerEdgeSettings.edgeRegionSize=13.000000
2014-09-08 13:04:54.823 Blog Reader[3682:193346] 17545849:_UIScreenEdgePanRecognizerEdgeSettings.edgeRegionSize=13.000000
NumbersOfRowsInSection is Main Thread
NumbersOfRowsInSection is Main Thread

NumbersOfRowsInSection is NOT Main Thread
configureCell is NOT Main Thread
cellForRowAtIndexPath is NOT Main Thread
configureCell is NOT Main Thread
cellForRowAtIndexPath is NOT Main Thread
configureCell is NOT Main Thread
cellForRowAtIndexPath is NOT Main Thread
configureCell is NOT Main Thread
cellForRowAtIndexPath is NOT Main Thread
configureCell is NOT Main Thread
cellForRowAtIndexPath is NOT Main Thread
configureCell is NOT Main Thread
cellForRowAtIndexPath is NOT Main Thread
configureCell is NOT Main Thread
cellForRowAtIndexPath is NOT Main Thread
configureCell is NOT Main Thread
cellForRowAtIndexPath is NOT Main Thread
configureCell is NOT Main Thread
cellForRowAtIndexPath is NOT Main Thread
configureCell is NOT Main Thread
cellForRowAtIndexPath is NOT Main Thread
alexeis
  • 2,152
  • 4
  • 23
  • 30
  • Perhaps some code might help... What do you have in cellForRowAtIndexPath? – Mike Sep 06 '14 at 01:39
  • @Mike Of course, I added some. – alexeis Sep 06 '14 at 02:04
  • Have you verified that your UI updates are being dispatched on the main thread; and that your Core Data setup is either using thread confinement, or the appropriate alternatives? – nzeltzer Sep 06 '14 at 02:07
  • @nzeltzer Im not sure: Wouldn't I see it in the code if anything is dispatched to another thread? Or are there library methods that automatically run on another thread? I haven't heard about further core data "setup" in detail but is it not thread confined by default? – alexeis Sep 06 '14 at 20:16
  • In most circumstances, yes. I only ask because these sort of delayed UI refreshes are a common symptom of updates being pushed from some thread other than the main thread. – nzeltzer Sep 06 '14 at 20:53
  • Do you know how to analyze and proceed in such a case? Maybe that's the "answer" I'm looking for, just have no idea what to do/try next! – alexeis Sep 06 '14 at 23:12
  • It's really dependent on application design. You can use the NSThread isMainThread method as a crude check on some likely candidates: cellForRow, configureCell, etc. – nzeltzer Sep 06 '14 at 23:28
  • Oh, and Instruments! – nzeltzer Sep 06 '14 at 23:29
  • @nzeltzer Thanks for the good inputs! Check out my edit... What did you mean by instruments? Do I have to check further parts of my code? – alexeis Sep 08 '14 at 11:12
  • http://stackoverflow.com/questions/27355545/swift-reuse-cells-in-ios7-1-simulator-cells-are-hidden i have this problem, maybe you have an idea – Esqarrouth Dec 08 '14 at 14:39

2 Answers2

1

I had a similar issue.
You need to do any ui changes on the main thread. When you do a data request, the callback may not be on the main thread. So to do that do something like this:

dispatch_async(dispatch_get_main_queue()){    
    self.tableView.reloadData() // reload table/data or whatever here. However you want.
}

When you load data from a url, you are going to have some sort of callback which will be sent to a closure (a block in objc). When that closure is called it is not guaranteed to be on the main thread. Sometimes it might be and sometimes it will not be. Why running dispatch_async and telling it to grab the main thread with dispatch_get_main_queue() you are saying, run the following code on the main thread. All UI changes must be made from the main thread.

Johnston
  • 20,196
  • 18
  • 72
  • 121
  • I specified the problem further (check EDIT again). Is it what you were dealing with too? I'm trying now to figure out how to solve it with those new inputs... – alexeis Sep 08 '14 at 11:36
  • So without you doing anything the table data disappears? – Johnston Sep 08 '14 at 11:46
  • No, in those cases where some of these parts run on a global queue, the problem shown above occurs: The data in the table is loaded and shown at start up but then there is a second refresh where the text disappears-reappears. Only now the table runs without any issues. – alexeis Sep 08 '14 at 11:49
  • Exactly. So you are refreshing the data yourself? – Johnston Sep 08 '14 at 11:50
  • If you are refreshing the data yourself you are not refreshing it on the main thread. You need to do that. Like I suggested in the answer – Johnston Sep 08 '14 at 11:51
  • Yes through that code (new json -> new objects in core data) that runs in case of internet connection I guess. – alexeis Sep 08 '14 at 11:52
  • Ok, so all of this code is within a `task = session.dataTaskWithURL...` - I try to dispatch it right (never done before;) – alexeis Sep 08 '14 at 11:56
  • hm, it's not about that code there... like you said, it will be about the code who is responsible for the refresh, so actually the cellAtIndexPath, configureCell etc., right? – alexeis Sep 08 '14 at 12:08
  • Any changes to the UI must be made on the main thread. If you are in a callback of a URL request you are not guaranteed to be on the main thread. – Johnston Sep 08 '14 at 12:19
  • Some of it worked! Dispatching `configureCell` to the main queue let the content of the cells work fine from beginning! There's still a second visible refresh and the cell marks are not 100% correct before that happens, so maybe I have to dispatch the `cellForRowAtIndexPath` as well, right? Is dispatching those parts individually the only way or is there kind of a "head"-thing I could dispatch right and had the right influence on all the other parts? Or just write that dispatch once for my whole MasterViewController? ...I have no experience so far with dispatching. – alexeis Sep 08 '14 at 12:47
  • Where do I have to put the dispatch exactly? It was no problem to dispatch the closure `configureCell` as I just put the whole code as `dispatch_block_t` but how must it look like if I want to dispatch the `cellForRowAtIndexPath` function? I can't add the dispatch in front of the function within the class { }, as the error "Expected declaration" pops up. – alexeis Sep 08 '14 at 13:04
0

You can also reload UITableView like this

self.tblMainTable.performSelectorOnMainThread(Selector("reloadData"), withObject: nil, waitUntilDone: true)

As I mentioned here

Community
  • 1
  • 1
Anand Suthar
  • 3,678
  • 2
  • 30
  • 52