Displaying complex data in a UITableViewCell - ios

I have a custom UITableViewCell class that I use to display quite a complex set of data.
Essentially the cell displays a Match object. But in doing so it displays information about the two Teams, the score, the time elapsed and so on.
Thinking about MVC and clean code.
Should I just pass in the Match object and let the cell do everything? Or is it better practise to expose the different elements of the cell (team1NameLabel, team1ScoreLabel, team2NameLabel, etc...) and set them all individually in the UITableViewController?
The first way makes the UITableViewController cleaner but then I'm relying on the UITableViewCell to "know" about the Match class, the Team class etc...
The second way makes more work for the UITableViewController but then makes the UITableViewCell a "dumb" display. All it does is then lay out the information within the cell. It doesn't know anything about the information it is displaying.

I would follow these rules:
The cell should just have the outlets for displaying the various bits of data. It is a view so it should not contain any logic.
The controller should get the Match data, parse and make calculations if necessary, and populate the cell. It is a controller, so that is its primary function in a MVC context.

IMO it is better and more MVC-like to pass the Match object to your table view cell.
Lot of the code you find on the internet(even Apple examples if I remember well) is not doing that. You can see many times a configureCell method within the view controller that is called in tableView:cellForRowAtIndexPath:.
I prefer to pass the model object object to the cell, it makes my view controller code simpler and also it is simpler to unit test: when I test my view controller I only verify that the model object is passed to the cell, and then in the table view cell tests I verify that the test of the labels is set to the expected values. Someone may say that this is making the view knowing about the model, but I don't see any big problem on that.

Both ways are fine, but personally I would go for second option, i.e. table view exposing #property and, if necessary, outlets.
However, if you really want to go for the first option, I would suggest to have any objects passed to the cell to implements a protocol exposing few methods:
#protocol tableViewCellProtocol
-(NSString*)titleForCell;
-(NSString*)descriptionForCell;
Then you can "pass the protocol" rather than the object.
[mytableCell renderObject:objectImplementingProtocol];
This way you slightly decouple the objects itself, and prepare cell for reuse with other objects.

Related

UICollectionViewCell does load only when on view?

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)

iOS controllers for custom views

I'm all for trying to create lightweight view controllers (testability, separation of concern, etc. etc.), however, I've been unable to find a reasonable solution or pattern when it comes to certain cases.
A very common case (with plenty of examples out there) is separating the view controller from a tableview's delegate & datasource; I get this, it makes complete sense. But what about cases where a view controller may contain multiple custom views of varying complexity? What should be responsible for controlling each of those views? Surely not just the parent view controller.
I tend to think of a 'UIViewController' as more of a screen controller that is heavily coupled with the UI framework and its events; it does not have a single responsibility of controlling one particular view. To further illustrate my point, imagine a tableview with a couple of different prototype cells - some of which are fairly complicated and may require network access for instance - how should this be managed? Surely no single view controller, datasource or delegate should act as the "controller" for all of these cells? And a lot of that logic/responsibility does not belong in the cell views themselves, so it needs to be delegated somewhere.
One option I've thought of is to just create controller objects (subclasses of NSObject) that act as "view controllers" for the custom views I create, such as a controller object for a complex tableview cell - its single responsibility is to manage that one particular view. The tableview cell then delegates to the controller object, which then (if needed) delegates back to the parent UIViewController. Whilst this will work and helps to separate concerns, it starts to feel a bit awkward with all the layers of delegation going on.
Does anybody have any good suggestions on handling these scenarios or know of good code examples out there that demonstrates this?
Thanks!

Best way to implement UICollectionViewDataSource protocol?

