Three20 : how to pass a class of objects between 2 views - iphone

I have a TTableView. The items in this table a mapped to an url, so that when I click on an item, another view appear with informations about this item.
All these informations are attributes of a class. So, how can I build my TTableTextItem URL in order to transmit the class containing informations to the view responsible for the display of these informations ?
Thanks in advance.

One way of doing it is to use a TTURLAction. When the user selects a row in your table, which will call your didSelectObject (of TTTableViewController) method, extract the object or set of objects you want to pass and build a TTURLAction like this:
TTURLAction *action = [[[TTURLAction actionWithURLPath:#"tt://showUser"]
applyQuery:[NSDictionary dictionaryWithObject:user forKey:#"kParameterUser"]]
applyAnimated:YES];
Then open the action:
[[TTNavigator navigator] openURLAction:action];
The controller you want to open as a result of this action should be registered in your TTURLMap and should have a constructor thus:
- (id) initWithNavigatorURL:(NSURL*)URL query:(NSDictionary*)query {
self = [super init];
if (self != nil) {
self.user = [query objectForKey:kParameterUser];
}
return self;
}
I tend to create categories on classes for objects I want to be able to open another controller and display themselves.

The big problem with directly using TTURLAction is that you can't really use them with TTTableViewItem. The only way to really do it is override -didSelectObject:atIndexPath: and build your custom TTURLAction with your desired object in the query dictionary. But this breaks the nice separation of Model and View Controller, and gets complicated once you have multiple objects to pass.
Instead of this, I've been using a small category which automatically takes the userInfo property of the table item (which I set to whatever I need), and automatically adds it as a URL parameter.
And then you use this to retrieve it in your mapped view controller.
- (id)initWithNavigatorURL:(NSURL *)URL query:(NSDictionary *)query {
if (self = [self initWithNibName:nil bundle:nil]) {
id myPassedObject = [query objectForKey:#"__userInfo__"];
// do the rest of your initlization
}
return self;
}
You can download it as a GitHub Gist here. The code is also below. We're considering merging this into the main branch at some point.
TTTableViewDelegate+URLAdditions.h
#interface TTTableViewDelegate(URLAdditions)
#end
TTTableViewDelegate+URLAdditions.m
#import "TTTableViewDelegate+URLAdditions.h"
#implementation TTTableViewDelegate(URLAdditions)
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
id<TTTableViewDataSource> dataSource = (id<TTTableViewDataSource>)tableView.dataSource;
id object = [dataSource tableView:tableView objectForRowAtIndexPath:indexPath];
// Added section to automatically wrap up any TTTableItem userInfo objects. If it is a dictionary, it gets sent directly
// If it is not, it is put in a dictionary and sent as they __userInfo__ key
if( [object isKindOfClass:[TTTableLinkedItem class]] ) {
TTTableLinkedItem* item = object;
if( item.URL && [_controller shouldOpenURL:item.URL] ) {
// If the TTTableItem has userInfo, wrap it up and send it along to the URL
if( item.userInfo ) {
NSDictionary *userInfoDict;
// If userInfo is a dictionary, pass it along else create a dictionary
if( [item.userInfo isKindOfClass:[NSDictionary class]] ) {
userInfoDict = item.userInfo;
} else {
userInfoDict = [NSDictionary dictionaryWithObject:item.userInfo forKey:#"__userInfo__"];
}
[[TTNavigator navigator] openURLAction:[[[TTURLAction actionWithURLPath:item.URL]
applyQuery:userInfoDict]
applyAnimated:YES]];
} else {
TTOpenURL( item.URL );
}
}
if( [object isKindOfClass:[TTTableButton class]] ) {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
else if( [object isKindOfClass:[TTTableMoreButton class]] ) {
TTTableMoreButton* moreLink = (TTTableMoreButton*)object;
moreLink.isLoading = YES;
TTTableMoreButtonCell* cell
= (TTTableMoreButtonCell*)[tableView cellForRowAtIndexPath:indexPath];
cell.animating = YES;
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if( moreLink.model )
[moreLink.model load:TTURLRequestCachePolicyDefault more:YES];
else
[_controller.model load:TTURLRequestCachePolicyDefault more:YES];
}
}
[_controller didSelectObject:object atIndexPath:indexPath];
}
#end

I think the documentation on the official website does describe very clearly the navigation scheme for Three20. Your question is the very common task of any application, and Three20 provides powerful support for that.

I have little fix this awesome code :)
as posted version strip callback from TTTableButton.
Correction is:
if( [object isKindOfClass:[TTTableButton class]] ) {
if (item.delegate && item.selector) {
[item.delegate performSelector:item.selector withObject:object];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}

Related

Comparing two UITextFields

I have no idea why this isn't working and I feel it's something really very stupid.. I'm trying to compare two textfields (like a confirming the password type of thing) but it just isn't working!! Ive tried several different options and none of them work. I've tried:
if (passwordCheckField1.text == passwordCheckField2.text) {
[self performSegueWithIdentifier:#"segue4" sender:nil];
}
I've tried:
NSString *retrivedPasswordCheckField1 = passwordCheckField1.text;
NSString *retrivedPasswordCheckField2 = passwordCheckField2.text;
if ([retrivedPasswordCheckField1 isEqualToString:retrivedPasswordCheckField2]) {
[self performSegueWithIdentifier:#"segue4" sender:nil];
}
I've tried:
if ([passwordCheckField1.text isEqualToString:retrivedPasswordCheckField2.text]) {
[self performSegueWithIdentifier:#"segue4" sender:nil];
}
I've implement the <UITextFieldDelegate> in the .h and i've also set the delegates for both fields, (passwordCheckField1.delegate = self;
passwordCheckField2.delegate = self;)
What am I missing???? Thanks for your help!
EDIT
I probably should mention that I'm using a Navigation Controller, with several child view controllers. Each contains a text field, so when the view controller about the first password comes up, the user enters it and then clicks next taking them to the confirm password view controller. All are linked to the same class files.
This should work.
NSString *password = passwordCheckField1.text;
NSString *confirmPassword = passwordCheckField2.text;
NSLog(#"password: '%#'", password);
NSLog(#"confirmPassword: '%#'", confirmPassword);
if([password isEqualToString: confirmPassword]) {
NSLog(#"Match");
} else {
NSLog(#"Not Match");
}
if (passwordCheckField1.text == passwordCheckField2.text) {
[self performSegueWithIdentifier:#"segue4" sender:nil];
}
The above code doesn't compare two string values. It actually compares two pointers value.So it is not going to be useful for you.
but code
if ([retrivedPasswordCheckField1 isEqualToString:retrivedPasswordCheckField2]) {
[self performSegueWithIdentifier:#"segue4" sender:nil];
}
should have compared the string values. May be string your values are not same or nil. Have you printed the passwordCheckField1.text and passwordCheckField2.text.What is the values for two strings ?
chk your values.
if ([passwordCheckField1.text isEqualToString:passwordCheckField2.text]) {
//Match it's working for me
}
try this
if([[passwordCheckField1 text] isEqualToString:[passwordCheckField2 text]])
{
[self performSegueWithIdentifier:#"segue4" sender:nil];
}
else
{
//not matched
}
As a precautionary measure, try triming both the password strings for leading and trailing spaces before comparing like
NSString *pwd1 = [pwdTxtField1.text stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#" "]];

How to recognize File or Folder from dropbox APi

Hello I am using dropbox api and displaying meta data from dropbox account..
I want to differentiate files and folders from loaded data..because I want to show next level if there is folder and if there is file I don't want to show next View
my code to load data
- (void)restClient:(DBRestClient*)client loadedMetadata:(DBMetadata*)metadata {
[self.metaArray release];
self.metaArray = [[NSMutableArray alloc]init ];
for (DBMetadata *child in metadata.contents) {
NSString *folderName = [[child.path pathComponents] lastObject];
[self.metaArray addObject:folderName];
}
[self.tableView reloadData];
[self.activityIndicator stopAnimating];
}
According to the Dropbox Developer Docs the metadata includes a property called is_dir which should allow you to determine whether the particular item is a directory or not.
Looking at the header of DBMetaData it is indeed exposed as a property
#property (nonatomic, readonly) BOOL isDirectory;
So you can just do a simple test like so
- (void)restClient:(DBRestClient*)client loadedMetadata:(DBMetadata*)metadata
{
if (metadata.isDirectory) {
// handle directory here
} else {
// handle file here
}
}
With regards pushing views based on whether or not an entry is a directory, you could subclass UITableViewCell and add an isDirectory property. Instead of adding just the name to self.metaArray you could add a dictionary containing both the name and the value of isDirectory. Then in your table view datasource where you populate the cells you'd set the isDirectory property of the UITableViewCell based on the same property in the appropriate dictionary from the array. Finally, in the table view delegate method
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
you can get the selected cell using the indexPath and then test the isDirectory property and based on it's value take the appropriate action.
Hope this helps.
Using the API V2 of Dropbox with the Dropbox SDK is:
DropboxClient *client = [DropboxClientsManager authorizedClient];
[[client.filesRoutes listFolder:path]
response:^(DBFILESListFolderResult *result, DBFILESListFolderError *routeError, DBRequestError *error) {
if (result) {
for (DBFILESMetadata *entry in result.entries) {
if ([entry isKindOfClass:[DBFILESFileMetadata class]]) {
DBFILESFileMetadata *fileMetadata = (DBFILESFileMetadata *)entry;
NSLog(#"File: %#", fileMetadata.name);
} else if ([entry isKindOfClass:[DBFILESFolderMetadata class]]) {
DBFILESFolderMetadata *folderMetadata = (DBFILESFolderMetadata *)entry;
NSLog(#"Folder: %#", folderMetadata.name);
}
}
}

Need some help cleaning up a bit of my code

I have 5 buttons named opt1, opt2, opt3, etc. If I want to hide/show/do something to them, could I create a simple for statement instead of doing opt1.hidden = YES, opt2.hidden....? If so, what would it look like? Thanks
EDIT: This is the code I am trying to clean:
opt1.hidden = NO;
opt2.hidden = NO;
opt3.hidden = NO;
opt4.hidden = NO;
opt5.hidden = NO;
Is there a simple for statement I could use that would hide all of them without having to hide each one manually since the only difference in their name is the number at the end? It doesn't seem like a lot of buttons but I will have to add lots more soon so I would rather not have 20 lines of code just to hide a bunch of buttons.
You can do this.
NSArray *myButtons = [NSArray arrayWithObjects:b1, b2, b3, b4, nil];
for (UIButton *button in myButtons)
{
button.hidden = YES;
}
David's suggestion is a good one if you know and have a pointer to all your buttons.
An alternative would be to loop through all of your UIView subviews and hide buttons as you find them:
for (id subview in self.view)
{
if ([subview isKindOfClass:[UIButton class]])
[(UIButton*)subview setHidden:YES];
}
If you want to be selective with the buttons you are hiding, simply add a specific tag to it upon creation (i.e. button1.tag = 999) and use:
for (id subview in self.view)
{
if ([subview isKindOfClass:[UIButton class]] && subview.tag == 999)
[(UIButton*)subview setHidden:YES];
}
David's answer is probably best, and is how I would do it for a small number of controls. You could also use KVC.
NSArray *myButtons = [NSArray arrayWithObjects:#"b1", #"b2", #"b3", #"b4", nil];
for (NSString *str in myButtons)
{
id cont = [self valueForKey:key] ;
if ([cont isKindOfClass:[UIButton class]]) {
[cont setHidden:YES] ;
}
}
The reason I am showing you this method is it can be used to create "bindings" between a DB and your controls. Imagine if the myButtons array contained the names of DB fields. You could then name your UI controls in your controller with the same name. Then all you need is a simple for loop, and maybe some isKindOfClass test, to move all the control data into your DB. Here is an example from one of my projects.
NSArray *fn = [AOSConfig sharedInstance].fieldNames ;
for (NSString* name in fn) {
#try {
id uifield = [self valueForKey:name] ;
if ([cont isKindOfClass:[UITextField class]]) {
[aosShotData setValue:[uifield valueForKey:#"text"] forKey:name]
}
}
#catch(NSException *e) {
}
}
That's it to save all the text data to a CoreData managed object. You would need to get creative if you need various data types. If the DB is complex in terms of the data type to control mapping it may be better to just write it brute force.

Navigating between UITextFields within UITableViewCell

In the Contacts app's add/edit view, if you hit the 'Return' key in a field, you cycle through each of the UITextFields. These fields seem to be inside a UITableViewCell. What's a good way to do this?
When I have a series of UITextFields not inside a Table, I can invoke
[self.view viewWithTag:tag] to get a list of the views and the use that to cycle through them. But within a table, I only get one view and I'm not sure how to convert this over.
-(BOOL)textFieldShouldReturn:(UITextField *)textField {
// Find the next entry field
for (UIView *view in [self entryFields]) {
if (view.tag == (textField.tag + 1)) {
[view becomeFirstResponder];
break;
}
}
return NO;
}
/*
Returns an array of all data entry fields in the view.
Fields are ordered by tag, and only fields with tag > 0 are included.
Returned fields are guaranteed to be a subclass of UIResponder.
From: http://iphoneincubator.com/blog/tag/uitextfield
*/
- (NSArray *)entryFields {
if (!entryFields) {
self.entryFields = [[NSMutableArray alloc] init];
NSInteger tag = 1;
UIView *aView;
while (aView = [self.view viewWithTag:tag]) {
if (aView && [[aView class] isSubclassOfClass:[UIResponder class]]) {
[entryFields addObject:aView];
}
tag++;
}
}
return entryFields;
}
Just discovered this while browsing for something myself - sorry for the delay.
If you haven't resolved the issue, you could create an array of uitextfield objects in the same order presented on screen. As you create the table, set the .tag property of the text field. Within the text field delegate method - read the tag, increment by 1 (to move to the next field), and issue the becomefirstresponder call.
You could also look at the textFieldDidEndEditing delegate method.

Possible View Caching problem?

I'm building an iphone app and I've got a table view with some textfields inside the cells, the content of the fields is set in viewWillAppear (its a grouped TableView w/ 3 fields that are always the same). The content of the text fields is retrieved from getter methods that return values from various class variables.
The problem I'm having is the getter seems to be returning the original value, not the value that is modified by the setter method. The class variable is an NSMutableString. Is it possible the view is caching the method call?
//header file
#implementation ManageWorkoutViewController : UIViewController {
NSMutableString *workoutDifficulty;
}
-(void)setWorkoutDifficulty:(NSString *)value;
-(NSString *)getWorkoutDifficulty;
#end
//implementation file
-(NSString *)getWorkoutDifficulty {
if (nil == workoutDifficulty) {
workoutDifficulty = [NSMutableString stringWithString:#"Easy"];
}
NSLog(#"getter: Returning workoutDifficulty as: %#", workoutDifficulty);
return workoutDifficulty;
} //end getWorkoutDifficulty
-(void)setWorkoutDifficulty:(NSString *)value {
workoutDifficulty = [NSString stringWithFormat:#"%d", value];
NSLog(#"setter: workoutDifficulty set as: %#", workoutDifficulty);
}//end setWorkoutDifficulty
//elsewhere in the implementation another table view is
//pushed onto the nav controller to allow the user to pick
//the difficulty. The initial value comes from the getter
workoutDifficultyController.title = #"Workout Difficulty";
[workoutDifficultyController setOriginalDifficulty:[self getWorkoutDifficulty]];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[(UINavigationController *)self.parentViewController pushViewController:workoutDifficultyController
animated:YES];
//then in that workoutDifficultyController it calls back into the first controller to set the selected value:
[manageWorkoutController setWorkoutDifficulty:selectedDifficulty];
You've got many issues here. First, you're creating your accessors incorrectly. The problem that's particularly causing you trouble is this line:
workoutDifficulty = [NSString stringWithFormat:#"%d", value];
value is an NSString here. You should be receiving a warning about this. I believe "Typecheck Calls to printf/scanf" is turned on by default, and should catch this. workoutDifficulty is being set to some random number (probably taken from the first 4 bytes of value).
Here is what you probably meant. I would probably switch workoutDifficulty to an enum, but I'm keeping it an NSString for consistency with your code. I'm also doing this without properties because you did, but I would use a property here.
//header file
#implementation ManageWorkoutViewController : UIViewController {
NSString *_workoutDifficulty;
}
-(void)setWorkoutDifficulty:(NSString *)value;
-(NSString *)workoutDifficulty; // NOTE: Name change. "getWorkoutDifficulty" is incorrect.
#end
//implementation file
-(NSString *)workoutDifficulty {
if (nil == workoutDifficulty) {
_workoutDifficulty = [#"Easy" retain];
}
NSLog(#"getter: Returning workoutDifficulty as: %#", _workoutDifficulty);
return _workoutDifficulty;
} //end workoutDifficulty
-(void)setWorkoutDifficulty:(NSString *)value {
[value retain];
[_workoutDifficulty release];
_workoutDifficulty = value;
NSLog(#"setter: workoutDifficulty set as: %#", _workoutDifficulty);
}//end setWorkoutDifficulty
You have to retain workoutDifficulty whenever you set it to a new value (and release the old value).