I am a newbie in iPhone application development.
I am developing an iPad application. It contains a menu bar on top, clicking on which retrieves a sub view. The sub view consists of UIPickerView. Upon selecting a row from UIPickerView, navigates to another UIViewController.
The UIPickerView methods are written in a separate class (As this functionality comes throughout the app, I made it a general one). So,
[self.navigationController pushViewController:controller animated:YES];
will not work for me!
I was able to get the name of the class to be pushed (It changes according to the selection made). Is there any way I can do it?
Thanks In Advance :-)
I guess what you really want is to create an object from a classname
The simple answer is
[[NSClassFromString(className) alloc] init...]
For a more thorough answer you should look at Create object from NSString of class name in Objective-C
You can use delegate method (delegate methods allows communication between objects) to implement this scenario
For example in your UIPicker(.h) class define a delegate protocol as follows
#protocol pickerProtocol;
#interface MyPicker : NSObject {
id <pickerProtocol> pickerDelegate;
}
#property(nonatomic,retain) id <pickerProtocol> pickerDelegate;
#end
#protocol pickerProtocol
- (void) pushViewController;
#end
And call this delegate method when selecting a row from UIPickerView
[pickerDelegate pushViewController];
Then in all view controller that uses picker write the implementation of the delegate method
- (void) pushViewController {
[self.navigationController pushViewController:controller animated:YES];
}
dont for get to set the delegate as follows
MyPicker *picker = [MyPicker alloc]init];
picker.pickerDelegate = self;
Related
I want to write a category on UINavigationItem to make changes to barBackButtonItem across my entire app.
From what I have been told in the comments here ( Change BackBarButtonItem for All UIViewControllers? ), I should "override backBarButtonItem in it, then your method will be called whenever their back bar button item is called for." - but how do I know what method to override? I have looked at the UINavigationItem documentation, and there are multiple methods used for initializing a backBarButtonItem. How do I determine which method I should override in my category?
If you want to override backBarButtonItem, override backBarButtonItem. There is one and only one method called backBarButtonItem. ObjC methods are uniquely determined by their name.
You'd do it like so:
#implementation UINavigationItem (MyCategory)
- (UIBarButtonItem *)backBarButtonItem
{
return [[UIBarButtonItem alloc] initWithTitle:nil style:UIBarButtonItemStylePlain target:nil action:nil]
}
#end
I'm not saying it's a good idea, but that's how you'd do it.
You want a subclass of UIViewController instead of a catagory.
For example:
#interface CustomViewController : UIViewController
#end
#implementation CustomViewController
-(void) viewDidLoad {
[super viewDidLoad];
self.navigationItem.backBarButtonItem.title = #"";
}
#end
Now you just need to use the CustomViewController class for your view controllers, and they will all have the changes applied to them.
If you're doing this programatically, then you'll just want to change the superclass of the view controllers:
From this.... to this...
If you're using storyboards, you'll want to change the superclass from within the Identity Inspector...
I am looking to replace the back button in the UINavigationController throughout my application. My requirements is that this back button be defined in one XIB and if possible, the code to set it is in one place.
I have seen various methods that set the property self.navigationItem.backBarButtonItem to be a UIBarButtomItem with the custom button as it's view, e.g. [[UIBarButtonItem alloc] initWithCustomView:myButton];
My first thought was to create a global category (not sure if that's the term, I'm new to Objective-C as you might have guessed) that implements 'ViewDidLoad' for all my UINavigationControllers, and setting this property. My problem is loading the XIB to this button that I create at runtime.
Does anyone have a suggestion on a neat way of doing this (I guess it must be a common thing to do, and I can't imagine repeating code in all my screens). I have considered creating a UINavigationController subclass, however I wasn't sure how this would effect my custom implementations of ViewDidLoad.
Any advice much appreciated. Also I need to target >= iOS4 (the appearance API is iOS5 only).
I prefer to not force inheritance where possible so you could do this with two categories
#interface UIViewController (backButtonAdditions)
- (void)ps_addBackbutton;
#end
#implementation UIViewController (backButtonAdditions)
- (void)ps_addBackbutton;
{
// add back button
}
#end
#interface UINavigationController (backButtonAdditions)
- (void)ps_pushViewController:(UIViewController *)viewController animated:(BOOL)animated;
#end
#implementation UINavigationController (backButtonAdditions)
- (void)ps_pushViewController:(UIViewController *)viewController animated:(BOOL)animated;
{
[viewController ps_addBackbutton];
[self pushViewController:viewController animated:animated];
}
#end
Now #import these files as appropriate and instead of using
[self.navigationController pushViewController:aViewController YES];
use
[self.navigationController ps_pushViewController:aViewController YES];
Disclaimer
I free styled this in the browser so you may need to tweak it
I had the same issue in my current project, the solution I came up with was to create a MYBaseViewController base class without xib and there in viewDidLoad programmatically (if you want to init barButtonItem with custom view, you are not able to create in xib anyways) create a customBackButton (and of course release it and set to nil viewDidUnload.
This works good for me because this way I can create xibs for all my other viewControllers that are subclasses of MYBaseViewController(if you created a view for base class in nib you would not be able to create a nib for a subclass).
I was wondering how I could access data from another class using Xcode 4.2 and Storyboard?
Say for instance how would I access the text of a text field from another class?
Google hasn't helped and the lesson on MyCodeTeacher.com about this is outdated and doesn't work anymore...
Thanks for bearing with me!
-Shredder2794
Not sure if this is the only or best way, but you can create a property in the destination view's .h file and set it to a value before the segue is performed
in the destination view controller's .h file:
#interface YourDestinationViewController : UIViewController
{
NSString* _stringToDisplay;
//...
}
#property (nonatomic, retain) NSString* stringToDisplay;
//...
and in the presenting view's .m file
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
YourDestinationViewController*viewController = segue.destinationViewController;
viewController.delegate = self;
viewController.stringToDisplay = #"this is the string";
}
Then you can do what you want with the property in whichever of the viewWillAppear/viewDidLoad/viewDidAppear/etc. methods best suits your purpose in the destination view's .m file
And then to check if it works, in the destination view controller's .m file:
-(void)viewWillAppear:(BOOL)animated
{
NSLog(#"self.stringToDisplay = %#", self.stringToDisplay);
...
//and if a label was defined as a property already you could set the
//label.text value here
}
Edit: Added more code, and made it less generic
This isn't specific to Storyboard. There are several ways to do what you are trying to do. You could declare a variable in your AppDelegate (an NSString) and set that in your first class. Then in your second class access the AppDelegate variable and use that to set your label. The code to do this is:
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
label.text = appDelegate.myString;
Another way to do it (probably the easiest) is to declare an NSString in your second class. Then in your first class, before you push the second view set that string variable. Something like this:
MyViewController *vc = [[MyViewController alloc] initWithNibName:#"" bundle:nil];
vc.myString = #"";
The third way to do this is using delegates. This is the most 'complicated' way but is the best. You would create a delegate which gets called when your second view appears. The delegate could then return the value from the first class to you.
You may also be able to use the new completion handler block on the iOS 5 pushViewController: method.
Edit:
Custom init method:
- (void)initWithNibName:(NSString *)nibName bundle:(NSString *)bundle string:(NSString *)myString
And then when you are pushing the view just class this method and set the string through it.
I am trying to open a ViewController from within another ViewController if certain conditions are met. The code seems to run without error but the view is never shown. I am new to xcode 4 /ios 5 so I must be missing something.
Here is the code responsible for opening the second viewcontroller:
CreateUserViewController *createUserController = [[CreateUserViewController alloc] initWithNibName:#"CreateUserView" bundle:[NSBundle mainBundle] keyWrapper:keyChainWrapper];
[self presentViewController:createUserController animated:YES completion:nil];
In my project I have a xib called, "CreateUserView". I have added a view controller to this xib and assigned it to, "CreateUserViewController".
Also I noticed in the apple documentation that is shows setting the delegate of the viewcontroller to be presented. But it seems that no property called, "delegate" is on the viewcontroller object. Is this documentation old? This is the document I am trying to use (section 9-1):
View Controller Programming
Can someone give me a hint? Thanks..
edit Adding Custom Constructor
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil keyWrapper:(KeychainItemWrapper *)keyWrapper
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if(self){
[self setKeyChainWrapper:keyWrapper];
}
return self;
}
Regarding CreateUserView.xib: you don't want to put a CreateUserViewController object in the nib. You want to set the custom class of the File's Owner placeholder to CreateUserViewController. Then you need to connect the view outlet of File's Owner to the top-level view in the nib.
Regarding the delegate property: The UIViewController class doesn't have its own delegate property. The idea is that you add a delegate property to your subclass of UIViewController. The delegate provides a way for your presented view controller to pass custom information back to the presenting view controller.
Why would you want to do that? Let's consider the code you posted. I'll assume you have a UserListViewController that shows a list of User objects, and has a "Create new user" button. When the user touches the "Create new user" button, you create a CreateUserViewController and present it.
The user interacts with the CreateUserViewController to set the attributes of the new User object - name, rank, hairstyle, etc. Then he touches a "Done" button. Your CreateUserViewController creates the new User object and puts it in the database. Then it needs to dismiss itself, so the UserListViewController's list of User objects will appear again.
But you want the User list to include the newly created User object and you want to scroll the list so that the new User is on the screen. So you need a way to have your CreateUserViewController tell the UserListViewController about the newly created User object. This is where the delegate comes in.
You define a protocol like this:
#protocol CreateUserViewControllerDelegate
- (void)didCreateUser:(User *)user;
#end
and you give your CreateUserViewController a delegate property:
#interface CreateUserViewController
#property (weak, nonatomic) id<CreateUserViewControllerDelegate> delegate;
// ...
When your CreateUserViewController's "Done" button is touched, you notify your delegate of the new User:
- (IBAction)doneButtonWasTouched:(id)sender {
User *user = [self createUser];
[self.delegate didCreateUser:user];
[self dismissViewControllerAnimated:YES completion:nil];
}
In your UserListViewController, you adopt and implement the protocol:
#interface UserListViewController <CreateUserViewControllerDelegate, UITableViewDelegate, UITableViewDataSource>
// ...
#end
#implementation UserListViewController
- (void)didCreateUser:(User *)user {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[self.users count] inSection:0];
[self.users addObject:user];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation: UITableViewRowAnimationAutomatic];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition: UITableViewScrollPositionNone animated:YES];
}
and when you need to present a CreateUserViewController, you set the new controller's delegate to the UserListViewController:
- (IBAction)createUserButtonWasTouched:(id)sender {
CreateUserViewController *createUserController = [[CreateUserViewController alloc] initWithNibName:#"CreateUserView" bundle:[NSBundle mainBundle] keyWrapper:keyChainWrapper];
createUserController.delegate = self;
[self presentViewController:createUserController animated:YES completion:nil];
}
In iOS5 the method for pushing new view controllers was really changed around quite a bit from iOS4 and Xcode 3. In summary, storyboards are now used to create your application view controller flow. Even though you may use standalone .xib files to build an application it is much less common in iOS5.
Anyway, the main method for pushing new view controllers onto the screen is done using segues. Check out this tutorial for an introduction: http://www.raywenderlich.com/5138/beginning-storyboards-in-ios-5-part-1
It does a good job on explaining how to create a storyboard and use segues. You can still present view controllers in code "the old way" but it is much much less common now with the introduction of these new technologies. There are also some absolutely awesome tutorials on iTunes U - search for CS193P. It's the Stanford Introductory class to Objective-C and programming for iOS. This should get you started and maybe help you think of a way to push your createUserController in a way more up to speed with iOS5.
UPDATE
I just wanted to add. If you configure your program to use storyboards and segues you can use the method performSegueWithIdentifier:sender: to perform the segue to your createUserController view if the proper conditions are met. See the Apple API for UIViewController for information on how to use this method.
I know this question has been asked several times and I did read existing posts on this topic but I still need help.
I have 2 UIViewControllers - parent and child. I display the child UIViewController using the presentModalViewController as below:
ChildController *child =
[[ChildController alloc] initWithNibName:#"ChildView" bundle:nil];
[self presentModalViewController:child animated:YES];
[child release];
The child view has a UIPickerView. When user selects an item from UIPickerView and clicks done, I have to dismiss the modal view and display the selected item on a UITextField in the parent view.
In child's button click delegate, I do the following:
ParentController *parent =
(ParentController *)[self.navigationController parentViewController];
[parent.myTextField setText:selectedText];
[self dismissModalViewControllerAnimated:YES];
Everything works without errors. But I don't know how to load the parent view so that it displays the updated UITextField.
I tried
[parent reloadInputViews];
doesn' work. Please help.
Delegation is the way to go. I know some people that may be looking for an easier solution but trust me I have tried others and nothing works better than delegation. So anyone having the same problem, go read up on delegation and follow it step by step.
In your subviewcontroller.h - declare a protocol and declare delegate mthods in it.
#protocol myDelegate
-(void)clickedButton:(subviewcontroller *)subController;
#end
In your subviewcontroller.h, within #interface:
id<myDelegate> delegate;
#property (nonatomic, assign) id<myDelegate> delegate;
NSString *data;
-(NSString *)getData;
In your subviewcontroller.m, synthesize myDelegate. Add the following code to where you want to notify your parentviewcontroller that the subview is done doing whatever it is supposed to do:
[delegate clickedButton:self];
and then handle getData to return whatever data you want to send to your parentviewcontroller
In your parentviewcontroller.h, import subviewcontroller.h and use it's delegate
#import "subviewcontroller.h"
#interface parentviewcontroller : VUIViewController <myDelegate>
{}
In your parentviewcontroller.m, implement the delegate method
- (void)clickedButton:(subviewcontroller *)subcontroller
{
NSString *myData = [subcontroller getData];
[self dimissModalViewControllerAnimated:YES];
[self reloadInputViews];
}
Don't forget memory management!
If a low-memory warning comes in during your modal view's display, the parent's view will be unloaded. Then parent.myTextField is no longer referring to the right text field until the view is reloaded. You can force a reload of the view just by calling parent.view;
However, a better idea might be to have the parent view have a String property that can be set by the child view. Then, when the parent view reappears, put that data into the text field, inside viewWillAppear: for example. You'd want to have the value set to some default value for when the parent view initially shows up too.
-(void) viewWillAppear:(BOOL) animated doesn't get called for me either, exactly when it's a modal view controller. No idea why. Not incorrectly overridden anywhere in this app, and the same problem occurs on the other 2 apps I'm working on. I really don't think it works.
I've used the delegate approach before, but I think that following approach is pretty good as well.
I work around this by adding a private category to UIViewController, like so:
.h file:
#interface UIViewController(Extras)
// returns true if this view was presented via presentModalViewController:animated:, false otherwise.
#property(readonly) BOOL isModal;
// Just like the regular dismissModalViewController, but actually calls viewWillAppear: on the parent, which hasn't been working for me, ever, for modal dialogs.
- (void)dismissModal: (BOOL) animated;
#end
and .m file:
#implementation UIView(Extras)
-(BOOL) isModal
{
return self == self.parentViewController.modalViewController;
}
- (void)dismissModal: (BOOL) animated
{
[self.parentViewController viewWillAppear: animated];
[self dismissModalViewControllerAnimated: animated];
}
#end
which I can now call like this when I want to dismiss the dialog box:
// If presented as a modal view, dismiss yourself.
if(self.isModal)
[self dismissModal: YES];
and now viewWillAppear is correctly called.
And yes, I'm donating a bonus 'isModal' property, so that the modal view can tell how it was being presented, and dismiss itself appropriately.