first post. Been an iPhone developer intern for about five weeks now. I've read a lot of introductory Apress material, but please take it easy if I make some vocabulary violations. So far I've been able to find answers by searching and lurking. However I now have a task for which I can find little relevant information.
My iPhone application currently uses a rigid view hierarchy for selection of items. The MainViewController links to (err, Subviews?) a TableView for selecting any from a list of factories. Selecting a factory then loads a TableView for various statistics about that factory. The data to populate these tables loads from a remote JSON server by http.
I would like to have an XML definition of the view hierarchy on the remote server from which the application dynamically constructs the view structure. In this way the view structure is not hard-coded into the client (the iPhone ViewControllers/nibs) and offers more flexibility for reorganizing the content server-side.
Is this possible, and has anybody accomplished this? Most relevant answer was Dynamic UI in iphone, however upon reading I feel Apple's guide to Serializing/Archiving departs quickly from what I am trying to do. Can somebody explain its relevance or point to another resource?
Yes, it is entirely possible, and I (as well as others) have done something similar. Essentially, you will have to write a couple classes:
One is a Controller class to parse the XML to get the content you need upon download into a format you can use in a Model class. There are a number of OSS libraries out there, but don't use Cocoa's XML classes on the iPhone because they are just too slow when parsing. Be sure to double-check the license that comes with the OSS library so that you don't run afoul of GPL and the like.
The second class you'll need is one that will translate your model into a view hierarchy and place it on screen on the fly as the user moves through the content. This is to implement as individual steps, but the challenge is managing the content placement workflow.
The content noted in your question does depart a bit from the core task at hand but it can still be relevant to your interests. At the very least it may give you an idea on how to approach certain aspects of your project.
Here's a follow-up. I have accomplished what I wanted to do.
I have a ListViewController class and a Repository model/object. To parse XML, I used XPathQuery, Matt Gallagher's adaptation of libxml2 for Cocoa (http://cocoawithlove.com/2008/10/using-libxml2-for-parsing-and-xpath.html).
The ListViewController tracks the current XML path. When a cell in the table view is selected, the corresponding path is concatenated to the existing path. The ListViewController creates and pushes another instance of itself and PerformXMLPathQuery() returns an array to populate the table view cells. The Repository holds an NSMutableData object and an NSString with current path. Both are required for XPath Query:
NSArray *PerformXMLXPathQuery(NSData *document, NSString *query);
PerformXMLPathQuery does a lot of back work for you. This is how I used it to get what I wanted from the XML:
-(NSArray*)getListFromRepositoryWithPath:(NSString *)path {
// Get the nodes referred to by the path
NSArray *nodes = PerformXMLXPathQuery(repository.data, path);
// Build an array with the node names
NSMutableArray* sublist = [[NSMutableArray alloc] init];
for (NSDictionary* node in nodes) {
for (id key in node) {
if ([key isEqualToString:#"nodeChildArray"]) {
NSArray* subNodes = [node objectForKey:#"nodeChildArray"];
for (NSDictionary* subNode in subNodes) {
for (id subKey in node) {
if ([subKey isEqualToString:#"nodeName"]) {
[sublist addObject:[subNode objectForKey:subKey]];
}
}
}
}
}
// Ignore duplicate entries in the data
if ([sublist count] > 0) {
return [NSArray arrayWithArray:sublist];
}
}
return [NSArray arrayWithArray:sublist];
}
When a row is selected, I used didSelectRowAtIndexPath to prepare the next path and view controller:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *nextXpathQueryString = [self.xpathQueryString copy];
// Build the path string for the next view's XML path query
if ([[nextXpathQueryString lastPathComponent] isEqualToString:#"*"]) {
nextXpathQueryString = [nextXpathQueryString stringByDeletingLastPathComponent];
nextXpathQueryString = [nextXpathQueryString stringByAppendingPathComponent:#ROOTNAME];
}
nextXpathQueryString = [nextXpathQueryString stringByAppendingPathComponent:[repository.currentListToDisplay objectAtIndex:indexPath.row]];
// Navigation logic. Create and push another view controller.
ListViewController *detail = [[ListViewController alloc] init];
// Populate the new ViewController
[detail setRepository:repository];
[detail setTitle:nextXpathQueryString];
[detail setXpathQueryString:nextXpathQueryString];
[nextXpathQueryString release];
UIBarButtonItem *doneButton = [[self navigationItem] rightBarButtonItem];
[[detail navigationItem] setRightBarButtonItem:doneButton animated:YES];
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:detail animated:YES];
[detail release];
}
The cool thing is each instance of ListViewController keeps its own path string and content array. No fooling around with nested tree objects or pointers.
Essentially I have a flexible XML tree browser for the iPhone. Thanks Stack Overflow.
Related
When I click a button, a UIAlertView prompts the user to type a name. This name is then created as a new 'Customer' object and inserted into a mutable array.
There is a separate mutable array called 'CustListByName', which stores a list of all names.
The problem im having is that when adding a second or third name, the app crashes. Sometimes it happens on the second try, other times on the third try. There is no information given in the debugger except for (lldb). The program reports EXC_BAD_ACCESS and then it dumps me to a screen with a bunch of assembly code.
The crash is happening in these lines of code:
Essentially, it clears the array of names and then repopulates it based upon the object array. I've studied in step by step with a breakpoint but everything seems correct up until the point of crash. It is also confusing why this happens on the second or third try, never the first.
[custListByName removeAllObjects];
for (Customer *object in custListByObject) {
[custListByName addObject:object->name];
}
Here is the code where a customer is created and inserted everytime the new customer button is clicked:
Customer *tempCust = [[Customer alloc] init];
tempCust->name =[[alertView textFieldAtIndex:0] text];
[custListByObject addObject:tempCust];
[tempCust release];
I would really appreciate help with this, thanks!
What I suspect is happening is that the UIPickerView is attempting to load a row using information from your customer array after you have already cleared it, and before you repopulate it. This would cause a bad access error.
What you may consider doing instead, is keeping two arrays, an NSMutableArray for loading the customers, and an NSArray as the actual data source for the UIPickerView. Then right before you reload the UIPickerView, you say:
dataSourceArray = [loadingArray copy];
[pickView reloadAllComponents];
Hopefully this helps.
Edit:
Here's what your updated code would look like if your loading array was called loadingCustListByName:
[loadingCustListByName removeAllObjects];
for (Customer *object in custListByObject) {
[loadingCustListByName addObject:object->name];
}
custListByName = [loadingCustListByName copy];
[pickView reloadAllComponents];
Doing this will ensure that the UIPickerView's datasource array always matches up with the number of rows it thinks it has.
I already have a tableView with data in it. IF you tap a cell/row it pushes to an edit type of view. Is there anyway to edit core data's data other than: By edit, i mean i already have data inserted into my context. I have loaded my data into my view, the user can change the existing data, and re save it.
.h
//Below is the entity/entity's class name 'Amm'
Amm *amm;
.m
-(IBAction)save
{
[self.amm setValue:self.nameField.text forKey:#"name"];
[self.amm setValue:self.nicknameField.text forKey:#"nickname"];
[self.navigationController popViewControllerAnimated:YES];
NSError *error;
if (![self.managedObjectContext save:&error]) {
//Handle Error
}
}
I want this code to work, however the design pattern of my app isnt allowing this code to work for me as it does in other parts of my app. Thank you very much for any and all help!
I assume from what you've said you have:
A table view listing your managed objects
A view where you can edit the values of a managed object
A save button bound to the save method
What's the actual issue? I'm assuming when you tap save that:
The values in self.nameField.text isn't setting self.amm.name
The values in self.nicknameField.text isn't setting self.amm.nickname
Is that right? If so perhaps try the following code to set the managed object values:
self.amm.name = self.nameField.text
self.amm.nickname = self.nicknameField.text
If that's not the issue and you are actually setting the managed object values properly, is it that you just need to refresh the table-view? Perhaps use some NSLog commands to log every step of the applications progress.
I select a row from a TableView which gets informations from a Webservice. Everything is loaded: A search, then we get results and then choose one.
But after choosing one, I can set the data to the other view and make another web service to Work.. I'm okay with that.
The problem is to show the new table containing the results of the new web service..
I get this after choosing a row from the last table View:
2012-04-30 13:54:29.687 +%¨*%¨%¨*[6759:f803] JSON(
"Name Gerald",
"",
4,
FR,
""
)
I made this with this:
NSString *nom = [olo objectForKey:#"name"];
NSString *hd = [olo objectForKey:#"24_7"];
NSString *passenger = [olo objectForKey:#"passengers"];
NSString *country = [olo objectForKey:#"country"];
NSString *special = [olo objectForKey:#"special_trip"];
_jsonOlo = [[NSMutableArray alloc] initWithObjects:nom, hd, passenger, country, special, nil];
But when I set the table I get a black screen :
1st:
http://tinypic.com/r/2uzpc20/6
2:
http://tinypic.com/r/287qeeq/6
I have the same code as the last tableView which sends to the results but I get Black screen.
If the two tableviews are different and you are not simply changing the datasource of the same table then your view might be loading before you have got all the data from web service .I don't know, which framework you are using for json parsing but there should be a delegate which signifies the end of getting results from web service . Parse the data there and then send it to the tableview in viewDiDLoad method.
You might not have connected the tableview properly in your XIB or you might not have added subview to main view or u must have allocated it when the webservice has finished retrieving data but u might have touched it before.
You can do one thing: make the cell selection disable till u are ready with all the data.
I'm building a web-app which is communicating with a database. I want the web-app to download all info from my database and then save it locally so it can be accessed and edited (even if the user is offline) locally and then synced back to my database (when the user is online).
Every row in my database consists of 2 strings and 3 integers and I've got around 1000-1500 rows in my database (that might give you and idea about the size of my db).
So which is the best way to go? Is it possible to cache this kind of data on the users device somehow? Like retrieving the database via JSON and than storing it locally in a javascript array which the user can access and work on (as mentioned: the later part should be able to work even if the user is offline). All input on this is greatly appreciated. Am I at least thinking in the right direction here?
EDIT: I just want to clarify that I'm working with a web app. An app devoloped with HTML, CSS and Javascript. I'm not doing a native objective C iOS app.
make a class of NSObject
#interface abc : NSObject<NSCoding>
#property..... // add properties that you want to save
-(void)initWithDictionary:(NSMutableDictionary *)dictionary; // override init by passing dictionary with values of your properties
#end
in abc.m file
implement these functions
-(void)initWithDictionary:(NSMutableDictionary *)dictionary
{
yourProperty=[dictionary valueForKey:#"propertyKey"];
}
-(void) encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:yourProperty forKey:#"propertyKey"];
}
-(id) initWithCoder:(NSCoder *)aDecoder{
self =[super init];
if(self){
[self setyourProperty:[aDecoder decodeObjectForKey:#"yourProperty"]];
}
return self;
}
now make a shared class that can be accessed from anywhere
#interface xyz : NSObject
{
NSMutableArray *allItems;
}
+(xyz *) sharedStore;
-(NSMutableArray *)allItems;
-(abc *) createItem:(NSMutableDictionary *)dictionary;
-(NSString *) saveItemPath;
-(BOOL)saveChanges;
now in xyz.m implement these functions
-(id)init{
self=[super init];
if(self){
NSString *path=[self saveItemPath];
allItems=[NSKeyedUnarchiver unarchiveObjectWithFile:path];
if(!allItems){
allItems=[[NSMutableArray alloc]init] ;//]WithObjects:#"No more item in store", nil];
}
}
return self;
}
+(xyz *)sharedStore{
static xyz *sharedStore=nil;
if(!sharedStore){
sharedStore=[[super allocWithZone:nil]init];
}
return sharedStore;
}
+(id)allocWithZone:(NSZone *)zone{
return [self sharedStore];
}
-(NSArray *)allItems{
//[allItems insertObject:#"No more items are in store" atIndex:[allItems count]];
return allItems;
}
-(abc *) createItem:(NSMutableDictionary *)dictionary
{
abc *p=[[abc alloc] init];
[p initWithDictionary:dictionary];
[allItems insertObject:p atIndex:0];
return p;
}
-(NSString *) saveItemPath
{
NSArray *documentdirectories=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString * documentDirectory=[documentdirectories objectAtIndex:0];
return [documentDirectory stringByAppendingPathComponent:#"items.archive"];
}
-(BOOL) saveChanges
{
NSString *path=[self saveItemPath];
return [NSKeyedArchiver archiveRootObject:allItems toFile:path];
}
now you can use the global variable like this
[xyz sharedStore]allItems]
now do one more thing, add this line to application did enter background
[xyz sharedStore]saveChanges];
The best way to store the data specially if you are using web technologies to develop your mobile app is to use SQLite.You can store, retrieve your data in offline or online mode. Also another advantage is you can port your database to any mobile device or Desktop app if you decided to go native.
If you have more question or require help contact me and i can give you more info of how to use SQLite with web technologies?
If you want to save time on designing a Web GUI for your mobile app you can use Mobile web framework, Which speed up development time and allows you to access some native API's on the device. I recommend Sencha: http://www.sencha.com/products/touch/ (Really nice framework base on Html5, JS, CSS3)
You should use Core Data (which uses SQLite).
You need to look into persistence, and the best option for that is Core Data. Aside from persistence, you get a lot of other great benefits that I and others have outlined here and here, for instance. As mentioned, you can use SQLite as the backing store for the app, and then you can access the object representations of the entries however you wish. If you're dealing with syncing, you may also want to look into iCloud, which you can find information about here.
I have searched throughout SO on this topic for iPhone and everything points to the WWDC 2010 coverage, so yes I'm aware of that. But can anyone point me to a more detailed resource from where I can learn how to build a robust system for presenting different user interfaces on an app depending on the data that I'm presenting? I am fetching the data in a JSON format and my UI needs to vary depending on what I get out of the JSON parser.
Any books or online resources that give a detailed look into this topic?
Thanks!
I recently had the same issue in one of my apps (navigation style) and the way I solved it is fairly simple.
I had a user_type flag in my JSON response, and depending on that flag I would push a different view controller.
For example, given my JSON response is stored in a NSMutableDictionary called "response"
if ([response objectForKey:#"account_type"] == 1) {
/*
initialize user_type 1 viewController
*/
[self.navigationController pushViewController:userType1ViewController animated:YES];
else if ([response objectForKey:#"account_type"] == 2) {
/*
initialize user_type 2 viewController
*/
[self.navigationController pushViewController:userType2ViewController animated:YES];
}
You can have as many different user_types as you want.
Editing after clarification in comments
You will likely be manually drawing these views.
I would subclass UIView for each of the different question types you have listed (including properties for common elements like question title, choices, etc). Assuming that questions with the same question type will have the same layout.
Then you can cycle through your JSON response (once it's in an array or dictionary) and lay it out.
If it's a multiple choice question, make a new multipleChoiceQuestion view, add its properties, then addSubview to the main feed view. Then, for your next view, you will need to set the frame to be:
nextQuestion.frame = CGRectMake(0, 0 + firstQuestion.frame.size.height, height, width);
This will ensure that your second question is drawn right below the first question.