UITableView w/ optimistic UI updates in iOS - ios

I have a UITableView that contains a number of 'likeable' items.
This status can be toggled via a UIButton in the cell.
I'd like to update the UI to reflect changes when a user un/likes an item in an optimistic fashion. Should the operation fail, I will then roll back these values.
I am trying to understand how to implement this change without complication the design of my application.
Currently I have 3 layers;
API
Presentation
UI
Each layer communicates via adapters that map between delegates and interfaces to avoid coupling the implementations.
The Presentation layer maps a RemoteFeedItem into a FeedItemViewModel which a FeedItemCellController will then use to prepare a UITableViewCell.
The FeedItemViewModel contains only the values for the UI in the correct state/type.
For example the 'likes' count is now a String not an Int so this can be rendered by a UILabel
My UITableView has an array of FeedItemCellController's which are used in cellForRowAt.
I am trying to understand where the best place for this optimistic UI change to take place is.
Updating the value in the cell on tap works, however should the user scroll before the async operation is completed the change reverts when the cell is reused as the original model is unchanged.
If I make a change in the model, this will persist across cell reuse, however I need to keep a reference to the updated model somewhere - the issue here is I now have 2 sources of truth for my cell -
The model produced by the Presentation layer
My local copy, updated with the new status
Is this OK - under the assumption both will be eventually consistent?
Or should the Presentation layer be notified of the action and produce a new model based on this?
That way of the action fails, it can produce a new model again with the reverted values?
I appreciate this may be an opinion point, but I am keen to find a way to implement this in a clean and testable fashion.

Related

ViewModel for UITableViewCell: Database operations "allowed"?

I am trying to understand how to correctly use the MVVM pattern in iOS. Lets assume we have a music player app with a playlist like in the following sketch:
I have a PlaylistViewController and a PlaylistViewModel. Furthermore the cells (SongTableViewCell) have a view model SongCellViewModel which holds the data (song name,..). If the user presses the X button on the TableViewCell I have to delete the song from the database. The cell is calling the following function in the SongCellViewModel:
func deleteSongFromPlaylist() {
DatabaseService.shared.deleteSong(self.song)
}
My question: Is it good to do it that way? It doesn't feel right to initiate database operations in the view model of the TableViewCell.
I suggest moving such operations to PlaylistViewModel and do all operations from there. It also simplifies implementation of later improvements like showing activity indicator if needed or reloading certain cells or views from there.
I think that SongCellViewModel should have a actionBlock which will take enum with associated values - for example .select(song) .delete(song) - and this way you can easily communicate between PlaylistViewModel and SongCellViewModel.
One more suggestion is to use Dependency Injections instead of using shared for all type of services.

What is a better way to deal with data after an async call using NSURLConnection?

This is the current logic that I'm using to populate a table view with NSURLConnection. It doesn't seem elegant to me.
Table View Controller's viewDidLoad method calls "sendConnection" method in my api wrapper class with the URL string as a parameter. This method makes the NSURLConnection. In connectionDidFinishLoading (which is in my wrapper class), another method is called (also in the wrapper class) with the connection as a parameter. This method extracts the URL from the connection object and examines it. It then uses a switch statement to deal with the data depending on the URL. The data is stored in variables in the wrapper class itself. By the time cellForRowAtIndexPath is called, the async call has finished and the data has been processed.
Is there a better way of doing this?
My reason for asking this is as follows:
I want to refresh a cell with a new height and a new text label when it is clicked. The data for this text label will be retrieved from the server upon the cell being tapped. Each cell will have slightly different data in the label (each cell represents a 'user' and the label will display how many mutual friends you have with the user). I want to store the data in the cell itself when the data is retrieved and then place it into the text label. This doesn't seem possible with my current way of making URL calls.
Any help with how to achieve this would be appreciated.
Here is some pseudo code for a pattern I like to use in these situations. Maybe it will help you as well.
- (void)viewDidLoad {
//1. put up some type of progressHud or spinner
//2. call your NSURL wrapper
//3. in the completion block of your wrapper, set your datasource variables
//example: #property (nonatomic,strong) NSArray *listOfData;
//4. create a custom setter for your datasource that calls tableview reload
//5. enable a refresh function; like "pull to refresh" or a bar button
//6. when pull to refresh is tapped or called, just repeat these steps
}
- (void)setListOfData:(NSArray*)listOfData {
_listOfData = listOfData;
if (_listOfData) {
[self.tableView reloadData];
}
}
As I read your question again, here are a couple more thoughts:
the pattern above will work for your initial load, to create the list of people or friends, etc.
If you plan on making another round trip after the cell is tapped, then you have to consider a number of issues. This is similar to a common problem with lazy loading images into tableview cells. There are issues like scrolling to consider - what if the cell is scrolled off the view before the data returns, for example, what if the cell has been reused, now the data is not tied to that cell any longer.
There are many async image libraries available on Github that would be good to look at to see how they solved those issues. Generally they are keeping track of the item in the cell and then checking if the cell is still in view and if so, they set the image.
You have a similar issue to solve. Tap the cell, get the new data, then update the cell. Resizing the cell will require you to reload it.
Look into [tableview reloadRowsAtIndexPaths:(NSArray*) with RowAnimation:(UITableViewRowAnimation)];
hope that helps
best wishes;
You should have a "Data Model" which represents the content (that is the cells) of your Table View.
Since you have "rows" in your table view, it makes sense this data model is a kind of array (possibly a NSArray) whose elements keep the data and state of the cell.
The data for each cell should not only have all the "data" properties rendered in your cell (e.g. the label) but also its state:
When a user tabs on a cell it will start an asynchronous task. This task may take a while to finish since it fetches data from a remote server. Think of several seconds, or even longer. You need to keep track of pending update tasks, since your implementation should prevent the user to update a cell again before the corresponding pending update task has been finished.
There are several techniques to accomplish this. One way is to have a property in your "Cell Data" class which reflects this state, for example:
#interface CellModel : NSObject
#property (atomic) BOOL hasPendingUpdate;
...
When the cell will be rendered, you retrieve the value of the property and render the cell appropriately.
When the update task finishes, it updates its cell model data.
This model update will eventually update your Table View. There are several techniques to accomplish this. You should take care about thread-safety here and the "synchronization" of your Data Model and the table view cells. For example ensure the value of the hasPendingUpdate only changes on the main thread - since otherwise your rendered cell may become out of sync with the data model (not to mention race conditions in case you modify and access the property on different threads without synchronization primitives).
While the cell waits for an update, it should visually indicate this state (using a spinner for example) and disable the action to start an update task.
Very much recommended is a "Cancel" button, which either cancels a certain cell update task or all pending update tasks.
When the user moves away from this view, you may consider to cancel all pending tasks.

