iOS4 - custom uiview accessing UINavigation stack - iphone

Bit confused with this one so bear with me...
I have a Navigation-based project which is working fine. I'm trying to create my first custom UIView to make a couple of buttons which I will use in multiple places. One of the buttons needs to push a viewcontroller into the navigation when it's clicked but I'm not sure how to do this.
When I had the button set up within a view controller I was using:
LocationViewController *controller = [[LocationViewController alloc] initWithNibName:#"LocationViewController" bundle:nil];
[self.navigationController pushViewController:controller animated:YES];
[controller release];
but the self.navigation controller won't work now, will it? How do I access the navigation controller of the viewcontroller that this uiview will be added to?
Hope at least some of that makes sense, as I said it's my first go at subclassing the uiview and adding it to multiple pages so I'm a bit lost.
EDIT TO ADD - I have the button click events inside the custom UIView, so that is where I'm trying to change the viewcontroller from. Should I instead wire up the events in whichever viewcontroller I add the view to?

Usually your appDelegate has a UINavigationController property. You can access it in your custom view like this:
UINavigationController *navController = (MyAppDelegate *)[[[UIApplication sharedApplication]
delegate] navigationController];
But more effective way is to make delegate method for your custom view and handle button action in your viewController.
MyCustomView.h
#protocol MyCustomViewDelegate
#interface MyCustomView : UIView {
id<MyCustomViewDelegate> cvDelegate; }
#property(nonatomic, assign) id<MyCustomViewDelegate> cvDelegate;
#protocol MyCustomViewDelegate #optional
-(void)didClickInCustomView:(MyCustomViewDelegate*)view withData:(NSObject*)data;
#end
MyCustomView.m
- (void)myButtonClick:(id)sender
{
[self.cvDelegate didClickInCustomView:self withData:someData];
}
So now you can handle this event in any place where is your custom
view.

Add the button from the interface builder or from the view controller's viewDidLoad using code:
CGRect frame = CGRectMake(0, 0, 24, 24);
UIButton *button = [[UIButton alloc] initWithFrame:frame];
[button addTarget:self action:#selector(handleMyButton:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
Then implement -(void)handleMyButton:(id)sender {}; in your view controller. Or you could instead write -(IBAction)handleMyButton:(id)sender {}; and link method and button using the interface builder.
Then inside the method just paste the block of code you posted above. If you started with the Xcode navigation controller template project it should work.
I think it's cleaner to hide the designated initializer initWithNibName: because it is an implementation detail.
When you say you are subclassing the UIView I don't know exactly what you mean. If you want to add another view controller with a custom view just use the UIViewController template and customize the XIB file, no need to subclass an UIView unless you are really modifying its behaviour, which I guess you are not. The view is a view, and the controller stuff like handling buttons should be in the controller.

The actual controller need to be in the navigation controller stack to be able to push another controller.
Or you can make a new navigation controller instance and push your LocationViewController.

Related

Pushing a UIViewController from a UIView

I need to push a UIView into my UINavigation controller. I am doing it by
[self.view addSubview:showContactFlow];
And on a button click in UIView I need to push another UIViewController over the UIView. From the UIView I am not able to access self.navigationcontroller How can I do this?
Edit:
I have set the UIView as the view of a new UIViewController I am pushing into, the before mentioned UIViewController . Now I would like to know, how to handle the UIView button event inside its UIViewController, in which's view it is set.
Add a UINavigationController ivar to the UIView and assign it to the main view controller's. Then you should be able to access it from the UIView.
Edit:
Your UIView subclass:
// CustomView.h
#interface CustomView: UIView {
// ...
// your variables
// ...
UINavigationController *navController;
}
#property (nonatomic, assign) UINavigationController *navController; // assign, because this class is not the owner of the controller
// custom methods
#end
// CustomView.m
#implementation Customview
// synthesize other properties
#synthesize navController;
// implementation of custom methods
// don't release the navigation controller in the dealloc method, your class doesn't own it
#end
Then before the [self.view addSubview:showContactFlow]; line just add [showContactFlow setNavController:[self navigationController]]; and then you should be able to access your hierarchy's navigation controller from your UIView and use it to push other UIViewControllers.
You should try to work with an MVC approach. So your controller has access to all that stuff and can keep pushing and popping views, so the view doesn't need to know too much about the controller.
Otherwise, and for this case you can solve it fast by using delegation. So:
showContactFlow.delegate = self;
[self.view addSubview:showContactFlow];
So later in the UIView, you can just say:
[self.delegate addSubview:self];
This is gonna work, but it's not likely to be the best approach you should use.
On button click, you can present a view controller like,
-(void)buttonFunction{
ThirdVC *third= [[ThirdVC alloc]initWithNibNme];......
[self presentViewController:third animated:NO];
}
Using Core animation you can make NavigationController's pushviewController like animation on writing code in ThirdVC's viewWillAppear: method.
where do you add the UIButton is it in showContactFlow view or in the ViewController's view??
In regard to the modalViewControllers issue the correct method is
[self presentModalViewController:viewController animated:YES];
the standard animation in upwards

