UICollectionView with variable cell sizes - ios

I'm struggling a little bit with the size for cells in UICollectionView.
In android, you can easily "wrap" the size of the cell.
Just like in iOS, you have a function call 'GetCell' and you decide how big it will be.
The difference in iOS is that in the "getCell" function (of UICollectionViewController) it seems you can't choose the size of the cell (or the contentview). If I change the size, it will ignore it and use anyway the general 'ItemSize' of the CollectionView (which is the same for all cells).
This sometimes results in Views which are not very beautiful. For example, if I have a horizontal list with images, I want the distance between images to be the same, independent if one image is 200x200 and the other 400x200. So the cell size should be different also.
It is possible to define a different size for different cells. You can use the Collectionview delegate and the GetSizeForItem (= sizeForItemAtIndexPath in ObjC) function. The problem is, this function is called BEFORE the actual GetCell function.
So if I have a more complex Cell, with for example some labels. In my "GetCell" function, I build this Cell and at the end, when returning the Cell, I know which size it should be. However, in the GetSizeForItem function, that info is not available yet, because the Cell is still 'null'.
The only way I could do it, is to actually build the UIView for the cell (so I can request the size) at the moment of the 'GetSizeForItem' call. But this doesn't seem a good design, because I'm building the UIView before the 'GetCell' where I will build it again.
Is there something I'm overlooking?
Regards,
Matt

Indeed GetSizeForItem gets called separately from GetCell. It's done that way because creating UIViews is a very time and memory consuming task, and your application would either run out of memory or have to dispose other views to be able to handle big lists.
Before the view gets presented, the UICollectionView (and UITableView) asks for the sizes and positions of all (or most) elements in the list, so it can know where to draw them. Many of those elements won't be visible though, so the collectionView avoids having to create them. This is why the GetSizeForItem gets called upfront, and the GetCell only later.
In your case, try to separate the logic that calculates the size of the view from the view itself. Make it a simple math formula that doesn't require a view to exist, so it's fast enough to be run upfront.

Related

Is subclassing UIView the right way to go about an unknown number of these custom objects?

I have a screen in my app that pulls an unknown sized array from my user object, and must display an object for each item in the array that consists of a UIImageView for my background image, a UISwitch to activate/deactivate that particular item, a label with the name of the item, and a label that corresponds to the name of the item.
These are displayed in a UIScrollView, since there are nearly always going to be more items than will fit on the screen, especially on the iPhone 4.
I had initially quickly thrown together a static screen that consisted of 20 of these objects, but have found that I will often be exceeding that number of items in the array, and need to convert to a dynamic solution.
I have not used subclassing at all yet, and want to be sure that this is the proper solution before I dive into the implementation. Adding one subclass to my scrollView sounds a lot cleaner than adding each of those components individually and figuring out the spacing for various screens.
If this is the proper solution, are there any resources you could point me to to learn about how to properly do this subclassing? I.e., how to add a constructor for the objects where I can add the contents of the labels and state of the switch, and programmatically add constraints. I've only used constraints from the storyboard.
If you do not think that subclassing UIView would be the proper way to do this, what other solution would you suggest? I'm admittedly a novice when it comes to iOS development, so I apologize that this post seeks advice on where to start rather than help fixing a specific part of an implementation.
You should use UITableView or UICollectionView for that, and subclass UITableViewCell or UICollectionViewCell to create your custom view hierarchy.
UITableView and UICollectionView will handle reusing of cells which will help in memory management.
Update:
as you want to use UITableView so to add spacing between cells set number of sections to number of elements and then add header for section and set its height you can do that by implementing UITableViewDelegates visit below links for detail about UITableView and UITableViewDelegates
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableViewDelegate_Protocol/

Resizing UITableViewCell to fit Content Performance