Is this the correct "layout" for delegation?

I'm building a simple calendar application based in a single view controller. The view controller has a hierarchy as follows:
CDViewController
CalendarView
EventView
AgendaView
TPKeyboardAvoidingTableView
EventInfoCell
My question specifically pertains to the EventInfoCell within my AgendaView. The Cell has various ways of laying out textFields and textViews, depending on the information that needs to be displayed. These textfields and textviews are enabled/editable based on the tableview's edit status and edit/delete core data objects. When the strings within these change, the cell needs to alert the managedObjectContext to save or delete an object, update the model within the agendaView that populates it's tableView, and update the model that pertains to all events for the calendar. My current configuration makes the viewController the delegate of EventInfoCell, but this complicates the update for the agendaView model. It seems almost counterproductive to set the EventInfoCell delegate to be the AgendaView, tell it when information changes, and then have the AgendaView pass the word on to it's delegate (the viewController), but if it is more compliant to MVC than bypassing the agendaView altogether, I will gladly implement it. I'm trying to figure out what is the better design pattern, any input is greatly appreciated.
"It seems almost counterproductive to set the EventInfoCell delegate to be the AgendaView, tell it when information changes, and then have the AgendaView pass the word on to it's delegate"
Why does this seem counterproductive? This is the way I would handle it. If you don't do it this way then you are going to have to open up some sort of properties or a notification or something in the AgendaView for the ViewController to pass the information back to it. That sounds more counterproductive to me than passing the information up the chain. Plus, you may eventually have some sort of changes in the EventInfoCell that require its delegate to act, but do not necessarily alter the Core Data model. So, I think you should pass the delegation up the chain.

How to make UI elements around an app keep track of their own states

I have a network client iOS app. There is a "controller" object that is responsible for receiving updates from the network. It should store / forward them to the various visible pages in the app. One page may need some of the data, one page may need some other part. There is overlap.
For example, a button needs to be highlighted or not based on the status of a device on the network. Buttons on different pages may need to reflect this status.
I need to determine if my various view controllers need to handle this or if the UI elements themselves can do it. In my example, I will need the UI button to react to events, probably based on it's "tag" field.
I've thought about implementing a category to "wrap" the various UI elements, but I'd like to use storyboard layout. This seems convoluted. Or, I can set tags on the UI elements and have the enclosing view controller gather all these up into a dictionary of UI elements and do the watching/updating using tags as keys. Or...?
I guess I'd like some pointers on what model may be best. I need this to be flexible and adaptable moving forward, so I'd can't have a bunch of code and IBOutlets for each UI item. I'm trying to keep everything as generic as possible so that when I need to make changes, I can add UI elements, set their tags, and let their enclosing view controllers take care of them.
Thanks
I think you should think about it this way:
Your app needs a model that represents the external state of the
world; in your case, devices on the network.
Another part of your app (probably a singleton with some
timers) is keeping track of the state of the world (probably doing
GETSs using NSURLConnection) and then updating your model.
Your ViewControllers present a view to the user, and observe the state
of your model, telling the views to change when the model does. (e.g. your viewcontroller would observe that a device in your model changed state to offline and then set a corresponding myButton.enabled=NO).
Take a look at iOS key-value observing docs to learn more about that. If you think you'll have many views observing the same aspects of the model, consider using the NSNotification center.