I have theoretical question.
Currently my app is using UICollectionView as a way to display objects list. UIViewController, that contains UICollectionView as subview, implements UICollectionViewDelegate protocol and acts as delegate and datasource. Datasource uses NSFetchedResultsController to provide data;
In my opinion this is not the best way to implement datasource, and implementing it in separate class looks way better idea. But the issue it that datasource depends on search parameters in UITextField, and some other buttons selections, so every time when user types text into search field or press the any of "sorting" buttons I should update datasource (in particular fetchRequest in NSFetchedResultsController).
So, finally, my question: Is there any "best practices" of implementing datasources that depends on external parameters? Should I create separate class for datasource of leave it the way it is now? If implementing datasource as separate class - should I create datasourcedelegate for calling self-made delegate methods on delegate when datasource was updated or there is some other workarounds for this problem (I'm not considering using notifications on datasource update because as for me notifications mechanism is more global solution then I need here)?
I'm not looking for the fastest way, I just want to find out the rightest theoretical way of implementation.
Thank you all in advance :)
I personally implemented a concrete NSObject derived class, that implements UICollectionViewDataSource as well as NSFetchedResultsControllerDelegate that practically translates the fetched results controller events (object inserted, updated, deleted) to collection view events (insert, update or delete cells). You can find examples on how to do this, I took mine from here but I implemented it as a separate class instead of a category over collection view. I found my class highly reusable, in practice I use it in all of my projects where there is a need to visualize managed objects in a collection view. A similar class can be implemented also for UITableViewDataSource.
If you need to update the fetch request with the search predicate, I would subclass your newly created DataSource class, and add the logic to update the fetch request right there. Say, you add a -(void)updateSearchFilterWithText:(NSString*)text method where you add the logic to update the fetch request of the fetched results controller. Don't forget to perform fetch again afterwards and call a reloadData on the collection view!
With this architecture the view controller owns this dataSource object. Every time the user updates one of your filtering text field (or other widget), the view controller calls the updateSearchFilterWithText: of your data source object and the rest of the work is done by this later.
What you currently have is the standard approach. While there is no defined 'best' approach, what you describe is certainly a better approach.
Your view controller would own an instance of your new data source class, and would itself most likely handle the delegate methods (because these are actions to take rather than data to provide), so when anything changes in the UI the view controller should be 'pushing' these changes to the data source. No additional delegation should be required.
You shouldn't be creating your data source with the idea that text fields and buttons are directly driving changes in. Your data source should be presenting a generic interface where you can update the fetch request to execute (which covers the predicate and sorting) and change how the cell is configured (perhaps with a block). This way you keep your business logic in the view controller and the reusable data source code in another class that is reusable for other collection views / projects.

What is best practice when it comes to reusable UITableViewCell's and delegates?

I'm building an app that presents table views in a majority of the screens. They present a handful of different table view cells, but there is one that is presented in 3/5 of the table views. This cell, which displays a video and provides an interface for users to interact with the video (like, comment, flag, delete, play/pause, etc), has a fairly large delegate with seven methods/functions.
My question is:
Would it be a best practice to set up a separate controller that would be a property of my view controller to be assigned as the delegate to the cell, or to subclass a UITableViewController with the methods already implemented?
The problems I see with the latter is that I would then have to implement a weird way to handle the data source (set up methods to return the models, always ensure that videos are stored in that array) and the former just seems a little odd to the standard MVC practice. Any advice?
Update
I began factoring out a data source to use that implements the cell's protocol. Another issue that I seem to be running into is displaying multiple cells, i.e.:
I have a searchDisplayController that displays UserCell's and VideoCell's, based on the selectedScopeIndex of the search bar. One way I could handle this is to create a dataSource for this tableView that handles both cases, or swap out data sources based on changes to the selectedScopeIndex. Are either of the two options looked down upon? Is swapping a table view's data source valid?
I solved this issue by implementing a UITableViewDataSource controller that would also handle the cell's delegates. I was able to shorten the 7 method delegate to a 3 method delegate on the data source, used to push new controllers, remove objects from the data model, and handle fading/updates.
Granted, I needed to pass reference to the UITableView, the UIView, and the UIStoryboard of the source UIViewController, but the code is much more readable and manageable.

How to convert static UITableView to dynamic TableView

I've got a static TableView that now needs to become a dynamic TableView, because other views need to be placed around the ViewController, and this can not be done using containers in my case.
The question is: how do I efficiently convert the table view from static to dynamic?
I'm aware of having to change the inheritance from UITableView to UIViewController and add the plus the delegate methods.
But how about all of the Table-Sections: I have 3 sections with 6 types of cell in the static table. Do I really need to subclass UITableViewCell for all of these cell-types and deal with everything manually, or is there a more clever way to do this?
You really can't just convert between the two. By merely implementing some of the tables delegate methods, like cellForRowAtIndexPath:, you loose your static content. That being said, the table should be dynamic the entire time. This way, you can define logic to determine whether or not it should show the content that you originally added statically, or the new dynamic content.
Additionally, you don't need a view controller to implement the delegate/datasource methods. If you already have a subclass of UITableView, that's fine. You can set it as its own delegate/datasource, and implement those methods within the subclass.
And to answer your last question, no there really isn't a better way to do that. I recommend that you create one base class that subclasses UITableViewCell that implements everything that the cells will share, and then implement the individual changes in subclasses of this base class. Using multiple cell subclasses in a table view sounds a lot worse then it is.

Resources