Call UINavigationController from subview in UITableViewCell

I have a table view in which I have subclassed the cells. In these cells I add a subview of a UIView. When sliding the cell I add another UIView to the subclass of UITableViewCell.
I would like to present a ModalViewController when pressing a button inside the second UIView (subview in UITableViewCell). I do not have a navigation controller in this view, therefore I am passing the navigation controller from the view controller my table view is inside of and down to my second UIView.
Here, I call it as you normally would but nothing happens.
ComposeCommentViewController *ccvc = [[ComposeCommentViewController alloc] initWithNibName:#"ComposeCommentViewController" bundle:nil];
[navController presentModalViewController:ccvc animated:YES];
Does anyone have an idea what I might do wrong or have another solution?
EDIT: This is how I set navController
First I pass it to my subclass of UITableViewCell.
if (feedCell == nil)
{
feedCell = [[FeedCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
[feedCell setNavigationControllerForSlidedView:[self navigationController]];
}
The subclass has the method setNavigationControllerForSlidedView: which looks like this:
- (void)setNavigationControllerForSlidedView:(UINavigationController *)navController
{
[feedSlidedView setNavController:navController];
}
In my FeedSlidedView I have declared and synthesized UINavigationController *navController;
The way you are going about this runs contrary to MVC (model-view-controller) design practices. You have a number of mechanisms for accomplishing what you want within the MVC framework that Apple provides in its SDK. Probably the simplest, in my opinion, would be to add a target-action to the button in the subview of your UITableViewCell. In your view controller's tableView:cellForRowAtIndexPath: method, add something like the following:
[button addTarget:self action:#selector(presentComposeComment:) forControlEvents:UIControlEventTouchUpInside];
In this case, self would be the UIViewController that is responsible for the UITableView in question. You would then include the method for the selector above in that view controller:
- (void)presentComposeComment:(id)sender {
ComposeCommentViewController *ccvc = [[ComposeCommentViewController alloc] initWithNibName:#"ComposeCommentViewController" bundle:nil];
[self presentModalViewController:ccvc animated:YES];
}
Note that I am not sending the presentModalViewController:animated: message to the navigation controller, but rather the view controller.

TouchesBegan on a Sub-ViewController not getting called

I have a ViewController that responds to some touchEvents (touchesBegan, touchesMoved, etc...).
I've found that when I show this controller using presentModalViewController: it works just fine, but I'm trying to add it's View as a subview of another ParentViewController like so:
- (void)viewDidLoad
{
[super viewDidLoad];
//Add SubController
controller = [[SubViewController alloc] initWithNibName:#"SubViewController" bundle:nil];
controller.view.frame = CGRectMake(0, 30, 300, 130);
[view addSubview:controller.view];
[controller release];
}
When I do this, it gets added the parent view but it no longer responds to touch events. Is there a way to fix this?
Also, is there a better way to go about this? I know I probably could have used a View subclass for the child view, but it's supposed to use a Nib and I wasn't sure how to handle that without using a ViewController.
You're correct you should use a UIView subclass.
The easiest way to load it from a nib is to include the subview in your nib.
Just drop a UIView into the view connected to the original view controller.
Then with the view inside selected go to the identity inspector. It's the one that looks like a little ID card.
The very first field is called Custom Class.
Type the name of your UIView subclass here.
If you need a reference to this just create an IBOutlet in your original view controller and hook it up. That way you can set hidden = YES until you need it.
In your UIView subclass you might want to override
- (void)awakeFromNib
This will get called when the nib first unpacks.
for setting up any gesture recognizers, etc.
To load a nib directly into a view :
// Get the views created inside this xib
NSArray *views = [NSBundle mainBundle] loadNibNamed:#"myViewNib" owner:nil];
// There's probably only one in there, lets get it
UIView *myView = [views objectAtIndex:0];
// Do stuff . . .
[[self view] addSubview:myView];
You could try to call becomeFirstResponder in your subview and see whether it receives touchesBegan... It is probably so, but it will also possibly make the superview not receive touchesBegan if you require it...

Accessing Button Created in Subclassed UITabBarController

Ok, so I'm a little stuck and maybe someone can lend some advice.
I've subclassed UITabBarController, and am creating a custom button that overlays the tab bar whenever viewDidLoad gets called inside the CustomTabBarController.
This works great, except its not tied to any action.
What I would like to do is to have a UIModalViewController be displayed when that button is pressed. Now, preferably I would rather not make this call from the subclassed CustomTabBarController, but rather from within one of my viewControllers (rootViewController per-say) that is associated with a tab.
Can someone direct me in how to make this happen? IE, How to instantiate a button in one class and make that button respond to an action within another class.
Should I use NSNotificationCenter, delegate responders, something else? An example would be great :)
There are several approaches to achieve what you're asking for. The approach I usually take is that I do something like this:
// CustomTabBarController.h
#protocol CustomTabBarControllerDelegate
- (void)buttonAction:(id)sender;
#end
#interface CustomTabBarController : UITabBarController {
id<CustomTabBarControllerDelegate> customDelegate;
}
#property(nonatomic, assign) id<CustomTabBarControllerDelegate> customDelegate;
#end
// CustomTabBarController.m
#interface CustomTabBarController ()
- (void)buttonAction:(id)sender;
#end
#implementation CustomTabBarController
#synthesize customDelegate;
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
// Configuration of button with title and style is left out
[button addTarget:self action:#selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
- (void)buttonAction:(id)sender {
[self.customDelegate buttonAction:sender];
}
#end
I don't know what you're button will be doing, so I just call the method buttonAction:. Since UITabBarController already has a property named delegate I called our delegate customDelegate. All you need to do to make the above work is to add the following line to your root view controller (or whatever controller you want to handle the button action).
customTabBar.customDelegate = self;
Of course you also have to implement the protocol.
One could also imagine not using a delegate and just set the target like this:
[button addTarget:self.rootViewController action:#selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside];
The above code assumes that customTabBarController has a rootViewController property and that it has been set. Also it assumes that the root view controller has the following method:
- (void)buttonAction:(id)sender;
I prefer the delegate approach as it is the more general approach, but the later approach will also work. Using NSNotificationCenter is also an option, but I'm personally not a fan of sending notifications when it isn't necessary. I usually only use notifications when multiple objects need to respond to an event.
You can reference all the view controller in your UITabBarController with the viewControllers array. You can easily get view controller for the currently selected view with selectedViewController.
With that in mind, your CustomTabBarController action can call a methods on these view controllers. Just add method to the appropriate view controller(s) to display your UIModalViewController.

Pop-up modal with UITableView on iPhone

I need to pop up a quick dialog for the user to select one option in a UITableView from a list of roughly 2-5 items. Dialog will be modal and only take up about 1/2 of screen. I go back and forth between how to handle this. Should I subclass UIView and make it a UITableViewDelegate & DataSource?
I'd also prefer to lay out this view in IB. So to display I'd do something like this from my view controller (assume I have a property in my view controller for DialogView *myDialog;)
NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:#"DialogView" owner:myDialog options:nil];
myDialog = [nibViews objectAtIndex:0];
[self.view addSubview:myDialog];
problem is i'm trying to pass owner:myDialog which is nil as it hasn't been instantiated...i could pass owner:self but that would make my view controller the File's Owner and that's not how that dialog view is wired in IB.
So that leads me to think this dialog wants to be another full blown UIViewController... But, from all I've read you should only have ONE UIViewController per screen so this confuses me because I could benefit from viewDidLoad, etc. that come along with view controllers...
Can someone please straighten this out for me?
There is no such thing as a view controller being on the screen; its view is on the screen. With that said, you can present as many views as you want on the screen at once.
I would create a new view and view controller. You would not make a UIView be a UITableViewDelegate, you make a UIViewController be a UITableViewDelegate. But instead of doing that manually, instead make your new view controller a subclass of UITableViewController, if you're using iPhone OS 3.x+. You can then present this view controller modally.
You probably want to give the user a chance to cancel out of the selection. A good way to do that is to wrap your new dialog view controller in a UINavigationController and then put a "Cancel" button in the nav bar. Then use the delegate pattern to inform the parent view controller that the user has made their choice so you can pop the stack.
Here's what the code will look like inside your parent view controller, when you want to present this option dialog:
- (void)showOptionView
{
OptionViewController* optionViewController = [[OptionViewController alloc] initWithNibName:#"OptionView" bundle:nil];
optionViewController.delegate = self;
UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:optionViewController];
[self.navigationController presentModalViewController:navController animated:YES];
[navController release];
[optionViewController release];
}
Your OptionViewController .h will look like this:
#protocol OptionViewControllerDelegate;
#interface OptionViewController : UITableViewController
{
id<OptionViewControllerDelegate> delegate;
}
#property (nonatomic, assign) id<OptionViewControllerDelegate> delegate;
#end
#protocol OptionViewControllerDelegate <NSObject>
- (void)OptionViewController:(OptionViewController*)OptionViewController didFinishWithSelection:(NSString*)selection;
// or maybe
- (void)OptionViewController:(OptionViewController*)OptionViewController didFinishWithSelection:(NSUInteger)selection;
// etc.
#end
Your OptionViewController.m will have something like this:
- (void)madeSelection:(NSUInteger)selection
{
[delegate OptionViewController:self didFinishWithSelection:selection];
}
Which has a matching method back in your original view controller like:
- (void)OptionViewController:(OptionViewController*)OptionViewController didFinishWithSelection:(NSUInteger)selection
{
// Do something with selection here
[self.navigationController dismissModalViewControllerAnimated:YES];
}
There are plenty of examples throughout Apple's sample source code that follow this general pattern.