Elegant data model for UITableViewDataSource - iphone

I have a model that essentially stores contact info: phone, twitter, address, etc.
I need to show that data in a UITableView, so I need essentially 3 pieces of information: a label ("Phone"), the actual data ("(123) 456-789") and a URL to handle that data with the right application ("tel://whatever").
I was thinking of using 3 arrays, one for each piece of info, and going through each attribute, see if it's given (could be optional), and if so, add stuff to the 3 arrays for that attribute.
Then, in the data source for the table view, each row can reference an index in the 3 arrays to populate the cell.
The order is important so I can show address before phone, etc.
Can anyone come up with a more elegant way to design the model so that I may not need 3 arrays, or lots of:
if(self.phone != nil)
{
[labels addObject:#"foo"];
[descs addObject:self.phone];
[urls addObject:#"baz"];
}
There will only be one "Contact" that can have a varying number of attributes, depending on some outside condition. So each attribute is optional. The table view will show the attributes of that one contact, in a certain order.
Thanks!

I believe you should create a model class, with properties for each attribute. Otherwise this will come back and bite your ass some day.
Storing the values in an array, means you have stored presentational meaning into the model. Breaking the MVC pattern.
This also means you can have some actual logic in your model object, which will reduce potential defects allot. As an example:
// Contact.h
#interface Contact : NSObject {
}
#property(nonatomic, copy) NSString* phoneNumber;
#property(nonatomic, readonly) NSURL phoneNumberURL;
#end
// Contact.m
#implementation Contact
#synthesize phoneNumber;
-(NSURL*)phoneNumberURL {
return [NSURL URLWithString:
[#"tel:" stringByAppendingString:self.phoneNumber]];
}
#end
This way you do not have to update the URLs from outside the model object. The model object should be able to derive this information itself. This will ensure that your application logic is not spread out into dozens of different places, but are kept in well defined pockets of logic that are easy to maintain.

Related

iOS Collections and a strange algorithm

So I want to know if this is a good idea or a bad idea.
I'm building a simple iOS game (using standard UI Controls) which allows a user to create Characters, and Monster "Templates", then build Encounters which references Characters and Monsters and interact with them.
When the user creates an encouner, there is a simple Modal View which allows them to name the encounter, then push to another VC to select the characters participating, go back, push to a second view controller to select the Monster Templates involved, as well as how many of those monsters will be participating.
The goal, in the end, is to have the Monster Templates be used to construct "real" monsters that will be references in the Encounter.
Sample Encounter
Player Characters
Ramza Beoulve
Delita Hiral
Monsters
Orc 1
Orc 2
Orc 3
For the character selection piece, I used an NSSet to store the Character Entities selected and pass it between view controllers. (This way I avoid having to mess with the managed object context very much prior to actually saving the new encounter)
For the monsters, since I need to store a quanitity as well as the entity, it's a little more complicated.
So my original thought was to store them in an NSArray of NSDictionaries which in turn contain the Monster Template and the Quantity.
The problem with that approach is I have to loop through the NSArray and open each individual dictionary to check if a particular monster template exists or not.
It might not matter much at this scale of application, but it seems inefficient.
So I thought instead it might be better to simply maintain two NSMutable Arrays
NSMutableArray *selectedMonsterTemplates;
NSMutableArray *selectedMonsterTemplateQuantities;
This way I can simply call
[selectedMonsterTemplates containsObject:monsterTemplate];
when I need to check if something is already in there, and when I add or subtract the quantity for a particular monster, I can simply update both Arrays.
Then when the Encounter is saved I can simply iterate over the Array once to create my individual monster instances in the quantity desired, and associate them with the Encounter.
I'm concerned that this approach, while simple and efficient, might lead to concurrency problems if there is a small mistake in the code. Is there a better way to go about this?
Maybe its a better design to add a class, lets say TemplateStore. (Or whatever you like)
#interface TemplateStore : NSObject {
MonsterTemplate *template; //Super class of a monster entity.
NSInteger quantity;
}
- (id) initWIthMosterTemplate:(MonsterTemplate *)temp;
- (void) increaseQuantity;
- (BOOL) isKindOfTemplate:(MonsterTemplate *)otherTemp;
#property (readonly, retain) MonsterTemplate *template;
#property NSInteger quantity;
#end
WHen you store this kind of object in an array, you can iterate over it and use the isKindOfTemplate method to know if the MonsterTemplate exists. Then just increase the quantity.
Generally speaking, you wouldn't make an array of dictionaries just to hold structured data, you'd use a single dictionary with the item (template) as the key and the quantity as the value. Pseudocodey it would be like:
quantity=dict[template];
Of course that's assuming you have templates stored as pointers somewhere. A better alternative is to use structures:
struct monster_t { template_t t; int quantity; }
And then you hold an array of these things:
monster_t[] bestiary;
Or even better, figure out an ID of sorts (unique monster name file?) and hold it in a dictionary:
dictionary<string, monster_t> dict;
dict["big_monster_01"].quantity=5;
Of course this system is pretty useless, you don't care about quantity at all. What you do care about is about actual monster instances in the world, basically templates with customized values for hit stats, loot etc and a position:
struct monster_instance_t { monster_t template; vector position; }
monster_instance_t[] monsters; // who cares what they are?
// that's what polymorphism is for!

Effective way to design and manage iPhone application similar to settings

I would just like to clarify that by 'design', I mean software design, not UI design.
I have an application similar to the native settings app. The problem I have with it is it doesn't follow the same clear-cut MVC style. Other apps tend to focus around displaying one kind of thing. In the case of a periodic table app for example, it's elements. The elements clearly comprise the model, and they share similar properties and behaviours, meaning they can be displayed and interacted with identically. An app like this almost designs itself!
My app, like the settings apps, consists of an arbitrary selection of rows displaying dissimilar data in dissimilar ways. One row might contain a switch, the other might modally present a very specific view when tapped. They're all very different.
How do you design something like this?
At the moment, I'm doing it all in the view controller, and the relevant rows are being tracked via an enum:
enum {
kNameRow,
kGenderRow,
kJobTypeRow,
kLevelOfExerciseRow,
kEmailAddressRow,
kTelephoneNumberRow
};
As I described, these cells are all very different, so displaying cells is handled like this:
// - tableView:cellForRowAtIndexPath pseudocode.
switch (indexPath.row) {
case kNameRow: // create name cell.
case kGenderRow: // create gender cell.
case kJobTypeRow: // create job type cell.
case kLevelOfExerciseRow: // create level of exercise cell.
case kEmailAddressRow: // create email address cell.
case kTelephoneNumberRow: // create telephone number cell.
}
And interacting with cells is handled similarly:
// - tableView:didSelectRowAtIndexPath pseudocode.
switch (indexPath.row) {
case kNameRow: // do name-specific stuff.
case kGenderRow: // do gender-specific stuff.
case kJobTypeRow: // do job type-specific stuff.
case kLevelOfExerciseRow: // do level of exercise-specific stuff.
case kEmailAddressRow: // do email address-specific stuff.
case kTelephoneNumberRow: // do telephone number-specific stuff.
}
This seems hugely unwieldy, and has the added of problem of not working when the table is broken down into multiple sections.
Is there a better way to do this? Are there any design patterns I would benefit from using when working with big tables of largely unrelated data?
Any tips at all are hugely appreciated.
I've become fond of implementing section controllers that pull the logic out of you UITableViewController subclass (or other hosting controller) and move them into self-contained classes.
I ended up implementing a base protocol that defines what a section controller needs to do - for me, that includes the number of rows in a section and a cell for the row (don't need the whole index path since the controller deals with a single section). I've got optional method for returning a section name and row height. That's all I've implemented so far since that's all I've actually needed.
It works for me because my individual sections tend to be homogeneous, but you could easily use the idea to return heterogeneous cells within the same section or refactor the idea to have cell type controllers instead of section controllers. In the end, my UITableViewDelegate and UITableViewDataSource methods just need to figure out which section controller to call instead of embedded all the logic within the UITableViewController subclass.
I think I got the idea from this article, but I also saw a more recent article that describes the same idea.
you might want to look at coreyfloyds project http://github.com/coreyfloyd/Generic-Heterogeneous-Table-Views i think this might have the functionality you need.
Here's my suggestion - handle each cell as a member of the view.
lol, it's been a while since I've used a table, so I could just be talkin' crap here but give it a try.
instead of an enum use:
NSThingyCell *nameRow;
NSThingyCell *genderRow;
#property IBOutlet NSThingyCell *nameRow;
#property IBOutlet NSThingyCell *genderRow;
- (IBAction) nameRowChanged:(id)sender;
- (IBAction) genderRowChanged:(id)sender;
and then instead of a table call with a switch, just wire each individual cell up in Interface Builder.
This has the added benefit of being row-independent, so if you have to put "ageRow" in between name and gender, nothing gets screwed up.
This will also get pretty big, so if your view has several tables, you may want to consider splitting those tables out into separate nibs/controllers and loading the views at run-time.
Have you ever thought of simply having an array of objects for a class which contains a UI element and some other identifiable data?
#interface settingsOption {
NSString *key;
UIView *displayElement;
}
+ (settingsOption *)optionWithKey:(NSString *)key andDisplayElement:(UIView *)displayElement;
#property (nonatomic, retain) UIView *displayElement;
#property (nonatomic, retain) NSString *key;
#end
Where the class method would look like
+ (settingsOption *)optionWithKey:(NSString *)key andDisplayElement:(UIView *)displayElement;
settingsOption *option = [[settingsOption alloc] init];
option.key = key;
option.displayElement = displayElement;
return [option autorelease];
}
Your settings class would have an array of settingsOption instances.
- (void)somewhereInMySettingsClass
mySettings = [[NSMutableArray alloc] init];
[mySettings addObject:[settingsOption optionWithKey:#"age" andDisplayElement:[UIButton buttonWithStyle:UIButtonStyleRect]]];
[mySettings addObject:...];
}
The table's cellForRowAtIndexPath would just do
[cell addSubview:[[mySettings objectAtIndex:indexPath.row] displayElement]];
You were talking about sections, though, which would add another layer to the data. This might simply be a matter of splitting mySettings into an array of arrays instead, where each array in the array is one section.
Not sure if I missed anything above. Feel free to point and poke.
You might simplify the settingsOption class further by adding more helper classes for various types of elements, e.g.
+ (settingsOption *)buttonWithKey:(NSString *)key;
+ (settingsOption *)switchWithKey:(NSString *)key;
+ (settingsOption *)pickerWithKey:(NSString *)key withDataSource:(id <UIPickerViewDataSource>)source withDelegate:(id <UIPickerViewDelegate>)delegate;
etc etc.

Objective C Object Functioning & Passing Arrays

I apologise if this has been asked before but I can't find the info I need.
Basically I want a UITableView to be populated using info from a server, similar to the SeismicXML example. I have the parser as a separate object, is it correct to alloc, init an instance of that parser & then tell RootViewController to make it's table data source a copy of the parser's array.
I can't include code because I haven't written anything yet, I'm just trying to get the design right before I start. Perhaps something like:
xmlParser = [[XMLParser alloc] init];
[xmlParser getXMLData];
// Assuming xmlParser stores results in an array called returnedArray
self.tableDataSource = xmlParser.returnedArray
Is this the best way of doing it?
No, you don't want to do this. You don't want your view controller directly accessing the array of the data-model. This would work in the technical sense but it would be fragile and likely to fail as the project scaled.
As the projects grow in complexity, you will want to increasingly wrap your data model object (in this case the xmlParser) in protective layers of methods to control and verify how the data model changes. Eventually, you will have projects with multiple views, multiple view controllers as well as information entering from both the user and URLs. You need to get into the habit of using the data-model object not just a dumb store you dump stuff into but as an active manager and verifier of your data.
In a situation like this I would have my data-model's array completely wrapped by making it a #protected or #private property. Then I would have dedicated methods for fetching or inserting data into the actual array inside the data-model class itself. No objects outside of the data-model should actually have direct access to the array or have knowledge of its indexes.
So, in this case your data-model would have something like:
- (NSString *) textForLineAtIndexPath:(NSIndexPath *) anIndexPath{
//... do bounds checking for the index
NSString *returnString=[self.privateArray objectAtIndex:anIndexPath.row];
if (returnString=='sometest'){
return returnString;
}
return #""; //return an empty string so the reciever won't nil out and crash
}
as well as a setTextForLineAtPath: method for setting the line if you need that.
The general instructional materials do not spend enough (usually none) time talking about the data-model but the data-model is actually the core of the program. It is where the actual logic of the application resides and therefore it should be one of the most complex and thoroughly tested class in your project.
A good data-model should be interface agnostic i.e. it should work with a view based interface, a web based interface or even the command line. It should neither know nor care that its data will be displayed in a tableview or any other interface element or type.
When I start a new project, the first thing I do is comment out the '[window makeKeyAndVisible];' in the app delegate. Then I create my data-model class and test it old-school by loading data and logging the outputs. Only when it works exactly how I wish it to do I then proceed to the user interface.
So, think real hard about what you want the app to do on an abstract level. Encode that logic in a custom class. Isolate the data from all direct manipulation from any other object. Verify all inputs to the data before committing.
It sounds like a lot of work and it is. It feels like overkill for a small project and in many cases it is. However, getting the habit early will pay big dividends very quickly as your apps grow in complexity.
Not quite. You want the data source to be an object that implements the UITableViewDataSource protocol; what I would do in this situation is create an object that implements that protocol and parses XML, so that you can alloc-init it, then set the data source to that object and have it update the table view as appropriate. So based off your code (and assuming you're running within the table view's controller):
XMLParserAndDataSource xpads = [[XMLParserAndDataSource alloc] init];
[xpads getXMLData];
self.tableView.dataSource = xpads;
It's probably a good idea to give this class itself a reference to an NSXMLParser object, so you can use that to parse the XML, then provide convenience methods (like getXMLData) as well as the UITableViewDataSource methods for your own use. (If you go this route, you should also make your XMLParserAndDataSource class implement the more useful of the NSXMLParser delegate methods, and use them as appropriate to update your table view.)
I'm a Mac programmer and not an iPhone programmer; but on the mac,
self.tableDataSource = xmlParser.returnedArray is not correct. You are supposed to either bind the table's content to an Array Controller (if iPhone has one?) or set the datasource outlet to your RootViewController.
In your rootview controller, you would implement the methods:
– tableView:cellForRowAtIndexPath:
– tableView:numberOfRowsInSection:
For – tableView:cellForRowAtIndexPath: you would return a UITableViewCell with the data you received from the XML parsing according to the index path like so:
UITableCell *myCell = [UITableCell new];
myCell.textLabel.text = [parsedXMLArray objectAtIndex:[indexPath indexAtPosition:indexPath.length-1]];
return myCell;
(Something people don't know is that you can use the + new class method on all NSObject subclasses which automatically call alloc/init.)
For – tableView:numberOfRowsInSection just return the count of the data array:
return parsedXMLArray.count;
Can't edit my question nor post replies, can only post my response as answer.
#TechZen: I'm somebody who tries to form analogies, helps me understand. What you're saying is something like: My original idea was like going into the file room & dumping all the originals on my desk to work on where as you suggest the object be more like an organised file clerk who will search through the data for me and only return the specific datum that I need while being the only one with direct access to that data.
Have I understood correctly?
#Tim: What if I later need the parser to get data for something which is not a table? That's why I thought to dump it into an array & let the caller decide what to do with the data. Would you suggest a second object that would supply the data in the newly required form? (Am I sort of one the right track here or way off?)

Setting ivar in objective-c from child view in the iPhone

Maybe a FAQ at this website.
I have a TableViewController that holds a form. In that form I have two fields (each in it's own cell): one to select who paid (single selection), and another to select people expense is paid for (multiple selection).
Both fields open a new TableViewController included in an UINavigationController.
Single select field (Paid By) holds an object Membership
Multiple select field (Paid For) holds an object NSMutableArray
Both vars are being sent to the new controller identically the same way:
mySingleSelectController.crSelectedMember = self.crPaidByMember;
myMultipleSelectController.crSelectedMembers = self.crSelectedMembers;
From Paid for controller I use didSelectAtIndexPath method to set a mutable array of Memberships for whom is paid:
if ([[tableView cellForRowAtIndexPath:indexPath] accessoryType] == UITableViewCellAccessoryCheckmark) {
[self.crSelectedMembers removeObject:[self.crGroupMembers objectAtIndex:indexPath.row]];
//...
}
else {
[self.crSelectedMembers addObject:[self.crGroupMembers objectAtIndex:indexPath.row]];
//...
}
So far everything goes well. An mutable array (crSelectedMembers) is perfectly set from child view.
But...
I have trouble setting Membership object.
From Paid By controller I use didSelectAtIndexPath to set Membership:
[self setCrSelectedMember:[crGroupMembers objectAtIndex:indexPath.row]];
By NSlogging crSelectedMember I get the right selected member in self, but in parent view, to which ivar is pointed, nothing is changed.
Am I doing something wrong? Cause I CAN call the method of crSelectedMembers, but I can't change the value of crSelectedMember.
If I understand your question, the most likely cause is an improper property declaration.
If you want to pass values from one object to another using each objects properties, then you need to make sure to use assign to ensure the properties in one object are pointing at the same instances as the property in the other object.
So in your topViewController you have a property:
#property (nonatomic,retain) NSString crSelectedMember;
Then in your child view controllers you have:
#property (nonatomic,assign) NSString crSelectedMember;
This forces the value into the exact object in the parent controller.
However, this is a very fragile way to pass data between viewControllers. As your app becomes more complicated, it will be impossible to track all the passed data. (Worse, if you run into memory limitations, the parent view controller may unload and not even exist when you try to pass data to it.)
Instead, you should have a single custom object devoted to holding your data. Each view controller should query that object for the data it needs and should write any changes back to that object. The view controllers never communicate directly. This technique allows you to control the data in one specific location instead of spreading it out all over your code and it scales well. You can add an arbitrary number of view controllers to you app without having to worry about tying them all together.
See this post for details: iPhone: How to Pass Data Between Several Viewcontrollers in a Tabbar App

Why does Core Data create NSManagedObject's with properties but no accompanying instance variable?

Exactly as the title says, why does Core Data create instances of NSManagedObject with properties for each entity's attribute, but no accompanying instance variable? The problem is, I would like to use some of my 'Entities' in a typical alloc/init style fashion in some parts of my code. Not using Core Data's fetching/context to create/store.
I suppose I could stay without the instance variables, but would it hurt to add them? Can I also change the #dynamic to #synthesize so I can use KVC on my properties/ivars?
Perhaps I'm completely wrong in trying to use one of my Core Data entities in some parts of my code without using the core data APIs. Still rather new to it to understand when I can go my own route.
The reason it doesn't use ivars is that that data isn't there. When you load an object it may not fault in all of its data. ivars are just variables, if you and write to them NSManagedObject doesn't have a chance to fault in the value if it has not been loaded yet. Via accessors NSManagedObject has a choke point that allows it to read the data off the disk if it has not been faulted in yet, which means your object graph can be brought in lazily. Otherwise you would need to bring in every connected object.
Because of that, you can't just add ivars, they won't have the values you want. Likewise you can't change from #dynamic to #synthesized, things will not behave correctly. The dynamic property implementations provided by CoreData are completely KVC and KVO compliant anyway.
If you want to access the values without tripping KVO or KVC you can access the "primitive" values. You do that in one of two ways. You use primitiveValueForKey: and setPrimitive:valueForKey: or you can just declare the primitive and let CD provide dynamic implementations (from the documentation)
#interface Department : NSManagedObject
{
}
#property(nonatomic, retain) NSString *name;
#end
#interface Department (PrimitiveAccessors)
- (NSString *)primitiveName;
- (void)setPrimitiveName:(NSString *)newName;
#end
You don't need instance variables with Core Data. A managed object stores these values elsewhere.
I've never gotten very far with Core Data, but if I remember correctly, you're supposed to use primitiveValueForKey: and setPrimitiveValue:forKey: to perform these accesses from your accessor methods. (Outside of your accessors, you should be using either the accessors themselves or valueForKey:/setValue:forKey: instead.)