Core Data: Deleting Views Stored in Core Data

I am trying to delete views that have a yellow shadow from my main viewcontroller.
It registers the number correctly but it doesn't delete. (It doesn't update the view I have tried to call setNeedsDisplay and all of those lines but the don't work. It only updates when you quit out of the app an reload it. It isn't in the managedobjectcontext but it stays in the view. Am I not releasing something?) If I had it so it only passed one item .. if you clicked on it to delete.. it would have worked but this isn't working with the shadows. Can you see why???
Update:
I have views that are stored in core data (pages) and I want to delete the pages when they are selected and have a yellow shadow. If I need to how to I add the view to an array or something when it adds the shadow and then finds them when it needs to delete.
-(void)trashitems{
for (NSString *itemKey in [itemViews allKeys]){
UIView<CollectionViewItemView> *itemview = [itemViews objectForKey:itemKey];
if ([itemview layer].shadowColor == [UIColor yellowColor].CGColor){
NSLog(#"remove %i",[[NSDecimalNumber decimalNumberWithString:itemKey] unsignedIntegerValue]);
if ([dataDelegate respondsToSelector:#selector(collectionView:canDeleteItemAtIndex:)]
&& [dataDelegate collectionView:self canDeleteItemAtIndex:[[NSDecimalNumber decimalNumberWithString:itemKey] unsignedIntegerValue]]
&& [dataDelegate respondsToSelector:#selector(collectionView:didDeleteItemAtIndex:)])
{
[itemViews release];
NSUInteger itemsCountBeforeDeletion = [dataDelegate countOfItemsInCollectionView:self];
[dataDelegate collectionView:self didDeleteItemAtIndex:[[NSDecimalNumber decimalNumberWithString:itemKey] unsignedIntegerValue]];
NSUInteger itemsCountAfterDeletion = [dataDelegate countOfItemsInCollectionView:self];
if (itemsCountBeforeDeletion - 1 != itemsCountAfterDeletion){
[NSException raise:#"Collection View Deletion Exception" format:#"Count of items in collection view before deletion (%u) must equal one more than count of items in collection view after deletion (%u) but did not.", itemsCountBeforeDeletion, itemsCountAfterDeletion];
}
}
}
}
}
Like Tom said, storing a view in Core Data is bizarre. To make a view disappear, it needs to be removed from the view hierarchy. The data should be separate from the view. I highly suggest reading up on the MVC (Model-View-Controller) design pattern.
You've got a serious design problem here. This simply isn't going to work and you need to start over.
The Apple API uses the Model-View-Controller design pattern. It should have been called the Model-Controller-Interface design pattern because that better captures the true relationships. The model holds the data and data-behaviors, the controller connects the model to the interface and the interface provides the data to an external observer such as human looking at a command-line/GUI, another process or a remote server process.
You say that:
I am trying to delete views that have
a yellow shadow from my main
viewcontroller.
... but you are really not. The subviews themselves display some kind of data e.g. an image while the yellow shadow conveys to the user some kind of information about the state of that data e.g. a yellow shadow indicates that the image is older than some date. What you are really trying to do (in this example) is delete images that are older than a certain date and then you want the views of the user interface to reflect that change in the data.
Now the data of the image and its state of being older than a certain date belong in the Model. The controller reads the data from the model and configures the view and subviews according to the data provided. The controller doesn't know the logic of why the view should look like it does for any piece of represented data and the views don't know about the data at all, they just know what image they will display and what color their shadow is.
When you are using Core Data, you use it to create the model layer. You don't use it create the controllers, views or to store any state information directly related to the operation of the controllers or views. Ideally, a data model should be perfectly functional regardless of what kind of interface you eventually use i.e. it should work equally well with a command-line, a GUI, a webpage or an interprocess communication. It simply doesn't know or care about anything not directly related to the data and the associated logic (e.g. images older than a certain date need to be deleted) of how the data fits together.
So, you need to figure out what is data and data-logic and put that in Core Data while keeping the details of the UI in that displays that data in the controllers and views.
I can't really tell you exactly what you need to do because I don't know what data your app uses or what its data-logic is but I do know that you need to take all information concerning the actual views and their configuration out of Core Data.

Resources