I've been struggling with this problem for some days. I've been trying to have a persistent RightBarButtonItem in several views. By researching on several blogs and web searches, it turned out that I need to set my rightBarButtonItem in the function -navigationController:willShowViewController:animated:.
My app does not show any errors but when I try to debug or use NSLog statements, it shows that the app does not enter this function at all. I have <UINavigationControllerDelegate> in the interface of my RootViewController class, but I also set my NSXMLParser parser as a delegate to itself ([parser setDelegate:self];) in another class. Can this be a problem that the navigationController delegate is not recognized or something.
- (void)navigationController:(UINavigationController *)navigationController
willShowViewController:(UIViewController *)viewController
animated:(BOOL)animated
{
//[self.navigationController.navigationItem setRightBarButtonItem:twoButtons animated:YES];
self.navigationItem.rightBarButtonItem = twoButtons;
NSLog(#"We are in navigationController delegate function");
}
If you want several views to have the same rightBarButtonItem, why not create a base UIViewController that all of your views inherit? Conceptually, I think this is a better solution because not only will all of the views inherit the button, they'll get the behavior as well ;) This also allows you to override methods in your base controller just in the event that one view needs to handle a click in a slightly different manner.
#interface BaseViewController : UIViewController
#property (nonatomic, retain) YourApplicationDelegate *delegate;
- (void) setupButtons;
- (void) buttonClicked:(id)sender;
#end
#import "BaseViewController.h"
#implementation BaseViewController
#synthesize delegate=_delegate;
- (void) viewDidLoad {
[super viewDidLoad];
self.delegate = (YourApplicationDelegate *) [[UIApplication sharedApplication] delegate];
[self setupButtons];
}
- (void) setupButtons {
UIBarButtonItem *button = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave
target:self
action:#selector(buttonClicked:)];
self.navigationItem.rightBarButtonItem = button;
[button release];
}
- (void) buttonClicked:(id)sender {
NSLog(#"Click!");
}
- (void) dealloc {
[_delegate release];
[super dealloc];
}
#end
/* Now the rest of your view controllers look pretty clean and you don't have a lot of
code in your delegate method. Most problems can be solved with a layer or two of abstraction :) */
#interface MyViewController : BaseViewController
#end
Base ViewControllers are also a good place to inject your application delegate which you'll need a lot. That's why I included it in the code block even though it wasn't part of your question. If you wanted to use a shared instance of a button or delegate the response handler out then you can easily put that code in your delegate and leverage a base view to access it easily.
Related
I have created a navigation-based application. In that, I have created MyTableViewController using uiviewcontroller.class.
#import "RootViewController.h"
#import "MyTableViewController.h"
#implementation RootViewController
- (void)viewDidLoad
{
[super viewDidLoad];
MyTableViewController *tableViewController = [[MyTableViewController alloc] init];
}
#end
#import "MyTableViewController.h"
#implementation MyTableViewController
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"sedfsdsd");
}
#end
I don't want to show the view when the instance is created. I want to call the constructor method. I don't know how to do it. Please help me out.
#Caroline have described very good.
A normal method of your class could serve your purpose and you can name that something ViewContruction and define it in your MyTableViewController class.
-(void) ViewContruction
{
//Create all your views here
//Add that to the self.view of your controller
}
Call the above function explicitly on the instance of your view controller.
Just creating a UIViewController instance does not load the view.
If you have something like [self.view addSubview:tableViewController.view] then when that statement is executed, viewDidLoad will get executed.
However, if it's a navigation-based app, then you will need to push the viewcontroller to see it, rather than adding the subview as above.
For example:
Settings *settingsController = [[Settings alloc] initWithNibName:#"Settings" bundle:nil];
settingsController.contentSizeForViewInPopover = settingsController.view.frame.size;
[self.navigationController pushViewController:settingsController animated:YES];
[settingsController release];
I have a small doubt. I have a NSObject class where I am trying to display an alert view. So after the alert view is displayed when I tap on OK button I want to push a navigation controller onto the stack. Can I push a navigation controller from general NSObject class? Please let me know guys..thanks for your time..
This is the code..
- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)buttonIndex{
SettingsViewController *homeView = [[SettingsViewController alloc] initWithNibName:#"SettingsViewController" bundle:nil];
[self.navigationController pushViewController:homeView animated:NO];
[homeView release];
}
I am creating a property called navigationController of type UINavigationController and when I catch the error I am displaying an alert view and I am using above method to push the view controller but it doesn't work..
Yes and no... depending on how you have your application set up. To push views onto the navigation stack you need to have a navigation controller.
Does your NSObject have access to this navigation controller - you might have to set up a delegate method that gets called from your delegate view when the alert view delegate gets called in your NSObject.
I'm just wondering why you're displaying a UIAlertView in an NSObject, why aren't you displaying it in a UIView or a UIViewController?
CustomObject.h
#protocol CustomObjectDelegate<NSObject>
#optional
- (void)customObjectAlertViewDidClickOk;
#end
#interface CustomObject : NSObject <UIAlertViewDelegate>{
id<CustomObjectDelegate> delegate;
}
#property (nonatomic, assign) id<CustomObjectDelegate> delegate;
#end;
CustomObject.m
#synthesize delegate;
// then put this:
- (void)alertView:(UIAlertView *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
[delegate customObjectAlertViewDidClickOk];
}
Then your ViewController .h file needs to include the custom object and assign the delegate methods:
#include "CustomObject.h"
#interface MyViewController : UIViewController <CustomObjectDelegate> {
}
#end
and the .m viewDidLoad (or similar):
- (void)viewDidLoad{
CustomObject *obj = [[CustomObject alloc] init];
[obj setDelegate:self];
}
- (void)customObjectAlertViewDidClickOk{
AnotherViewController *page = [[AnotherViewController alloc] initWithNibName:nil bundles:nil];
[self.navigationController pushViewController:page];
}
Thats how I would do it - given I'm not too sure i understand quite what you're asking. :) thats all off the top of my head as well - so don't take it letter for letter, but you have the basis there to start off with. You can build on it. Look up #protocols and delegate methods, its all in there. :)
Is there a way to call code when a modal view is finished dismissing?
EDIT:
I'm sorry, I didn't clarify earlier. I'm trying to dismiss a UIImagePickerController and then show a MFMailComposeViewController and attach the image data to the email. When I try to call
[self presentModalViewController: mailController]
right after
[self dismissModalViewController];
I get errors and such.
You use a delegate pattern for the modal view to inform whoever presented it when it's finished.
MyModalViewController.h:
#protocol MyModalViewControllerDelegate;
#interface MyModalViewController : UIViewController
{
id<MyModalViewControllerDelegate> delegate;
}
#property (nonatomic, assign) id<MyModalViewControllerDelegate> delegate;
#end
#protocol MyModalViewControllerDelegate
- (void)myModalViewControllerFinished:(MyModalViewController*)myModalViewController;
#end
MyModalViewController.m:
#synthesize delegate;
// Call this method when the modal view is finished
- (void)dismissSelf
{
[delegate myModalViewControllerFinished:self];
}
ParentViewController.h:
#import "MyModalViewController.h"
#interface ParentViewController : UIViewController <MyModalViewControllerDelegate>
{
}
ParentViewController.m:
- (void)presentMyModalViewController
{
MyModalViewController* myModalViewController = [[MyModalViewController alloc] initWithNibName:#"MyModalView" bundle:nil];
myModalViewController.delegate = self;
[self presentModalViewController:myModalViewController animated:YES];
[myModalViewController release];
}
- (void)myModalViewControllerFinished:(MyModalViewController*)myModalViewController
{
[self dismissModalViewControllerAnimated:YES];
}
EDIT:
I haven't used UIImagePickerController, but looking at the docs, it looks like you already have most of the code done for you, as there is an existing UIImagePickerControllerDelegate class that has three different "dismissal" delegate callbacks (although one is deprecated). So you should make your ParentViewController class (whatever that is) implement the UIImagePickerControllerDelegate pattern and then implement those methods. While each method will do something different (since you have to handle when the user actually selects an image, or if they cancel), they each will do the same thing at the end: call dismissModalViewControllerAnimated: to dismiss the picker.
You have to dismiss the modalViewController somehow right? Either a UIButton, or by code:
- (void)dismissModalViewControllerAnimated:(BOOL)animated
In the IBAction (e.g. delegate) for the UIButton or in the method above, call whatever code you want.
I don't think there is a specific notification yet can subscribe to, to know when dismiss animation is done,...BUT. You can implement viewDidAppear: in the view controller that presented the modal view. This is what I do, when I use the (to UIImagePickerController quite similar) ABPeoplePickerNavigationController.
In the callback from people picker, I remember the person tapped in the picker on an instance variable, like this:
- (void)callbackFromModalView:(id)dataFromModalView {
// remember dataFromModalView as I need it when dismissed
self.dataFromModalView = dataFromModalView;
// now initiate dismissal
[self dismissModalViewControllerAnimated:YES];
}
then, in your view controller, implement this:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (self.dataFromModalView) {
//...present now view here
// don't forget to reset this one
self.dataFromModalView = nil;
}
}
in effect, you are using the combination of viewWillAppear: and the dataFromModalView property as the "notification about modal view dismissed".
I have an app with tabbar and webview. I'm trying to make the app come back to default url each time user taps the bar. Right now I'm intercepting taps and launching a method, however it's not affecting my webview (it's not loading the page). The method works properly when called from the class, but not when it's called from my app delegate, where I'm intercepting taps.
I suspect it's something with the way I create the SecondViewController object that it's not pointing to the webview, but I have no clue what I'm doing wrong.
Here is the code:
Second view header (where the WebView is located)
#interface SecondViewController : UIViewController {
IBOutlet UIWebView *secondView;
}
- (void) goToPage;
Second view implementation
#import "SecondViewController.h"
#implementation SecondViewController
- (void)awakeFromNib
{
[self goToPage];
}
- (void) goToPage
{
NSLog(#"go to page");
NSString *newURL = [NSString stringWithFormat:#"http://pageurl"];
[secondView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:newURL]]];
}
My app delegate, where I call the SecondViewController class method:
#import "RedDragonAppDelegate.h"
#import "SecondViewController.h"
#implementation RedDragonAppDelegate
#synthesize window;
#synthesize rootController;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Override point for customization after application launch
[window addSubview:rootController.view];
}
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
NSLog(#"didSelectViewController %d", rootController.selectedIndex);
SecondViewController * sv = [[SecondViewController alloc] init];
if (rootController.selectedIndex == 0){
//NSLog(#"if in didSelectViewController 0");
} else if (rootController.selectedIndex == 1) {
//NSLog(#"if in didSelectViewController 1");
[sv goToPage];
}
}
Thanks fot your help!
If I understand correctly, you've got an instance of SecondViewController with secondView connected to an instance of UIWebView in Interface Builder. What you want to do is call goToPage on that instance of SecondViewController from RedDragonAppDelegate. (In particular, note that I'm talking about instances of these--I believe this is the underlying issue.)
In tabBarController:didSelectViewController:, when you do SecondViewController * sv = [[SecondViewController alloc] init];, you are creating a new instance of SecondViewController and you can call its goToPage method, but sv is not the same instance of SecondViewController that appears in Interface Builder and has secondView connected to the UIWebView (that is, when you create a new instance of SecondViewController, the ivar secondView is unset and seems to be nil, but I don't know that it's guaranteed to be nil).
What you probably(*) want to do is add IBOutlet SecondViewController *sv; to the #interface of RedDragonAppDelegate, make sure that you have an instance of RedDragonAppDelegate in Interface Builder, connect the new IBOutlet sv of RedDragonAppDelegate to the instance of SecondViewController in Interface Builder, and delete the line in tabBarController:didSelectViewController: that defines and initializes sv.
(*) I'm not 100% certain on this because I don't do iPhone stuff and I don't know how your various views/objects/etc. are arranged in XIB/NIB files, but if everything's in one XIB/NIB file, I'm pretty sure it'll work.
I'm trying to work out the "best" way to use a UISegmentedControl for an iPhone application. I've read a few posts here on stackoverflow and seen a few people's ideas, but I can't quite sort out the best way to do this. The posts I'm referring to are:
Changing Views from UISegmentedControl
and
How do I use a UISegmentedControl to switch views?
It would seem that the options are:
Add each of the views in IB and lay them out on top of each other then show/hide them
Create each of the subviews separately in IB, then create a container in the main view to populate with the subview that you need
Set up one really tall or really wide UIView and animate it left/right or up/down depending on the selected segment
Use a UITabBarController to swap out the subviews - seems silly
For tables, reload the table and in cellForRowAtIndex and populate the table from different data sources or sections based on the segment option selected (not the case for my app)
So which approach is best for subview/non-table approaches? Which is the easiest to implement? Could you share some sample code to the approach?
Thanks!
I've come across this requirement as well in an iPad application.
The solution I came to was to create specialized view controllers for
each style of view to handle business logic relating to those views
(ie. relating to each segment), and programatically add/remove them as
subviews to a 'managing' controller in response to selected segment
index changes.
To do this, one has to create an additional UIViewController subclass that manages
UISegmentedControl changes, and adds/removes the subviews.
The code below does all this, also taking care of a few caveats/extras:
viewWillAppear/viewWillDisappear/etc, aren't called on the subviews
automatically, and need to be told via the 'managing' controller
viewWillAppear/viewWillDisappear/etc, aren't called on 'managing'
controller when it's within a navigation controller, hence the
navigation controller delegate
If you'd like to push onto a navigation stack from within a
segment's subview, you need to call back on to the 'managing' view
to do it, since the subview has been created outside of the
navigation hierarchy, and won't have a reference to the navigation
controller.
If used within a navigation controller scenario, the back button is
automatically set to the name of the segment.
Interface:
#interface SegmentManagingViewController : UIViewController <UINavigationControllerDelegate> {
UISegmentedControl * segmentedControl;
UIViewController * activeViewController;
NSArray * segmentedViewControllers;
}
#property (nonatomic, retain) IBOutlet UISegmentedControl * segmentedControl;
#property (nonatomic, retain) UIViewController * activeViewController;
#property (nonatomic, retain) NSArray * segmentedViewControllers;
#end
Implementation:
#interface SegmentManagingViewController ()
- (void)didChangeSegmentControl:(UISegmentedControl *)control;
#end
#implementation SegmentManagingViewController
#synthesize segmentedControl, activeViewController, segmentedViewControllers;
- (void)viewDidLoad {
[super viewDidLoad];
UIViewController * controller1 = [[MyViewController1 alloc] initWithParentViewController:self];
UIViewController * controller2 = [[MyViewController2 alloc] initWithParentViewController:self];
UIViewController * controller3 = [[MyViewController3 alloc] initWithParentViewController:self];
self.segmentedViewControllers = [NSArray arrayWithObjects:controller1, controller2, controller3, nil];
[controller1 release];
[controller2 release];
[controller3 release];
self.navigationItem.titleView = self.segmentedControl =
[[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:#"Seg 1", #"Seg 2", #"Seg 3", nil]];
self.segmentedControl.selectedSegmentIndex = 0;
self.segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
[self.segmentedControl addTarget:self action:#selector(didChangeSegmentControl:) forControlEvents:UIControlEventValueChanged];
[self didChangeSegmentControl:self.segmentedControl]; // kick everything off
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.activeViewController viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.activeViewController viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.activeViewController viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.activeViewController viewDidDisappear:animated];
}
#pragma mark -
#pragma mark UINavigationControllerDelegate control
// Required to ensure we call viewDidAppear/viewWillAppear on ourselves (and the active view controller)
// inside of a navigation stack, since viewDidAppear/willAppear insn't invoked automatically. Without this
// selected table views don't know when to de-highlight the selected row.
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
[viewController viewDidAppear:animated];
}
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
[viewController viewWillAppear:animated];
}
#pragma mark -
#pragma mark Segment control
- (void)didChangeSegmentControl:(UISegmentedControl *)control {
if (self.activeViewController) {
[self.activeViewController viewWillDisappear:NO];
[self.activeViewController.view removeFromSuperview];
[self.activeViewController viewDidDisappear:NO];
}
self.activeViewController = [self.segmentedViewControllers objectAtIndex:control.selectedSegmentIndex];
[self.activeViewController viewWillAppear:NO];
[self.view addSubview:self.activeViewController.view];
[self.activeViewController viewDidAppear:NO];
NSString * segmentTitle = [control titleForSegmentAtIndex:control.selectedSegmentIndex];
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:segmentTitle style:UIBarButtonItemStylePlain target:nil action:nil];
}
#pragma mark -
#pragma mark Memory management
- (void)dealloc {
self.segmentedControl = nil;
self.segmentedViewControllers = nil;
self.activeViewController = nil;
[super dealloc];
}
#end
Hope this helps.
I'd go with the second option you mention, creating the subviews in IB and swapping them in and out of a main view. This would be a good opportunity to use UIViewController, unsubclassed: in your initial setup, create a controller using -initWithNibName:bundle: (where the first parameter is the name of the NIB containing the individual subview, and the second parameter is nil) and add its view as a subview of your main view as necessary. This will help keep your memory footprint low: the default behavior of a UIViewController when receiving a memory warning is to release its view if it has no superview. As long as you remove hidden views from the view hierarchy, you can keep the controllers in memory and not worry about releasing anything.
(edited in response to comment:)
You don't need to subclass UIViewController, but you do need separate XIBs for each view. You also don't need to add anything to the containing view in IB.
Instance variables, in the interface of whatever class is handling all this:
UIViewController *controllerOne;
UIViewController *controllerTwo;
UIViewController *currentController;
IBOutlet UIView *theContainerView;
In your setup (-applicationDidFinishLaunching: or whatever)
controllerOne = [[UIViewController alloc] initWithNibName:#"MyFirstView" bundle:nil];
controllerTwo = [[UIViewController alloc] initWithNibName:#"MySecondView" bundle:nil];
To switch to a controller:
- (void)switchToController:(UIViewController *)newCtl
{
if(newCtl == currentController)
return;
if([currentController isViewLoaded])
[currentController.view removeFromSuperview];
if(newCtl != nil)
[theContainerView addSubview:newCtl.view];
currentController = newCtl;
}
Then just call that with, e.g.,
[self switchToController:controllerOne];
Here's a great tutorial that explains this concept further: http://redartisan.com/2010/5/26/uisegmented-control-view-switching
and the github location to it: https://github.com/crafterm/SegmentedControlExample.git