Re: Best strategy to update view controllers in navigation stack after users edit data
Re: Best strategy to update view controllers in navigation stack after users edit data
- Subject: Re: Best strategy to update view controllers in navigation stack after users edit data
- From: Markus Spoettl <email@hidden>
- Date: Sat, 21 Apr 2018 13:05:04 +0200
Hi,
On 4/20/18 10:15 AM, Glen Huang wrote:
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];
}
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 (email@hidden)
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:
This email sent to email@hidden