Pushing and Popping ViewControllers using a Navigation Controller: Implementation - objective-c

Like many others I started to code an experiment today where I would have two view controllers and be able to switch between them. I got this to work using a navigation controller, but I have a question about the implementation.
In my TwoViewsAppDelegate, I define the navigation controller and the rootViewController.
#interface TwoViewsAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
UINavigationController *navigationController;
RootViewController *rootViewController;
}
and set them up as follows:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
rootViewController = [[RootViewController alloc] init];
navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
[window setRootViewController:navigationController];
[self.window makeKeyAndVisible];
return YES;
}
Then in my rootViewController, I define the level2ViewController that I
am going to switch to, and a button that I'm going to press to make the
switch happen:
#interface RootViewController : UIViewController {
UIButton *theButton;
Level2ViewController *level2ViewController;
}
Here's the response to the button being pressed in RootViewController.m:
-(void)level1ButtonPressed:(id)sender
{
if (level2ViewController == nil)
{
level2ViewController = [[Level2ViewController alloc] init];
}
[self.navigationController pushViewController:level2ViewController animated:YES];
}
The problem is that if there was going to be a level3ViewController,
it would have to be defined as a member of level2ViewController, etc.
for however many view controllers i wanted to push onto the stack.
It would be nice to be able to define all the view controllers in one
place, preferably the app Delegate. Is this possible?

