Consider a chat application scenario, where you have a very large group with 100,000+ messages and realtime communication.
Just like most chat applications, we want the latest messages to appear at the bottom i.e. new items are added at the bottom.
What's the best practice for using lazy loading with NSFetchedResultsController? Changing the fetch-request by increasing the fetchLimit doesn't seem like a good idea. Also, using an extra array instead of fetchedObject also doesn't seem like a very elegant or convenient solution either.
What's the best practice for showing latest messages at the bottom i.e. reversing the UITableView direction? Transform doesn't seem to be an elegant solution, or is it?
Looking for an elegant solution, that's working for people. Please advise.
fetchLimit doesn't work with NSFetchedResultsController. To limit the controller you can do the following:
Do a single fetch with a fetchLimit=1 and a fetchOffset=BATCH_SIZE (where the batch size is something large, but not huge = ~200)
get the date of that message that you fetched
limit the fetchedResultsController to the date of that message.
Now you have a fetchedResultsController that is 200 messages. Note that it may increase in size as long as it is open.
When a user scroll back you can do some similar adjusting of the fetchedResultsController by doing a fetch to figure out a correct date range.
For display the cells I have used the double-inverse method (apply a 180 rotation to the collectionView and a 180 rotation to every cell). It is not that elegant but it works and it is not as expensive as it first seems - the whole screen is already in an openGL layer behinds the scenes anyways. There are a lot of little things that it caused (like the scrollIndicator being on the wrong side), but also a lot of little things that it fixed (like dealing with a chat with very few messages). If I had to do it again I would make a custom layout, but I wouldn't be so quick to dismiss the double-inverse method.
Related
All messages are ordered by message time asc (the bottom is the newest). All the messages are shown with UITableView. It will show 50 messages default, and load 20 more messages when pull down. How to implement this with CoreData NSFetchedResultsController? Does NSFetchedResultsController support pull-down paging?
I think it is NOT a good idea to do like this:
Set NSFetchRequest.fetchLimit to 50
Call performFetch method of NSFetchedResultsController
When pull down, set NSFetchRequest.fetchLimit to 70, 90, etc.
Call performFetch method of NSFetchedResultsController again
Reload the whole table view.
Before brainstorming on technical aspect of implementation it is very beneficial to carefully work out UX and overall design of the app. Here are some things that you might need to address while developing chat app:
Can message bubbles overlap?
Do you need dynamically displayed stuff like timestamps, typing animation etc.?
How would you organize partial fetch of the data so network will be used carefully?
Do you need to animate any objects in chat? (message bubbles insert, deletion and update). I mean some advanced animation that use UIDynamics.
Features listed above can be a real problem to implement with UITableView. While with UICollectionView you have all the power of custom layouts and batch updates.
For ideas listed in this post should give a credit to one of Viber Messanger developers who raised this problems on one popular russian language resource.
Instead of setting fetchLimit you could set batchSize, and let NSFetchedResultsController handle how it loads objects into memory.
Otherwise you'll have to update your fetchRequest and performFetch again.
I have a UITableView displaying a table of data with a somewhat large contentInset value. As a result, the user cannot trigger the UIRefreshControl because it expects them to scroll too far.
Basically, I'm wondering if it's possible to make UIRefreshControl adjust for contentInset values.
Edit: This question is mostly about curiosity, so I removed the extra details and rephrased the question to be more direct.
I think you're going about this a bit wrong. If you've encountered this issue because of the mechanism you've created to preload cells, you should think of a different approach for preloading them. If there's any content to be rendered / loaded / downloaded, you could be doing this in a background thread and leave a minimal amount of work for cellForRowAtIndexPath. If you insist on keeping this implementation, I'd suggest looking up a custom refresh control as there is no way to customize the build in UIRefreshControl in terms of content offset for refresh (AKA sensitivity). I believe this is ultimately a good thing - there should be consistent user experience across apps running on iOS which use a refresh control.
For an app I am creating I am looking for the correct way to display a medium sized records set (around 130 records, with about 13 columns). The data is now in a sqlite database, which I want to use to display in a UITableView. With this quantity of records and columns I think an array is way too memory intensive?
From looking around (and previous experiences) I think I best go for Core Data and use an NSFetchedResultsController, but I am not sure about this. There is no need for the data to be changed or added. So using this might be overkill?
If this is the way to go though, I would love to know what components to use, as I cannot really find an answer to this sort of question.
If this is not the way to go, please point me in the right direction in which I should go.
Core Data is a good match. It can also be used with sqlite as a datastore however you would probably have to reimport your data. The NSFetchedResultsController makes it fairly easy to use a core data collection as a datasource for the table-view. The modeling tools in XCode make it also pretty convenient to create your data structure and creating the NSManagedObject subclasses from it. I would also take into consideration that you might want to extend your dataset or structure in the future and core data has battle tested facilities to assist you with these tasks.
There is a little trick, you needn't select all data from this table and transform into an array, just select data like this:
SELECT `recordid` FROM `yourtable`;
in the delegate method "tableView:cellForRowAtIndexPath:", you can now load all rest data from this record:
SELECT * FROM `yourtable` WHERE `recordid` = [indexPath row];
If your database records is still growing, you can fetch your data by paging query:
SELECT * FROM `yourtable` LIMIT 10 OFFSET 0;
The answer depends on what you need. In particular, if you have to release the application in a day or so, well, refactoring to Core Data would be an obstacle. It has a very steep learning curve so it cannot be digested in a day. On the contrary if you have more time to spend, yes go with Core Data and reuse the know-how in the future.
So, stick with two options here. Stay with plain SQL or just migrate to Core Data. Well, if you have chosen the second, the master component you need is the NSFetchedResultsController.
This class works in combination with UITableView offering a lot of functionalities. One of the most important is to maintain a small memory footprint. In fact, if you work with batch sizes you can take advantage of lazy loading mechanism. Say for example, you want a batch of 20 items. The NSFetchedResultsController will load the first 20 items, if you scroll down, it will load other 20 and so on.
The main problem will be not how to organize your internal data structures, but how effectively present 13 columns of to the user, taking into account very little screen real estate you have on iPhone. Have you thought about that?
There are few options here
you can present just some kind of brief information into main table view and for each cell have detail view with the rest of the information
you can dynamically generate bunch of labels inside each cell and populate them with your data, but now you're really facing space limitation, since you probably won't fit that much of a data in one row. And you also need to create custom headers in your table view (if you need to show header)
or you can invent something else. When I had to deal with very similar issue I went and implemented something similar to Excel "freeze columns" functionality - where header columns is fixed and stays on the screen always and all data columns can be scrolled horizontally and basically go underneath the header. See screenshot attached (note scroll bars around columns with stat data):
I am using core data in my app to store entities that could have as many as 50k objects or more. I have this paired to an NSFetchedResultsController in a table view. The table view works fine due to cell reuse however my biggest problem is queuring the actual database to get the dataset.
When i first load the table view i need all results from the db. I am using the default fetch request with a single sort descriptor and I have set the batchSize to 1,000. On an iPad 2 this query takes up to 15 secs to finish! I also have to run this query after a search has been cancelled so overall it makes the app unusable. My assumption is that CD still has to resolve all those results or setup the sections or something, i really have no idea but just using the batchSize doesn't help?? The content is also very dynamic in the sense that new rows are always getting added, sort order changing etc.. so caching has a limited benefit.
I am thinking now that the best option would be to use a fetchLimit in the fetchRequest and then implement some basic paging. When the table view scrolls to the end fetch the next "page" of results? My only problem with this approach is that i lose the sectionIndex and i cant think of any way around that.
Anyone have any ideas or dealt with this issue already?
When you set the fetch request for the FRC the batch size should be just a few items bigger than, maybe twice the size as, the number of items that can be seen on screen at any one time. The FRC already does the pagination for you you just need to set the page size better.
s.newave,
Do your rows have variable height? If so, then the table view asks you to calculate each height and that causes every row to be fetched. 15 seconds is not an unreasonable time to fetch 50K items.
The bigger problem is your statement about not wanting to change your design. Frankly, a 50K item tableview is useless. You should change your design -- not because CD is slow, it isn't -- but because your design is not pragmatically usable.
Andrew
P.S. The fetched results controller is designed for mainstream applications. a 50K table view is not a mainstream app. If you insist on keeping with a 50K table view design, you will have to make your own controller.
I'm new to iOS development pondering how best to approach a fairly simple design problem. I want to display a set of items, each one of which has the structure as sketched. In a given set, not more than 10's of items.
Each item includes a thumbnail image, a heading, a blurb, and a set of buttons. There are two complications:
The amount of text and number of buttons is variable.
The text requires some internal formatting (italics and bold).
I've considered these approaches:
Use a table view, with custom, resizable UITableViewCell, probably using something like OHAttributedLabel for the text. For the variable number of buttons, either lay these out programmatically or possibly use the new collection view (for older iOS, have to use 3rd party grid view).
Use a table view with custom cell based on UIWebView.
Do the whole set as one UIWebView.
Use a table view with sections; each item having its own section and parsing out the buttons and text to rows.
Would love to get suggestions about how a more experienced iOS dev would approach this.
EDIT: I am now considering that the best way may be:
5) Use UICollectionView for the whole thing.
UPDATE: In the end, I laid the whole thing out in code as a custom table cell (ie., #1). This was a good choice, not only for the reasons given in the answer, but because as someone new to iOS development, it's something I needed to get under my belt. Didn't even use collection view for the buttons, because I was worried about performance and also the hassle of supporting iOS5.
I do think that using collection view for the whole design (#5) would have been an elegant solution, and I actually started down that path. However, some complications not shown in the simplified pic above made that unwieldy.
2nd UPDATE: #1 turned out to be a dead end. My final solution used a UIWebView (#3) - see answer.
I have found some resources that might be useful to some people who is doing complex tableviewcell and want fast scrolling. I am still developing it, but I want to share this first to you guys.
Facebook iOS release note: they mentioned techniques: core text, pre/asynchronous calculation of table height, do a lot of things on background thread, save layout attribute in core data. http://www.facebook.com/notes/facebook-engineering/under-the-hood-rebuilding-facebook-for-ios/10151036091753920
Fast scrolling sample: https://github.com/adamalex/fast-scrolling
Apple's sample project TableViewSuite. The 4th example.
https://developer.apple.com/library/ios/#samplecode/TableViewSuite/Introduction/Intro.html
Very close to solution..YES
I know this is an old thread, but I found it very interesting, as I am just now getting around enough as an iPhone developer to reach these types of performance concerns. I found a very interesting article on Facebook's site by Facebook Engineering describing how they implemented UITableView and overcame the dynamic sizing issues, also with rapid content management. It seems they precalculated using deeper objects and kept everything asynchronous and pre-cached where possible. I'm going to provide a link to the article, but I'm going to copy the section that tackles exactly this problem. I hope you find it useful. Here's the link, https://www.facebook.com/notes/facebook-engineering/under-the-hood-rebuilding-facebook-for-ios/10151036091753920, and the most relevant excerpt:
(Re-)Building for Speed
One of the biggest advantages we've gained from building on native iOS has been the ability to make the app fast. Now, when you scroll through your news feed on the new Facebook for iOS, you'll notice that it feels much faster than before. One way we have achieved this is by re-balancing where we perform certain tasks. For example, in iOS, the main thread drives the UI and handles touch events, so the more work we do on the main thread, the slower the app feels. Instead, we take care to perform computationally expensive tasks in the background. This means all our networking activity, JSON parsing, NSManagedObject creation, and saving to disk never touches the main thread.
To give another example, we use Core Text to lay out many of our strings, but layout calculations can quickly become a bottleneck. With our new iOS app, when we download new content, we asynchronously calculate the sizes for all these strings, cache our CTFramesetters (which can be expensive to create), and then use all these calculations later when we present the story into our UITableView.
Finally, when you start Facebook for iOS, you want to see your news feed, not a loading spinner. To provide the best experience possible, we now show previously-cached content immediately. But this introduces a new problem: If you have a lot of stories in your news feed, UITableView throws a small spanner in the works by calling the delegate method -tableView:heightForRowAtIndexPath: for each story in your news feed in order to work out how tall to make its scrollbar. This would result in the app loading all the story data from disk and calculating the entire story layout solely to return the height of the story, meaning startup would get progressively slower as you accumulate more stories.
The solution to this particular problem has two main parts. Firstly, when we do our initial asynchronous layout calculations, we also store the height of the story in Core Data. In doing so, we completely avoid layout calculation in -tableView:heightForRowAtIndexPath:. Secondly, we've split up our "story" model object. We only fetch the story heights (and a few other things) from disk on startup. Later, we fetch the rest of the story data, and any more layout calculations we have to do are all performed asynchronously.
All this and more leads to high frame rates while scrolling and an app that remains responsive.
I originally accepted (and implemented) #Daij-Djan's answer, but now I believe the best approach is #3 (UIWebView). It comes down to performance.
UITableView strains to perform well with custom cells with subviews, especially in the context of cells with varying heights. The rows of buttons make the scrolling choppy. As suggested by Apple in Cells and Table View Performance , I made sure that subviews were all opaque, however there is no way to follow the suggestion of "Avoid relayout of content."
Add onto that dynamic cell heights and attributed strings and these tables scroll pretty poorly. I suppose the next optimization would be to override drawRect, but at that point I decided to try UIWebView.
UIWebView is not without its performance issues either! Its scrolling performance degrades pretty fast as the content grows, however, I can manage that by hiding content and letting user "open" it as desired.
no 1 is maybe the most work directly followed by #2 BUT
as ACB said, it's also the most flexible and IMO will surely provide the best look'n'feel
no 3 works but will not feel as smooth / alway be tad 'html-ish'
no 4 sounds like highway to hell (later on. it will be a PITA to modify/maintain)