Discussion:
Best strategy to update view controllers in navigation stack after users edit data
(too old to reply)
Glen Huang
2018-04-20 08:15:40 UTC
Permalink
Hi,

I have an app where user can edit data and save to my server. I wonder what’s the best way to update affected view controllers in the navigation stack?

To give an example, imagine it’s an a recipe app where users can create recipes and edit other’s recipes. In the navigation controller’s root view controller, I show a list of all recipes, in each cell, in addition to the recipe name, it also shows the total number of ingredients the corresponding recipe requires.

When you tap a cell, I show the detail of the corresponding receipt. In this detail view, I have a cell that links to a view controller that shows the list of ingredients, and in that view controller, users can tap edit to show a view controller that allows adding/removing ingredients.

This set up means the same data can be displayed across view controllers in the navigation stack, and the it changes, the they need to be in sync.

So the question is, when the ingredients change, what’s the best way to update that ingredient count number in the root view controller?

One more complication is that the list of all recipes come from my server (via json).

I think I have a few choices:

1. Load all data (the list of recipes comes with full details) into Core Data. Basically I create a local replica of my server data and use a NSFetchedResultsController to fetch all ingredients sectioned by recipes. And then show the array count in each section for each cell.

Make the recipe list view controller listen for NSFetchedResultsController change event and when the ingredients change, reload the cells.

But this means I have to deal with core data (really hard to write bug free code), and the initial loading might be slow if the list is huge. Any since I don’t really need to persistent the data locally, I can use in memory store, but I still have to manually purge the store when all recipe view controllers are popped (the root view controller of my app isn’t the recipe list view controller)

2. Server returns tailored data for each view controller. For example, for the recipe list view controller, the server returns a list of recipe containing only names and ingredients counts. For the recipe detail view controller, the server returns details just for the corresponding recipe, and in the ingredient view controller, the server returns a list of ingredients just for that recipe, basically every view controller needs to make a request to my server to get its data.

This means I need to define object types specifically for each view controller, even though the types might refer to objects are conceptually the same. It also means updating the recipe list view controller isn’t that straight forward, because its data has no connection with that in the ingredient list view controller.

One solution might be that when users save the ingredient, I post a message containing the recipe id and the new ingredient count to NotificationCenter, and the recipe list view controller should listen for that message and update accordingly. But this sounds pretty cumbersome.

Which solution do you think is more appropriate or do you have any other better way?

Thanks
_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.net

This email
Ben Kennedy
2018-04-20 21:46:26 UTC
Permalink
Post by Glen Huang
I have an app where user can edit data and save to my server. I wonder what’s the best way to update affected view controllers in the navigation stack?
If I were to give a literal answer to your question, I'd suggest a callback method (either as a closure property on the child VC or a classic delegation) by which the parent VC can be notified by the child of relevant change.

However, it sounds like you might be well off to re-frame your question more generally as "what's the best way to keep my views in sync with my data model". The case you described (a detail VC on a nav stack) is merely one such manifestation.
Post by Glen Huang
1. Load all data (the list of recipes comes with full details) into Core Data. Basically I create a local replica of my server data and use a NSFetchedResultsController to fetch all ingredients sectioned by recipes. And then show the array count in each section for each cell.
That sounds like a reasonable approach. You need to model your data locally somehow; Core Data provides a somewhat decent way to do it.

I'm also fond of Couchbase Lite. Especially if you are dealing in various unstructured data, it might be well suited. It provides change-listener callbacks in similar way. The server-side setup would no doubt be an undertaking compared to what you currently have though.
Post by Glen Huang
2. Server returns tailored data for each view controller. For example, for the recipe list view controller, the server returns a list of recipe containing only names and ingredients counts. For the recipe detail view controller, the server returns details just for the corresponding recipe, and in the ingredient view controller, the server returns a list of ingredients just for that recipe, basically every view controller needs to make a request to my server to get its data.
That does not sounds like a good idea. In essence you then have no local data persistence, and the remote server works like a database over a very slow link. The app would never work offline, and any trivial architectural changes in the client would need to be also made on the server.

b

_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.n
Glen Huang
2018-04-21 09:24:55 UTC
Permalink
Thanks for the reply, Ben.
Post by Ben Kennedy
I'd suggest a callback method
I thought about this approach, but it has the drawback that child view controller needs to be aware of all other view controllers that it needs to notify, when the hierarchy of view controllers is complex, finding the correct ones isn’t that easy.
Post by Ben Kennedy
I'm also fond of Couchbase Lite
Thanks for brining Couchbase Lite to my attention. I wonder if it can easily handle circular data structure (or many to many relationship to be precise), that’s one of the benefits Core Data offers that I rely on heavily.
Post by Ben Kennedy
any trivial architectural changes in the client would need to be also made on the server.
Good point.
Post by Ben Kennedy
Post by Glen Huang
I have an app where user can edit data and save to my server. I wonder what’s the best way to update affected view controllers in the navigation stack?
If I were to give a literal answer to your question, I'd suggest a callback method (either as a closure property on the child VC or a classic delegation) by which the parent VC can be notified by the child of relevant change.
However, it sounds like you might be well off to re-frame your question more generally as "what's the best way to keep my views in sync with my data model". The case you described (a detail VC on a nav stack) is merely one such manifestation.
Post by Glen Huang
1. Load all data (the list of recipes comes with full details) into Core Data. Basically I create a local replica of my server data and use a NSFetchedResultsController to fetch all ingredients sectioned by recipes. And then show the array count in each section for each cell.
That sounds like a reasonable approach. You need to model your data locally somehow; Core Data provides a somewhat decent way to do it.
I'm also fond of Couchbase Lite. Especially if you are dealing in various unstructured data, it might be well suited. It provides change-listener callbacks in similar way. The server-side setup would no doubt be an undertaking compared to what you currently have though.
Post by Glen Huang
2. Server returns tailored data for each view controller. For example, for the recipe list view controller, the server returns a list of recipe containing only names and ingredients counts. For the recipe detail view controller, the server returns details just for the corresponding recipe, and in the ingredient view controller, the server returns a list of ingredients just for that recipe, basically every view controller needs to make a request to my server to get its data.
That does not sounds like a good idea. In essence you then have no local data persistence, and the remote server works like a database over a very slow link. The app would never work offline, and any trivial architectural changes in the client would need to be also made on the server.
b
_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.net