I am trying to create a table to show posts by users with possibly variable lengths. I want each cell in my table to resize to fit the content of the UILabel containing the text of the post. Currently I am doing this with auto-layout, programmatically setting constraints and calculating the height of the table view cell based on the height of the UILabel. Everything looks as I want it to, except when rapidly scrolling through the table the performance is horrendous, with the CPU getting fully maxed out and unable to keep up with setting the constraints for each cell as they are reused and placed with new text.
I was wondering if anyone knows of a better way to do this. Is there a way I can continue to use auto layout to size my cells without sacrificing performance? Or would the best solution be to create different sized cells with different identifiers, and just choose the best fit based on the UILabel text size.
I also read somewhere that UICollectionView has much better performance in displaying variable-sized content, would it be worthwhile/possible to try configuring a UICollectionView to display the messages, and somehow make it look like a UITableView?
Essentially I just need a suggestion on how to display messages of variable sizes (up to about 7 or 8 lines of text max) in a TableView-like manner without causing massive slowdowns as the user rapidly scrolls through the table.
Thank you
The best trick in the book for things like this is cacheing. Add to your model anything that's expensive to compute (not the views themselves, but the computational results, like view bounds sizes). Make your datasource methods all about looking up and assigning, not at all about computing.
The way I had implemented adding the constraints, the constraints were updated whenever [self updateViewConstraints] was called, regardless of whether or not the cell had already had its constraints calculated and applied. I resolved this by adding a BOOL property to my custom TableViewCell called didUpdateConstraints, and setting it to YES as soon as constraints are updated the first time. Then in my [self updateViewConstraints] method I only update the constraints if !self.didUpdateConstraints
Now that constraints are not being needlessly updated when all views have already been correctly constrained, the performance is significantly better, and I don't observe any slowdown upon scrolling through the table.

iOS >> UITableViewCell ImageView Property >> ContentMode doesn't have any effect

In many cases using one of the 4 UITableViewCell 'of the shelf' styles is enough for what I need, except for one 'little' annoying thing: The cell.imageView.contentMode property is always set to Scale to Fill. If images have different ratios, it causes the cells labels to be pushed with different x per each Row. It looks ugly...
If I try to fix it in code, let's say in the cellForRowAtIndexPath method, by addressing the property and assigning it to Aspect Fit, I get no warning or error, but it simply does nothing - i.e. it remains with Scale to Fill.
That means, that if I have an app where I'm not responsible for all the images presented in the table view and therefore cannot cut them in the same size - for example, if I wish to allow the user to select images from library or download etc... - I have to use a Custom Cell, even if all it has to have is an Image and a Label.
Does anyone know how to solve this using the default styles, without subclassing UITableViewCell?
In this SO thread you have a couple of solutions for that.
However, you will find that those solutions are just another way to create a custom cell. Or you need to create an ImageView at runtime or you just create a custom cell. As far as I know it is the right way to do it. I know that is annoying but the time you take to do a custom cell is way shorter than find hacky ways to go around that issue. Don't fight the framework :).

How can you create a zoomable timeline in iOS?

