So I have an interesting design question regarding an app I'm developing for the iPhone. I am creating an app that manipulates images, and there are different types of manipulations that can be performed. So the user opens the app, and selects what type of manipulation they want to perform, and are taken through a step by step process to perform the manipulation.
A lot of the manipulations are similar, so code can be reused here. So instead of creating a view controller for each window of each manipulation, I decided to create one view and one view controller. The view contains the steps of each image manipulation, and each time it is incremented to the next step, it reorganizes itself appropriately. The view controller is controlled by a navigation controller, and each time the user advances to the next step of whatever image manipulation they're trying to perform (ie pushed a new view controller on the stack), I make a copy of my view object, set it to reorganize its components to the appropriate step, then send it to the new view controller which will display it.
So my question is this, for some stages of the manipulations, I need to add some buttons to a universal toolbar that is attached to view controller (since this is a modal view, this tool bar will have a home button that will enable the user to exit back to the main screen). Basically, I have a couple of questions on how I should approach this:
1) Should I simply add the toolbar to the view that I'm using, instead of the view controller. If so, how would I have the home button on the toolbar exit the modal view?
2) Should I keep the toolbar on the view controller, and have my view return a set of buttons to be added to it when the view loads? Then I guess I would have to list all of my action methods in my view controller?
3) Should I keep the toolbar on the view controller, but send a pointer from the toolbar to my view object, then add the buttons within my view class? Would I be able to add my action methods in my view class then?
Anyhow, sorry if this is complicated, and if you have any follow up questions please let me know.
1) Ok.
For dismissing, does your view have a pointer to the view controller? How about something like this:
[self.viewController.parentViewController dismissModalViewControllerAnimated:YES];
Not sure if I understand exactly how your hierarchy is organized. That's just my guess.
2) That seems kludgy to me. You'd have to define some sort of data structure that describes what the view wants in a button, make a list of them. The view controller has to request that list, go through them.
3) That seems like the best option. But I wouldn't have your view directly add subviews to the toolbar. Create a ToolbarView custom view. Give it some kind of addButton method, with parameters that describe what essential attributes you want the button to have, like title and the target and action maybe. Let the ToolbarView decide what it looks like, where it's positioned, etc.
Can your action methods go on your view class? Yeah I guess, but they shouldn't. The recommended iPhone design pattern is that views shouldn't do anything, they should just show things. Methods that do things should be on view controllers, even if the only thing they do is change what views are being shown.
I finally came up with a solution for this. What I did was create a universal view controller called UIMainViewController that obviously inherits from UIViewController. I implement the toolbar like follows:
- (void) viewDidLoad
{
[super viewDidLoad];
[self assembleToolbarButtons];
[[self navigationController] setToolbarHidden:NO];
[self setToolbarItems: toolbarButtons];
[[[self navigationController] toolbar]setBarStyle:UIBarStyleBlack];
}
- (void) assembleToolbarButtons
{
NSMutableArray *toolbarButtonsTemp = [[NSMutableArray alloc] init];
[self setToolbarButtons: toolbarButtonsTemp];
[toolbarButtonsTemp release];
if ([self mode] == UIMainViewControllerMainMode)
{
UIBarButtonItem *createAPictureButton = [[UIBarButtonItem alloc] initWithTitle:#"Create" style: UIBarButtonItemStyleBordered target:self action:#selector(loadCreateAPictureModalViewController)];
UIBarButtonItem *accountButton = [[UIBarButtonItem alloc] initWithTitle:#"Account" style: UIBarButtonItemStyleBordered target:self action:#selector(loadAccountModalViewController)];
UIBarButtonItem *helpButton = [[UIBarButtonItem alloc] initWithTitle:#"Help" style: UIBarButtonItemStyleBordered target:self action:#selector(loadHelpModalViewController)];
[[self toolbarButtons] addObject: createAPictureButton];
[[self toolbarButtons] addObject: accountButton];
[[self toolbarButtons] addObject: helpButton];
[createACaptionButton release];
[accountButton release];
[helpButton release];
}
else
{
UIBarButtonItem *homeButton = [[UIBarButtonItem alloc] initWithTitle:#"Home" style: UIBarButtonItemStyleBordered target:self action:#selector(exitModalViewController)];
[[self toolbarButtons] addObject: homeButton];
[homeButton release];
}
}
-(void) loadCreateAPictureModalViewController
{
CreateAPictureViewController *createAPictureViewController = [[CreateAPictureViewController alloc] initWithMode:UIMainTableViewControllerModeModal];
UINavigationController *createAPictureNavController = [[UINavigationController alloc] initWithRootViewController: createAPictureViewController];
[[createAPictureNavController navigationBar] setBarStyle:UIBarStyleBlack];
[self presentModalViewController:createAPictureNavController animated:YES];
[createAPictureNavController release];
[createAPictureViewController release];
}
-(void) loadAccountModalViewController
{
AccountViewController *accountViewController = [[AccountViewController alloc] initWithMode:UICaptionDistractionTableViewControllerModeModal];
UINavigationController *accountNavController = [[UINavigationController alloc] initWithRootViewController: accountViewController];
[[accountNavController navigationBar] setBarStyle:UIBarStyleBlack];
[self presentModalViewController: accountNavController animated:YES];
[accountNavController release];
[accountViewController release];
}
-(void) loadHelpModalViewController
{
HelpViewController *helpViewController = [[HelpViewController alloc] initWithMode:UICaptionDistractionTableViewControllerModeModal];
UINavigationController *helpNavController = [[UINavigationController alloc] initWithRootViewController: helpViewController];
[[helpNavController navigationBar] setBarStyle:UIBarStyleBlack];
[self presentModalViewController: helpNavController animated:YES];
[helpNavController release];
[helpViewController release];
}
-(void) exitModalViewController
{
[self dismissModalViewControllerAnimated:YES];
}
So for my app, on each viewcontroller it will have a toolbar at the bottom that have the basic buttons for creating a picture, accessing the account, or accessing help. If one of these buttons is accessed, it will launch a modal view which will have the home button to exit the modal view (when the UIMainViewController is created, one of it's parameters tells it which mode it is in, and thus which toolbar buttons to add.
But the main thing is I created a class mutablearray varialbe to store the toolbar buttons and then the buttons are created in "assembleToolbarButtons". Now any class that inherits from UIMainViewController can override the assembleToolbarButtons in order to add it's own buttons on top of the main ones that have already been added.
As far as what I mentioned initially in using one UIView and having it reorganize itself, and only one uiviewcontroller, I avoided this and instead just created separate view controllers for each step and separate views so as to adhere to MVC more.
Related
In my iPhone application I want to make a page/view where the user can add notes.. essentially write something and then save it...
similar to what the Notes app offers on the iPhone... but at a basic level...
I was thinking of something simple like a tableview with a "+" button on the navigation bar that lets the user add a note.
I have googled every possible thing I could think of.. :(
Could someone show me how I would go about implementing this in my view controller?
(I know it seems very simple but I'm new to programming for the iPhone - hence why I'm struggling...)
You will want to add a barbuttonitem to the navigation bar and set the action to method that launches your Note viewController with a new note. The easiest way to do all that is to use Interface Builder to add the button and hook up the action to a custom method that looks something like this:
- IBAction newButtonPressed:(id)sender
{
NoteViewController *noteVC = [[NoteViewController alloc] initWithNibName:#"NoteViewController" bundle:nil];
[self presentModalViewController:noteVC animated:YES];
[noteVC release];
}
If you want to add the BarButtonItem from code you can do this in your viewDidLoad method:
UIBarButtonItem *newButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(newButtonPressed:)];
self.navigationItem.rightBarButtonItem = newButton;
[newButton release];
Note - the above code assumes your View Controller is called NoteViewController and has a Nib called NoteViewController, if you are doing some other form of initialization, then do it there, the important code is the presentModalViewController.
If instead of the modalview approach, you would like a more navigation like entrance (with back button functionality, etc.) you can use the following method instead:
- IBAction newButtonPressed:(id)sender
{
NoteViewController *noteVC = [[NoteViewController alloc] initWithNibName:#"NoteViewController" bundle:nil];
[self.navigationController pushViewController:noteVC animated:YES];
[noteVC release];
}
I know the SDK documentation says
Taps outside of the popover’s contents automatically dismiss the popover.
But I'm sure the smart people here found a way :)
maybe I should overwrite the popover dismiss function?
Thanks
EDIT:
I tried using the passthroughViews as was suggested here, and it works perfectly. Here's the code for whoever needs it - in this example, I put self.view in the array, which means that where ever outside the button where the popover was originated, nothing dismiss the popover.
popoverController.passthroughViews = [[[NSArray alloc] initWithObjects:self.view, nil] autorelease];
You need to set the passthroughViews property. From the documentation:
An array of views that the user can interact with while the popover is visible.
#property (nonatomic, copy) NSArray *passthroughViews
When a popover is active, interactions with other views are normally disabled until the popover is dismissed. Assigning an array of views to this property allows taps outside of the popover to be handled by the corresponding views.
Set passthroughViews to an array of view(s) that you want to handle the touch event instead of just dismissing the popover.
There is a very simple and legit solution. In the view controller that presents your UIPopoverController, conform to the UIPopoverControllerDelegate protocol and implement the following delegate method. I just tested this and it does prevent popover to dismiss.
- (BOOL)popoverControllerShouldDismissPopover:(UIPopoverController *)popoverController
{
return NO;
}
Just make sure that you have set the delegate of your popover controller to the view controller that implements this.
You can dismiss the popover by using [popoverController dismissPopoverAnimated:NO]; method.
The accepted answer does not really answer the question, "is there a way NOT to have the popover dismissed when pressing outside it?", imo. It does give a possible view but could require hackish access to all parent views and determining what views are on the screen etc. The question could be rephrased as, "how do I make a popover view modal?"
You would do this like so, with a done button to close the popover:
UIViewController* vc = [[[UIViewController alloc] init] autorelease];
UIBarButtonItem* doneButton = [[[UIBarButtonItem alloc] initWithTitle:#"Done"] style:UIBarButtonItemStyleDone target:self action:#selector(processDoneAction)] autorelease];
[vc.navigationItem setLeftBarButtonItem:doneButton];
vc.modalInPopover = YES;
//If you want full screen:
vc.modalPresentationStyle = UIModalPresentationFullScreen;
vc.wantsFullScreenLayout = YES;
UINavigationController* navC = [[[UINavigationController alloc] initWithRootViewController:vc] autorelease];
UIView* view = create your view
vc.view = view;
UIPopoverController* pc = [[[UIPopoverController alloc] initWithContentViewController:navC] autorelease];
pc.delegate = self;
self.popoverController = pc;
Then you'll in your processDoneAction method you will need to dismiss the popover. Other considerations would be dismissing and redisplaying on device orientation changes, but I will leave that to another exercise as that has been answered previously on stackoverflow.
I'm confused something fierce over having multiple views. I simply want to have a button on my main view that activates a new view, which in turn would have an (x) button which goes back to main view. For the life of me, I can't figure out how to do this with two separate .xib files. How might this be done?
Thanks!
It might be easiest to just use the utility application as a template.
From there, you can see how you would load view controllers and nibs in order to bring up the new view followed by how you would exit it.
I actually solved it by doing it this way:
NewViewController *new = [[NewViewController alloc] initWithNibName:#"NewViewController" bundle:nil];
new.delegate = self;
new.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:new animated:YES];
[new release];
You are using a navigation controller, right?
On the IBAction associated with a button tap on the main view, put
NewViewController *newView = ... // [alloc - init your view here if you haven't already]
[self.navigationController pushViewController:newView animated:YES];
And on the button on newView,
[self.navigationController popViewControllerAnimated:YES]
I have a navigation controller and a button located on the right of the navigation bar that is called toggle. When ever I press the button I basically want to toggle between a table view and a map view. How can I do this?
Can this be done by pushing and popping from the navigation stack back and forth?
Ok... what you want can be done using navigationControllers and such.
You just need to make a method in both viewControllers, and in the viewDidLoad of each put this:
[self.navigationItem setRightBarButtonItem:[[[UIBarButtonItem alloc] initWithTitle:#"Toggle" style:UIBarButtonItemStylePlain target:self action:#selector(toggleView:)] autorelease]];
then you want this method in the first view you get to (lets say its the list view):
-(void)toggleView:(id)selector {
MapViewController *mapViewController = [[MapViewController alloc] initWithNibName:nil bundle:nil];
[self.navigationController pushViewController:mapViewController animated:NO]; // this pushes a view onto the stack
/* or you could use this:
[self presentModalViewController:mapViewController animated:YES];
// which slides the view up over the current view */
[mapViewController release];
}
then in the mapview:
- (void)toggleView:(id)selector {
[self.navigationController popViewControllerAnimated:YES]; // this pops back a view
/* or you went for the second option above:
[self dismissModalViewControllerAnimated:YES];
// which slides the view back down. */
}
I am doing this same thing in one of my apps, except with a segmented control. In my case, the map and the table were logically on the same hierarchical level, so using the navigation controller didn't make sense.
Basically, I instantiate both the table view and the map view when the user is at that point in the app. When I receive a button press event, I just move one view in front of the other (bringSubviewToFront). The events from both of these views (map and table) push the next view onto the controller. To make sure that the back button on the navigation controller makes sense, I just toggle the navigationItem title in the RootViewController. So, if they want to toggle the map:
[self.view bringSubviewToFront:self.map_view.view];
UIBarButtonItem *backButton =
[[UIBarButtonItem alloc] initWithTitle:#"Map" style:
UIBarButtonItemStylePlain target:nil action:nil];
self.navigationItem.backBarButtonItem = backButton;
etc...
Since I ran into some memory problems, I also have some code that will trash the map and reset the back button title if I get a memory warning, and the toggle button delegate method will check to see if the map needs to be re-instantiated before shuffling the views. I like this better than instantiating and releasing the map every time it's toggled. Generally retaining the map has much more fluid performance, and it allows the user's state for the map to persist (as long as we don't hit the memory limit).
Hope this helps.
For some reason, if I try to go back to the main menu using the back button on the upper left corner, only the title returns to the previous menu, but not the view controller. View controller would return to the previous menu only if I explicitly call popViewControllerAnimated using some other button.
Is there anyway to solve this? I think I've coded something wrong. Tried googling but couldn't find any cases like mine.
I'm getting the exact same problem. Here is my code:
- (IBAction) showGameView:(id) sender {
gameView = [[TCGameViewController alloc] initWithNibName:#"TCGameViewController" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:gameView animated:YES];
[gameView release];
}
And when I am done with gameView, I do this:
[self.navigationController setNavigationBarHidden:NO animated:YES];
But all it does when I push the 'back' button is cycle through the navigation bar, but never pops the view. I don't even know how to debug it.
In my other view, "infoView" I call the same code as before except the NavBar is never hidden, but it works just fine.
helps!
This problem can occur when you override the following method in your custom view controller:
- (UINavigationItem*)navigationItem
But you don't specify a UIBarButtonItem for the leftBarButtonItem property of the returned UINavigationItem.
If you use a custom navigationItem, and want the standard back button functionality, you could add a method as follows (remember that every UIViewController has a reference to the navigationController that containts it):
- (void)backButtonTapped
{
[self.navigationController popViewControllerAnimated:YES];
}
And then setup part of the custom navigationItem as follows:
- (UINavigationItem*)navigationItem
{
UIBarButtonItem* newLeftBarButton = [[UIBarButtonItem alloc] initWithTitle:#"Back"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(backButtonTapped)];
UINavigationItem* navigationItem = [[[UINavigationItem alloc] init] autorelease];
Hope this helps.