This email
Markus Spoettl
2018-04-21 11:05:04 UTC
Permalink
Hi,
Post by Glen Huang
I have an app where user can edit data and save to my server. I wonder what’s the best
way to update affected view controllers in the navigation stack?
To give an example, imagine it’s an a recipe app where users can create recipes and
edit other’s recipes. In the navigation controller’s root view controller, I show a
list of all recipes, in each cell, in addition to the recipe name, it also shows the
total number of ingredients the corresponding recipe requires.
When you tap a cell, I show the detail of the corresponding receipt. In this detail
view, I have a cell that links to a view controller that shows the list of ingredients,
and in that view controller, users can tap edit to show a view controller that allows
adding/removing ingredients.
This set up means the same data can be displayed across view controllers in the
navigation stack, and the it changes, the they need to be in sync.
So the question is, when the ingredients change, what’s the best way to update that
ingredient count number in the root view controller?
This sounds to me like a very good application for KVO (Key Value Observation).

All you need is a model (your representation of recipes with names, pictures, descriptions
and ingredients). Each individual view controller knows what it's interested in, for
example the main view controller likely doesn't care about descriptions or ingredients,
just names and pictures, I imagine, while the detail view controller does display
everything so it will observe all those properties.

In every view controller you observe properties of the objects you display and implement
-observeValueForKeyPath::::, for example in your details view controller:

NSString *kRecipeNameContext = @"kSomeNameContext";

- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];

// assuming recipe is set already
[recipe addObserver:self
forKeyPath:@"name"
options:NSKeyValueObservingOptionNew
context:kRecipeNameContext];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
{
if (context == kRecipeNameContext) {
[self updateName];
} else {
[super observeValueForKeyPath::::];
}
}

Once this is in place, every time the name changes for the given recipe, your view
controller will be notified. The great thing here is that ANYONE interested in the
property can use this mechanism (you could have multiple view controllers that display
some aspect of the same recipe, so they all want to know when the name changes) and you
will have to maintain zero code that makes sure that you know who that might be.

When you no longer need the view controller (for example if the view controller gets
popped and destroyed), make sure to "unsubscribe" to changes via

- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[recipe removeObserver:sefl forKeyPath:@"name" context:kRecipeNameContext];
}
Post by Glen Huang
One more complication is that the list of all recipes come from my server (via json).
It does not matter where the data comes from so long as you make sure it is added to the
model in a KVO-compliant way. This basically means, always to use property setters to
update a model object instead of setting instance variables directly.

There's a little more to it when you want to observe arrays (as in wanting to know when
objects get added or removed). In that case you have to implement collection property
accessor methods for the array in your model class that contains the array. To access the
array, you always use proxy objects for arrays when you add or remove objects. Sounds
complicated but it isn't.

Say you have an "ingredients" NSMutableArray in your Recipe class.


@implementation Recipe

- (NSUInteger)countOfIngredients
{
return [ingredients count];
}

- (id)objectInIngredientsAtIndex:(unsigned)theIndex
{
return [ingredients objectAtIndex:theIndex];
}

- (void)getIngredients:(id *)objsPtr range:(NSRange)range
{
[ingredients getObjects:objsPtr range:range];
}

- (void)insertObject:(id)obj inIngredientsAtIndex:(NSUInteger)theIndex
{
[ingredients insertObject:obj atIndex:theIndex];
}

- (void)removeObjectFromIngredientsAtIndex:(NSUInteger)theIndex
{
[ingredients removeObjectAtIndex:theIndex];
}

- (void)replaceObjectInIngredientsAtIndex:(NSUInteger)theIndex withObject:(id)obj
{
[ingredients replaceObjectAtIndex:theIndex withObject:obj];
}


@end

You can find more information on this here:

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueCoding/DefiningCollectionMethods.html

The point of this is that you then can observe the "ingredients" property of the recipe
just like the observation above and you will get a -observeValueForKeyPath::::
"notification" if items are added, removed or replaced.

In order for this to work you just need to use a KVO proxy for the real array. So you
don't add or remove objects from your instance variable "ingredients" but instead you do
it like so:

NSMutableArray *kvoIngredients = [self mutableArrayValueForKey:@"ingredients"];
[kvoIngredients addObject:item];

-mutableArrayValueForKey: returns a proxy object that you can use to manipulate the array
instance via the property accessor methods above.

Don't be intimidated, once you get a hang of it, KVO becomes second nature. KVO an
bindings can save you millions of hours of coding.

Hope this helps.

Best Regards
Markus
--
__________________________________________
Markus Spoettl
_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.net

Th
Continue reading on narkive:
Loading...