I would like to create a zoomable timeline in my iOS application for a kind of a todo-list. Zooming in would display days and hours and zooming out would trigger the folding out of days or zooming out to months. There would be a scrolling function.
As an example, I would want it to work like this: http://almende.github.com/chap-links-library/js/timeline/doc/
What kind of basic view would be an appropriate starting point for this, keeping in mind that the memory needed should be as low as possible? Would a UITableView, UIScrollView, or something else work for this?
The UICollectionView / UITableView will not work because the cells are almost always the same width/height. Most importantly the cells always have the same spacing in-between each cell. Because of this it is able to easily calculate what the index range is, and query the dataSource for the cell's it needs based on index.
A timeline view on the other hand is much different than these controls. The spacing between cells is different, with the cells sometimes overlapping with each other. If you had a data source sorted by position, the control would still have to guess where to start looking for the range. So finding the correct index range is going to be more expensive - you just have to find the right algoritm to determine this in a shorter amount of time.
You're going to have to build your own control by subclassing a UIScrollView. You shouldn't have to mess with drawRect at all. An important concept, which is used by UITableView and UICollectionView, is dequeue'ing cells. The iOS 5 version of Apple's PhotoScroller demonstrates this concept with paging (the iOS 6 version replaces the custom paging with UIPageViewController). You'll have to download the old documentation to get the old sample code.
I'm currently building a timeline view, which I will open source at some point. It's somewhat based on the UITableView and works in horizontal or vertical direction. It dequeue's cells just like the UITableView. I'm not focusing on labels or scaling, but the concept of having inconsistent spacing in-between cells. To give you a head start, here are my dataSource methods I settled on:
- (NSInteger)numberOfCellsInTimelineView:(TimelineView *)timelineView;
- (CGRect)timelineView:(TimelineView *)timelineView cellFrameForIndex:(NSInteger)index;
- (TimelineViewCell *)timelineView:(TimelineView *)timelineView cellForIndex:(NSInteger)index;
Two of these calls are identical to what UITableView has, but it has a new call called cellFrameForIndex. What's significant about this call is that the TimelineView can guess an index and lookup the frame of the cell and see where it fits in the visible bounds. If it guesses a cell inside the visible bounds, it can simply iterate forward and backward until it finds the edges.
Currently the algoritm I'm using takes round(count * (CGRectGetMidX(timelineView.bounds) / timelineView.contentSize.width)) (for the horizontal direction). Basically what this does is takes the mid-point of the visible bounds of the UIScrollView and gets the percentage of what has been scrolled. Then it multiplies that by the number of cells. This works fairly well. When testing a random data-set with 100,000 records at random spacing the calls to cellFrameForIndex ranged from 8 to 150. I'm able to pull 52-60 FPS with this. I'm still working on it, and trying to find a better/quicker way to determine the index range. I'm trying to keep it down to visible cell count + 10 (max) iterations.
If you have time to wait, I'll update my answer to include a link to my GitHub project when I'm done. This could be a few days, to a week. I may add scaling. But you'll have to fork it and add labels and anything else you want.
Edit:
Here is my Github project: https://github.com/lukescott/TimelineView
UITableView is definitely not suitable. UIScrollView might be better but it is not very well suited for dynamic or very long content.
I believe the easist approach would be to do it everything by yourself - implement it by a UIView subclass with a drawRect. Of course, you should use UIPanRecognizer in the same manner as UIScrollView uses it.

Set UITableViewCell height *without* using heightForRowAtIndexPath

I'm trying to set the height of a UITableViewCell based on some dynamic content. I know you can "set" the height via the heightForRowAtIndexPath delegate method in your view controller, but... I can't do that!
The problem is that the height of the cell isn't known until cellForRowAtIndexPath is called, and heightForRowAtIndexPath is called before cellForRowAtIndexPath.
So, I need to somehow either reverse the order in which these two methods are called, or find another way to set the cell height.
Any ideas?
Sorry - there's no built-in way to reverse those methods or anything similar. One of the reasons is that a table view can want to know the height of a row for a number of reasons, not all of which include displaying a cell - -tableView:heightForRowAtIndexPath: might get called several times, or -tableView:cellForRowAtIndexPath: might not get called at all following a call to the height method.
What you can do is find another way to compute the appropriate height ahead of time, cache it, and rely on that cached value for -tableView:heightForRowAtIndexPath:. I've managed to do something similar by constructing a "dummy" UITableViewCell instance, keeping it out of the normal reuse queue, and just using it for layout and height-determination purposes. Such a solution would go something like this:
Get -tableView:heightForRowAtIndexPath:.
Look up the index path in your height cache. If you have a value, return it.
Otherwise, use your dummy cell to lay out the content that would appear at that index path. Measure the cell's height and cache it.
Potentially repeat steps 1-3 several times, depending on how much -tableView:heightForRowAtIndexPath: is called.
Get -tableView:cellForRowAtIndexPath:.
Dequeue a cell from the normal reuse queue, populate it, lay it out, and return it. (You may choose to verify that its height matches your precomputed height, so that the table view behaves how you'd expect.)
Depending on the content of your cell and the complexity of the phrase "lay it out" in your use case, this may incur a reasonable performance hit as you calculate the first several heights, or as your users scroll your table view rapidly. However, as your cache warms up, you should be computing fewer and fewer heights as your app continues to run.
One last point: remember that for cells with default heights (which may or may not appear in your table), you can early-out by returning tableView.rowHeight, rather than computing the default height every time it appears. This can ease the computation burden of the above approach somewhat.

Resources