I am trying to back my UICollectionView's data source with a data structure that (for good reasons) is more than two layers deep. In other words, my data can be broken into sections, but not all of those sections simply contain items. Some contain arrays of items (or "nested sections", so to speak). These items can be easily be referenced with an index path three indices long (3.2.4, for instance), but UICollectionView's data source methods only seem to support index paths up to two indices long.
All of the UICollectionView APIs use NSIndexPaths to reference collection view items, so in theory they should work with any number of indices. The challenge is getting the collection view to "know" to ask for cells, layout attributes, etc. for items at these deeper indices when the data source protocol only provides collectionView:numberOfItemsInSection: and
numberOfSectionsInCollectionView: to specify the indices it needs to ask for.
Is there any way around this that doesn't require me to (a) subclass UICollectionView, which Apple explicitly discourages (second point under "Tips for Implementing Your Custom Layouts") or (b) flatten my data structure, which would be less than ideal, and which seems unnecessarily limited given the APIs UICollectionView already provides?
It doesn't make logical sense in the scheme of a standard collectionView (or tableView) to have a three tier index. I've seen some cool tableviews that have "expanding" cells that work in the same way you're describing. It's just that the object representing the expanding cell tells the table that it has multiple items available.
I can't understand how your collectionView is supposed to look as an end product. If you are encountering arrays at some index paths, what are you trying to represent? I don't want to inform your design but why not just show one item at that particular index, and when a user selects it, expand or show another view with those subsequent items?
Related
Question
What is the benefit of using either tableHeaderView, tableFooterView or UITableViewHeaderFooterView as opposed to just using multiple UITableViewCell, and customising it to look like a header or footer?
Example
A static banner image
A static part with input fields that all look the same:
First name
Last name
Age
Height
Weight
A static part "Appointments", which can be expanded
A dynamic list of doctor's appointments
A static part "Images", which can be expanded
A dynamic list of images
A static part with a "Save" button
My gut reaction was making the top two static parts (1, 2) the tableHeaderView, the other static parts (3, 4) a section header, the dynamic lists (under 3,4) to becells inside sections, and the Save button the tableFooterView.
But then the input fields (under 2) look similar, so they could also be cells.
But then I need to treat sections in a special way, because the first section does not have a header view.
And then, 2 months later, the requirements change and 5 other special parts are added.
So all in all, I'm wondering, why not just implement everything as a UITableViewCell, and have a CellType enum that can easily be extended?
Then you have a flat array cells: [CellType] which is the single source of truth for the table view, and a single method func buildCells() where you populate this array.
So far, this is the only article I've found that talks about this, and I'm kind of agreeing with it.
This approach (just tableview cells, no header/footer views) works fine. In your example, I would have made all of these cells, too.
What is the benefit of using either tableHeaderView, tableFooterView or UITableViewHeaderFooterView as opposed to just using multiple UITableViewCell, and customising it to look like a header or footer?
While a series of cells might make sense in your particular example, I wouldn’t generalize that conclusion and, therefore, categorically dismiss the idea of header/footer views. There are many cases where the header/footer view approach is most logical and intuitive:
in many cases, the section headers don’t have any model data associated with them, but are just for organizational purposes ... in this case (e.g. grouping a list of contacts by the first letter), the header view approach makes a lot of sense;
in other cases, we’re dealing with arrays of arrays of homogenous items ... again, the header view approach makes lot of sense; it has the benefit that the index path section and row now map very nicely to the model;
sometimes we want a header to persist as it scrolls out of view (so the user can see what section they are in even though the header would have otherwise have scrolled out of view);
sometimes we want a footer that shows some derived, calculated value (e.g. when listing items on an invoice, perhaps the “total” should be shown in a footer).
Your scenario feels more like a heterogenous list of model objects. In that case, I think using cells for this model data is perfectly reasonable. But obviously header and footer views still have utility and, in many cases are simpler and more intuitive.
I'm developing an Chat application where I have a UICollectionView to control the messages and I came to a situation I would like to confirm with you.
For exemple, let's say I have 60 items in this UICollectionView, but based on the size of the items and the scrolling options I set, only the last 10 items are visible on the screen, from 50 to 59.
Based on that, it seems I'm not able to get cellForItem at IndexPath 30, for example. Is that correct?
I would like to confirm that with you before creating a solution to go over the items that are already "on screen" and I need to check. Any ideas and solutions you have already implemented is appreciated.
Also, based on the information above if, for example, I need to move on item from index path 30 to 31, will I have problems if they are not "instantiated" in the screen?
Thanks in advance!
You seem to be mixing your model, controller, and view classes, which is a bad thing™ for exactly the reason you encounter here.
I take it you're trying to access data from the index 30 (basically) and say to yourself "Hey, I already added that in the 30th cell, so I will just use the collection view's method to get that cell and take it from there". That means, you basically ask a view for data.
That won't work, because, as others pointed out (but more indirectly), there are not 60 cells at all at any given moment. There's basically as many cells as fit on the screen, (plus perhaps one or a few "buffer" cells so rendering during scrolling works, I can't remember that atm). This is why cellForItem(at:) is nil for an IndexPath that refers to a cell not actually visible at the moment. Basically it works in a similar way to a table view. The collection view simply does not keep around stuff it doesn't need to render for memory reasons.
If you need anything from a cell (which is after all also a view) at this path, why don't you get it from whatever data object represents the contents of this cell? Usually that's the UICollectionViewDataSource.
That's how the paradigm is supposed to work: The UICollectionViewDataSource is responsible for keeping around any data your app may need at a given time (this may or may not reloading it or parts of it, your choice). The UICollectionView uses its collectionView(_:cellForItemAt:) method when a certain IndexPath becomes visible, but it throws that away again (or rather queues it again so your data source may dequeue it in collectionView(_:cellForItemAt:) and reuse it for another data set that becomes visible).
And btw, please don't use use the UICollectionViewDataSource's collectionView(_:cellForItemAt:) method to get the cell and then the data from there. This method is supposed to be called by the collection view and depending on how you reuse cells or create them, this might mess up the entire process. Or at the very least create view-related overhead. Instead, get the data in the same way your UICollectionViewDataSource would get in inside of the method. Wrap that in an additional method you rely on or the like. Or, even better, rely on the model object that the controller uses as well.
Edit in response to your comment:
No, I did not mean it's bad to use a UIViewController as a UICollectionViewDataSource for a UICollectionView. What I meant was that it's bad to use the UICollectionView to get data, because that's what the data source is for. In your question you were wondering why cellForItem(at:) gives nil. That method is defined on UICollectionView. You didn't mention your intention was to move items around (I'll explain in a second), so I assumed you were trying to get whatever data was in the cell (I know, "assume makes an ass out of u and me...", sorry :) ). This is not the way to go, as the UICollectionView is not meant to hold the data for you. Rather, that's your job, and you can use a UICollectionViewDataSource for that. This latter class (or rather protocol a class can adopt) is basically meant to offer an interface for UICollectionView to get the data. It needs that, because, as said, it doesn't keep all data around. It requests stuff it needs from the data source. The data source, on the other hand, can manage that data itself, or maybe it relies on some deeper class architecture (i.e. other objects taking care of the underlying model) to get this. That part depends on your design. For smaller scenarios having the data source simply have the data in an array or dictionary is enough. Furthermore, a lot of designs actually use a UIViewControllerto adoptUICollectionViewDataSource`. That may be sufficient, but be careful not to blow up your view controller to a monstrosity that does everything. That's just a general tip, you have to decide on your own what is "too much".
Now to your actual intention: To move around cells you don't need to get them. You simply tell the UICollectionView to move whatever is at a given index path to some other index path. The according method is moveItem(at:to:). This works even if cellForItem(at:) would return nil for one of the two index paths. The collection view will ensure the cells are there before they become visible. it does so relying on the data source again, more specifically its collectionView(_:cellForItemAt:) method. Obviously that means you have to have your data source prepared for the move, i.e. it needs to return the correct cell for the given index. So alter your data source's internal storage (I assume an array?) before you move the items in the collection view.
Please see the documentation for more info on that. Also note that this is basically how to move items around programmatically. If you want the user to interactively move them around (in a chat that seems weird to me, though), it gets a little more complicated, but the documentation also helps with that.
Based on your question. If the currently visible cells on screen are from 50 to 59, the cellForItem at IndexPath 30 will not be available. It would be nil. Reason being the 30the cell would have already been reused to display one of the cells from 50 to 59.
There would not be problem to move cell from 30 to 31. Just update your array/data source and reload the collection view.
You can access the cell only if its visible for non visible cell you need to scroll programmatically using indexpath:-
collectionView.scrollToItem(at: yourIndexPath, at: UICollectionViewScrollPosition.top, animated: true)
I want to loop through a TableView and extract the text from all the selected rows. I suppose I "could" create and maintain a special array that is updated every time a row is selected/deselected using the didSelect/didDeselectRowAtIndexPath methods. But creating a separate array seems like an extra step. Is there no way to let the TableView itself serve as the array and then simply loop through it and get the selected rows? What would the code look like? I'm new to Swift, so this might be a silly question.
Part of the problem is that cells are supposed to be reused, and when used this way it is not possible to loop through them all. You could get around this by using a unique reuse identifier for each cell, such as the indexPath itself or some underlying unique id in your model. Then, you could indeed loop through all cells and retrieve whatever state you desired from each.
You would, however, find your application crushed under the weight of too many cells being instantiated and kept in memory. If you don't have many cells you won't be killed, but try it with a big data set and your app will enjoy a very quick death.
It is far more efficient to store one array with a bunch of id's than a large number of memory-intensive UITableViewCells.
As mentioned in comments, you should work with underlying datasource, not the table itself.
For example if your table shows rows from Array, it is way more faster to retrieve strings directly from that array than creating UITableViewCells and get strings from them.
Get indices of selected rows using UITableView's property indexPathsForSelectedRows.
Query datasource for each row.
As has been said the tableview only handles displaying, your datasource is what powers the data shown if you think about it.
Plus as said before the tableview dequeues cells as they scroll on and off the screen.
The best way to achieve what you want is to add a property to your datasource for each element that will allow you to filter out the select properties easily.
How are you storing the state for each selected cell currently? As this is the same functionally you would use to be able to generate your selected text array.
I found that UICollectionView is like an upgraded version of UITableView introduced in iOS6, but when should I choose UICollectionView instead of UITableView?
There are still Apps using UITableView, if UICollectionView can do anything UITableView can do , why people still use UITableView? Is there a difference as far as performance is concerned?
Thanks!
That depends on the requirements. How the application flows determines which type of UI to integrate into the application.
People mainly use the UICollectionview for creating types of UIs with multiple images shown in a grid. This would have complex logic using UITableView, but with UICollectionview, it would be easy.
When using UICollectionview, you don't need to set buttons with tags or other things by getting selected items values. You can simply get -(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath and in UITableViewDelegate:
`-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath`
You get the selected row instead of the item, so for creating grid or modified items, using UICollectionview is best.
For the listing details of each item, people use UITableView because it shows more info on each item.
Apple Docs:
UICollectionView Class Reference
The UICollectionView class manages an ordered collection of data items and presents them using customizable layouts. Collection views provide the same general function as table views except that a collection view is able to support more than just single-column layouts. Collection views support customizable layouts that can be used to implement multi-column grids, tiled layouts, circular layouts, and many more. You can even change the layout of a collection view dynamically if you want.
UITableView Class Reference
A table view displays a list of items in a single column. UITableView is a subclass of UIScrollView, which allows users to scroll through the table, although UITableView allows vertical scrolling only. The cells comprising the individual items of the table are UITableViewCell objects; UITableView uses these objects to draw the visible rows of the table. Cells have content—titles and images—and can have, near the right edge, accessory views. Standard accessory views are disclosure indicators or detail disclosure buttons; the former leads to the next level in a data hierarchy and the latter leads to a detailed view of a selected item. Accessory views can also be framework controls, such as switches and sliders, or can be custom views. Table views can enter an editing mode where users can insert, delete, and reorder rows of the table.
Here's my criteria:
If a UITableView can do it, use it
If a UITableView needs lots of code to do it or can't do it at all, use UICollectionView.
You have to consider the restrictions on UITableView before making a decision: It's a single column. And you can only customize the cells, but not section backgrounds and such. So if you have a straight-up list of things with no extra frills - that looks like a bog standard iOS view, basically - then use UITableview. If you have custom insets, or a border around each section, use UICollectionView.
I'm actually considering UICollectionView for all things simply because it's very expensive when you start developing your view as a table view, then later find out it can't do that one thing that you need it to do. 1st hand experience ;)
Edit after even more experience with the two: Disregard that last paragraph. UICollectionView requires a lot of boilerplate code to make it work like a UITableView. Use UICollectionView only when really needed. ;)
For simple lists and forwards/backwards navigtaion, use UITableView.
If you need a high degree of customisability, use UICollectionView.
Generally speaking, in software development, it's best to choose the approach which represents "The Simplest Possible Thing".
EDIT: As of iOS 14, UICollectionView can now do lists as well and is now the recommended approach. See this session from WWDC20 for more information and implementation details: https://developer.apple.com/videos/play/wwdc2020/10026/
According to my point of view main difference between collectionView and tableView is that
TABLEVIEW --> show list of items in only one column.
COLLECTION-VIEW -->show list of items in multiple column.
Hope it will help you.
If you choose UITableView for iPhone, make sure you have considered your iPad strategy first. If you want an iPad-specific layout, you may want that single-column layout to become a grid.
Although it's not required, I always use a collectionview. That way I can easily adapt how my collections are presented for differing resolutions. A plus is that it's ready to quickly add new types of cells when refactoring in the future.
I see no point of tableviews. It's very simple to use a collection view to represent a table. IMO.
From my personal experience the two elements should only be compared loosly.
TableView
A TableView is a UI element designed for showing data in a list format. There is certain functionality that comes as standard with a UITableView, such as:
Accessory View
Cell Selection Style
Editting Style (Delete and edit buttons).
The above elements enhance the usability of data when displaying and interacting in a list format. Such as viewing emails.
CollectionView
A CollectionView is a UI element designed for showing content using a custom layout (usually anything that isn't a list). CollectionViews improve functionality of displaying data in completely bespoke layout styles and also dynamically changing layouts on the fly. Some examples are:
Horizonal Lists
Photo Galleries
Thumbnail views
Carousels
Dials
Laying out elements on a map
etc.
CollectionViews also allow for multiple selections.
Conclusion
As you can see from the above, both have completely different use cases and are designed for enhancing the development and usability of their own specific data sets.
If you are looking at displaying anything in a list style with the followin interactions:
- Adding
- Deleting
- Re-ordering
Then a UITableView will simplify this process by providing the support straight out of the box.
Anything else, you should leverage the benefits of CollectionView as you have more flexibility.
Its totally dependent on how your data to be shown.
As mentioned by many above, if you require only single set of data and that too not complex, go for UITableView else use UICollectionView.
UICollectionView is customization friendly.
If you are dealing with multiple cell heights or so, then go for UICollectionView.
Both are depends on the requirements. Table Views also have support for a variety of editing scenarios. This support has not been implemented in the Collection View classes.
If you are converting from a Table View that relies on these methods, expect to do a little extra heavy lifting in the Collection View.
Collection View section headers can be placed anywhere within the view.
and UITableView don't need to set buttons with tags or other things by getting selected items values.
In practice, everyone uses UICollectionView that I've come across, when they only need a UITableView. "It's one-dimensional. It goes up and down. Why are you adding unnecessary delegate methods for layout AND data?". I once spent an extra 2 hours helping a startup find out why their UICollectionViewCell got squished because the owner, who didn't read the Animations manual, nor HIG, nor the UICollectionView guide, decided to use it and add variable heights and anims. Needless to say, he gave himself a headache and much lost time on a non-business-critical issue he could have avoided by simply using a table cell, since there's no extra layout delegate + Nib.
Let me get this straight, I am all for UICollectionView's when your data and display need it. They're very powerful. But in practice, most people I've seen have been using them on lists.
This brings up another flaw. They're also used on short, constant lists that won't change, ever. In this case, just make a Xib. Or write a custom view that stacks them. Why? Because you don't need the memory management for 5 sets of labels with a button or switch. If they might change, then yes, use a list. If you want physics, then UICollectionView works well with a some cool effects. But do you really need to add 5 delegate methods and a layout system for 5 labels that will never move?
Also, I'm not forgetting that iOS has a native stacking view now too. I can never get it to deform how I want, even though I'm quite adept at the 2D and animation systems, so I never use the built-in one.
All I'm saying is, define your requirements. Maybe you don't need either of these, if your UI isn't adding/removing items and refreshing itself. Or maybe you want to write a Card Game and throw them out virtually on a table, then use UICollectionView with a physics system for its layout guide.
Personally I think the UICollectionView can do most of the work which UITableview can do. well, at the same time, it's more complex to use.
I suggest you use UICollectionView as TableView just in case your manager change requirements in the future.
Based on our need we are choosing TableView or CollectionView.
Example:
For phone contacts tableView is best option.
For photo gallery, collection view will be best option.
I had this issue in my current project. Which to use. In my case it was simple really. I needed both. I needed my view to look like UITableView and also to change its change / layout. So, UICollectionView was used. I also use UITableView everywhere I don't need any extra customisation. Since UiTableView comes with a default layout that includes images and text - I use it for simplicity.
Based on our requirement we choose UITableView or UICollection view.
If we want to display images or items in grid type or if we need more customisability we use UICollectionview.
For listing each item with details and subdetails we use UITableView.
UICollectionView:
The UICollectionView class manages an ordered collection of data items and presents them using customizable layouts. Collection views provide the same general function as table views except that a collection view is able to support more than just single-column layouts.
UITableView: A table view displays a list of items in a single column. UITableView is a subclass of UIScrollView, which allows users to scroll through the table, although UITableView allows vertical scrolling only.
As per my view for Grid View display use UI Collection View.All other list view use UITable View
When building a view for an iPhone app, one must consider how variable data will be determined by the view. Two design options jump readily to mind:
An NSArray of items
A dataSource property, which implements a protocol and returns the items.
The former is used by views such as UITabBar, while the latter is used by UITableView. What are the pros and cons of these options? Is there a reason for the two distinct paradigms, or is one universally superior?
It's mostly about the amount of data and limited amount of memory in relation to simplicity.
Simpler is always better if you can get away with it. A tab bar probably have less than 10 items which is no problem to hold in memory at once so the simplest solution is the best.
A table view however may have thousands of rows that may contain expensive data like images. Therefor it has a more complex design to be able to keep only the necessary data in memory.