To solve this, you can create a callback-type method which uses the delegate of the class that'll be sending the requests for the view controllers. Best explained through code...
RootViewController.h
#import "RootInterfaceView.h"
// all the other VC imports here too
#interface RootViewController : UIViewController <RootInterfaceViewDelegate>
{
RootInterfaceView *interface;
}
RootViewController.m
-(void)rootInterfaceView: (RootInterfaceView*)rootInterfaceView didSelectItem:(NSUInteger)itemTag
{
switch (itemTag)
// then create the matching view controller
}
RootInterfaceView.h
// imports here if required
#protocol RootInterfaceViewDelegate;
#interface RootInterfaceView : UIView <RootInterfaceItemViewDelegate>
{
id <RootInterfaceViewDelegate> delegate;
}
#property (nonatomic, assign) id delegate;
#end
#protocol RootInterfaceViewDelegate <NSObject>
#optional
-(void)rootInterfaceView: (RootInterfaceView*)rootInterfaceView didSelectItem:(NSUInteger)itemTag;
#end
RootInterfaceView.m
// remember to synthesize the delegate
-(void)rootInterfaceItemSelected: (RootInterfaceItemView*)rootInterfaceItemView
{
NSUInteger theTag = rootInterfaceItemView.tag;
if ([self.delegate respondsToSelector:#selector(rootInterfaceView:didSelectItem:)])
[self.delegate rootInterfaceView:self didSelectItem:theTag];
}
Alternatively, if the only options from level 2 were either back to root/pop one VC or to push controller 3, then it'd be fine for level 2 to be importing 3 to allow for it's creation.

Related

Cocoa app doesn't show textview

I'm an iOS developer and I want to create a simple desktop app. I thought the switch would go perfect but it doesn't.
I've created a cocoa app ( from the xCode template ). Now I don't want to use user interface builders and stuff so I wrote my first controller like this:
#interface MainViewController ()
#property (nonatomic, strong) NSTextView *test;
#end
#implementation MainViewController
-(instancetype) init
{
self = [super init];
if(self)
{
NSLog(#"%s", __PRETTY_FUNCTION__);
_test = [[NSTextView alloc] init];
[_test setString:#"DKDDK"];
[self.view addSubview:_test];
[_test mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
}
return self;
}
#interface MainViewController : NSViewController
#end
And I just use the NSWindow that is created by the template:
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
MainViewController * mainView = [[MainViewController alloc] init];
[self.window.contentView addSubview:mainView.view];
mainView.view.frame = ((NSView*)self.window.contentView).bounds;
}
When I run the application it gives me:
[NSViewController loadView] loaded the "(null)" nib but no view was set.
I don't know how to solve this. How can I create an app without nib, just like you do on iOS?
If you aren't loading the view from a NIB then there is little need for a view controller.
Discard the view controller and subclass NSView instead, and set that as the window's content view.
Note: you are making a rod for your own back by not using IB.

How do I use a button to get back to my first view

I am using a single view application template in xcode. I created the first view controller, and then added another with a new .m and .h and xib.
I can click a button IBAction, and get to my second view, however the code I am using for the "back" button wont take me back to my first view, everything crashes. I have included my code which seems to follow the tutorial I was using. Additionally I just control clicked my button and dragged the line to my IBAction in the .h to hook in the secondViewController buttons, which is what I did on the first view controller and it seems to work there.
If anyone can help that would be great!
//from my first view controller .h which works
-(IBAction) buttonPressedPayTable: (id) sender;
//from my first view controller.m which also works and gets me to the second view
-(IBAction) buttonPressedPayTable: (id) sender
{
SecondViewController *payTableView = [[SecondViewController alloc]
initWithNibName:#"SecondViewController" bundle:nil];
[self.view addSubview:payTableView.view];
}
//from my second view controller .h that will not get me back to the first view without crashing
#import <UIKit/UIKit.h>
#interface SecondViewController : UIViewController
{
}
-(IBAction) back: (id)sender;
#end
//from my second view controller .m which doesn't seem to work
#import "SecondViewController.h"
#implementation SecondViewController
-(IBAction) back: (id)sender
{
[self.view removeFromSuperview];
}
#end
use UINavigation controller
[self.navigationController popViewControllerAnimated:YES];
You might be better off using modal views. So instead of addSubView use:
[payTableView setModalPresentationStyle:UIModalPresentationFullScreen];
[payTableView setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
[self presentModalViewController:payTableView animated:YES];
Then on the seconViewController back method:
[self dismissModalViewControllerAnimated:YES];
You can change the ModalTransitionStyle to the few that apple gives you :D
Hope this helps
You can use a navigation method for switching from one view controller to the other.
See the apple docs about view controllers
Don't add your second ViewController as a subview of the view.
Use a UINavigationController to add the new UIViewController to the view stack using [self.navigationController pushViewController:payTableView animated:YES];
Then you can either use the nav controllers back button, or use your own button with the code [self.navigationController popViewControllerAnimated:YES];
Alternately, you can use Storyboards and use a simple segue.
[payTableView.view removeFromSuperview];
i think you are removing the whole view .that is why app is crashing
You need to use delegate here:
in .h of first view declare a member variable :
#interface FirstViewControllerClassName: UIViewController
{
SecondViewController *payTableView;
}
#property (nonatomic, retain) SecondViewController *payTableView;
in .m :
#synthesize payTableView;
-(IBAction) buttonPressedPayTable: (id) sender
{
if (!payTableView)
payTableView = [[SecondViewController alloc]
initWithNibName:#"SecondViewController" bundle:nil];
payTableView.delegate = self;
payTableView.isFinishedSelector = #selector(removeView);
[self.view addSubview:payTableView.view];
}
- (void)removeView
{
[payTableView.view removeFromSuperview];
[payTableView release];
payTableView = nil;
}
//Just declare a member variable delegate in secondviewcontroller like:
#import <UIKit/UIKit.h>
#interface SecondViewController : UIViewController
{
id delegate;
SEL isFinishedSelector;
}
#property (nonatomic, assign) id delegate;
#property (nonatomic, assign) SEL isFinishedSelector;
-(IBAction) back: (id)sender;
#end
#import "SecondViewController.h"
#implementation SecondViewController
#synthesize delegate;
#synthesize isFinishedSelector;
-(IBAction) back: (id)sender
{
if ([self.delegate respondsToSelector:isFinishedSelector])
[self.delegate performSelector:isFinishedSelector];
}
#end

How to call viewDidLoad after [self dismissModalViewControllerAnimated:YES];

Okay. If you have two viewControllers and you do a modal Segue from the first to the second, then you dismiss it with [self dismissModalViewControllerAnimated:YES]; it doesn't seem to recall viewDidLoad. I have a main page (viewController), then a options page of sorts and I want the main page to update when you change an option. This worked when I just did a two modal segues (one going forward, one going back), but that seemed unstructured and may lead to messy code in larger projects.
I have heard of push segues. Are they any better?
Thanks. I appreciate any help :).
That's because the UIViewController is already loaded in memory. You can however use viewDidAppear:.
Alternatively, you can make the pushing view controller a delegate of the pushed view controller, and notify it of the updates when the pushed controller is exiting the screen.
The latter method has the benefit of not needing to re-run the entire body of viewDidAppear:. If you're only updating a table row, for example, why re-render the whole thing?
EDIT: Just for you, here is a quick example of using delegates:
#import <Foundation/Foundation.h>
// this would be in your ModalView Controller's .h
#class ModalView;
#protocol ModalViewDelegate
- (void)modalViewSaveButtonWasTapped:(ModalView *)modalView;
#end
#interface ModalView : NSObject
#property (nonatomic, retain) id delegate;
#end
// this is in your ModalView Controller's .m
#implementation ModalView
#synthesize delegate;
- (void)didTapSaveButton
{
NSLog(#"Saving data, alerting delegate, maybe");
if( self.delegate && [self.delegate respondsToSelector:#selector(modalViewSaveButtonWasTapped:)])
{
NSLog(#"Indeed alerting delegate");
[self.delegate modalViewSaveButtonWasTapped:self];
}
}
#end
// this would be your pushing View Controller's .h
#interface ViewController : NSObject <ModalViewDelegate>
- (void)prepareForSegue;
#end;
// this would be your pushing View Controller's .m
#implementation ViewController
- (void)prepareForSegue
{
ModalView *v = [[ModalView alloc] init];
// note we tell the pushed view that the pushing view is the delegate
v.delegate = self;
// push it
// this would be called by the UI
[v didTapSaveButton];
}
- (void)modalViewSaveButtonWasTapped:(ModalView *)modalView
{
NSLog(#"In the delegate method");
}
#end
int main(int argc, char *argv[]) {
#autoreleasepool {
ViewController *v = [[ViewController alloc] init];
[v prepareForSegue];
}
}
Outputs:
2012-08-30 10:55:42.061 Untitled[2239:707] Saving data, alerting delegate, maybe
2012-08-30 10:55:42.064 Untitled[2239:707] Indeed alerting delegate
2012-08-30 10:55:42.064 Untitled[2239:707] In the delegate method
Example was ran in CodeRunner for OS X, whom I have zero affiliation with.

Adding navigation controller on a view controller subclass

How do you add a navigation controller on a newly created view controller? i've search everywhere but all the tutorials are from creating a navigation controller project.
Anyone can lead mo to a tutorial that creates a navigation controller using a view controller subclass?
What i'm doing so far:
I created a UIViewController Project, and i have something like this to go to another view controller, with a navigation controller.
NavController *view=[[NavController alloc] initWithNibName:nil bundle:nil];
view.modalTransitionStyle=UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:view animated:YES];
[view release];
Added a new view controller subclass.
Add > New File > UIViewController subclass with nib
on NavController.h
#import <UIKit/UIKit.h>
#interface NavController : UIViewController {
IBOutlet UIWindow *window;
IBOutlet UINavigationController *navCon;
}
#property (nonatomic, retain) UIWindow *window;
#property (nonatomic, retain) UINavigationController *navCon;
#end
on NavController.m
#import "NavController.h"
#implementation NavController
#synthesize window,navCon;
- (void)viewDidUnload {
[super viewDidUnload];
}
- (void)dealloc {
[window release];
[navCon release];
[super dealloc];
}
#end
i already dragged a Navigation Conrtoller and a Window on my IB, and connected window to window and the Navigation Controller to navcon outlets, but whats next?
If you're using the storyboards select your view controller then in top menu choose "editor" / "embed in" / "navigation controller".
Normally, you have to create an Navigationcontroller object inside your Appdelegate.h (like the existing window object). After that you import the h.File of a ViewController into the Appdelegate.m and init it like in the following example the menuviewcontroller.
stackoverflow
To call another view use following lines of code, so the navigationcontroller will handle everything for you.
#import Viewcontroller
ViewControllerName controllerVarName = [ViewControllerName alloc] init];
[self.navigationcontroller pushViewController:_ViewControllerName animated:YES];
Inside your specific ViewController use this line to set the title the Navigationcontroller will use:
self.title = #"titleName";

Pass a delegate method multi-levels up a navigationController stack

I have a button in a toolbar that has a popover associated with it. Within the popover, I've set up a navigationcontroller. What I'm trying to achieve is for a view controller two or three levels down in the navigationcontroller stack to change the state of the button that originally called the popover. I've managed to do this, but it requires a couple of delegates and seems very clunky; my reason for posting here is to figure out if there is a more elegant and efficient solution.
So, to get started:
//ProtocolDeclaration.h
#protocol ADelegate <NSObject>
- (void)changeButtonState;
#end
#protocol BDelegate <NSObject>
- (void)passTheBuckUpTheNavChain;
#end
Then, for my MainController that holds the button:
// MainController.h
#import "A_TableController.h"
#import "ProtocolDeclaration.h"
#class A_TableController;
#interface MainController : UIViewController <ADelegate>
...
#end
// MainController.m
- (IBAction)buttonPressed:(id)sender {
A_Controller *ac = [[[A_Controller alloc] init] autorelease];
ac.ADelegate = self;
UINavigationController *nc = [[[UINavigationController alloc] initWithRootViewController:ac] autorelease];
UIPopoverController *pc = [[[UIPopoverController alloc] initWithContentViewController:nc] autorelease];
[pc presentPopoverFromBarButtonItem...]
}
// ADelegate Method in MainController.m
- (void)changeButtonState
{
self.button.style = ....
}
Now, for A_Controller, my rootViewController for my navController:
//A_Controller.h
#import "B_Controller.h"
#import "ProtocolDeclaration.h"
#class B_Controller;
#interface A_Controller : UITableViewController <BDelegate>
{
id<ADelegate> delegate;
...
}
#property (assign) id<ADelegate> delegate;
...
#end
//A_Controller.m
//In the method that pushes B_Controller onto the stack:
B_Controller *bc = [[[B_Controller alloc] init] autorelease];
bc.BDelegate = self;
[self.navigationController pushViewController:bc animated:YES];
//In the BDelegate Method in A_Controller:
- (void)passTheBuckUpTheNavChain
{
[ADelegate changeButtonState];
}
Lastly, in B_Controller:
//B_Controller.h
#import "ProtocolDeclaration.h"
#interface A_Controller : UITableViewController
{
id<BDelegate> delegate;
...
}
#property (assign) id<BDelegate> delegate;
...
#end
//B_Controller.m
//Where it's necessary to change the button state back up in MainController:
[BDelegate passTheBuckUpTheNavChain];
Now, this works, but it seems like a sort of Rube-Goldberg-ish way of doing it. I tried init'ing both A_Controller and B_Controller in MainController and setting B_Controller's delegate to MainController right there, and then using a NSArray of the two viewcontrollers to set the navcontroller stack, but it really messed up the way the viewcontrollers appeared in the navcontroller: I'd get a back button even on the rootviewcontroller of the navcontroller and you could just keep clicking Back and going round and round the navcontroller stack instead of stopping at the root. Any ideas on a better way to do this?
If you want to decouple the view controllers you could define a notification, and just post that one.
This way only the root view controller that receives the notification needs to know about the deep nested view controller.
Define the notification like this:
// In .h
extern NSString* const BlaControllerDidUpdateNotification;
// In .m
NSString* const BlaControllerDidUpdateNotification = #"BlaControllerDidUpdateNotification";
The deeply nested controller (BlaController) need to post the message like this:
[[NSNotificationCenter defaultCenter]
postNotificationName:BlaControllerDidUpdateNotification
object:self];
And the root view controller need to act on it with something like this:
// In init or the like:
[[NSNotificationCenter defaultCender]
addObserver:self
selector:#selector(blaControllerDidUpdateNotification:)
name:BlaControllerDidUpdateNotification
object:nil];
// And then define this method:
-(void)blaControllerDidUpdateNotification:(NSNotification*)notification {
// Update UI or whatever here.
}