Jump between different viewControllers using UIButton? - objective-c

I have a button that open another viewController(familyView) when clicked.
In familyView there is another button which suppose to bring me back to the mainViewController(ViewController.xib) but I don't know how to call the main viewController.
My method to call familyView
UIViewController* familyView = [[UIViewController alloc] initWithNibName:#"familyView" bundle:[NSBundle mainBundle]];
[self.view addSubview:familyView.view];
I hope you could help on how to call the main ViewController ? do I have to use the same method to call it? like this I mean:
UIViewController* mainView = [[UIViewController alloc] initWithNibName:#"viewController" bundle:[NSBundle mainBundle]];
[self.view addSubview:mainView.view];
If yes, is there a better way to implement this? in my demo project, I'm trying to make 7 views full with data and a button to go back and forth.
EDIT:
If I use UIView would that be best for me instead of using different viewControllers with their implementations and interfaces files?
My project will have views, and each view has data on it parsed from a different html page.

There are two method that can be used.
UINavigationController
Delegates
From your question it seems that a UINavigationController is the best option but I will show you both.
UINavigationController
When you load your mainViewController from your app delegate your going to need to wrap it in a nav controller like so:
AppDelegate.h
#property (strong, nonatomic) UINavigationController *navController;
AppDelegate.m
#synthesize navController = _navController;
//in didFinishLaunchingWithOptions:
UIViewController *mainViewController = [[MainViewController alloc] initWithNibName:#"MainViewController" bundle:nil];
navController = [[UINavigationController alloc] initWithRootViewController:mainViewController];
self.window.rootViewController = nav1;
[self.window makeKeyAndVisible];
Now in your MainViewController you have the convince of UINavigationController.
When you want to push to a child from a parent you can simply do:
ChildViewController *child = [[ChildViewController alloc]...];
[self.navigationController pushViewController:child animated:YES];
If you in your ChildViewController and want to go back simply do:
[self.navigationController popViewControllerAnimated:YES];
This is the "Drill Down" technique.
(I know "Drill Down" has more meaning than simply that but it provides a good frame of reference.)
Delegate
Now the other method that you have is to setup delegates between the classes. So if your in childView and need to call your parent, you will have a channel to do so.
In your MainViewController.h setup it like so:
#import <UIKit/UIKit.h>
//This is our delegate
#protocol TalkToParentDelegate <NSObject>
//This is our delegate method
- (void)helloParent;
#end
#interface MainViewController : UIViewController <TalkToParentDelegate>
...
..
#end
In your MainViewController.m make sure add the delegate method.
- (void)helloParent {
NSLog(#"Hello child, let me do something here");
}
In your ChildViewController.h setup it like so:
#import <UIKit/UIKit.h>
//Add header of class where protocol was defined
#import "MainViewController.h"
#interface ChildViewController : UIViewController
//Create a property we can set to reference back to our parent
#property (strong, nonatomic) id <TalkToParentDelegate> delegate;
#end
Now, in your MainViewController.m , whenever you present your ChildViewController do this:
ChildViewController *child = [[ChildViewController alloc]...];
//Set the delegate reference to parent
child.delegate = self;
//present the view
Last but not least, no when you in your child you can call methods on your parent (MainViewController) like so:
[self.delegate helloParent];
So here are two methods that you can use.
I would like to note however, you can use these together. Say you had a UINavigationController but still needed a child to talk to its parent, you can setup a delegate so that's possible.
Hope this helps.

Related

Unable to set content in NSPopover

I'm showing an NSPopover in an NSView, originating from a point on an NSBezierPath. I'm able to show the popover without a problem, but I can't seem to set the string value of the two text fields in it. The popover and the content view are both a custom subclass of NSPopover and NSViewController, respectively. The NSPopover subclass is also the NSPopover's delegate, although I don't implement any delegate methods, so I'm not sure I even need to do that.
Here is my subclass of NSViewController:
#import <Cocoa/Cocoa.h>
#interface WeightPopoverViewController : NSViewController
#end
#import "WeightPopoverViewController.h"
#interface WeightPopoverViewController ()
#end
#implementation WeightPopoverViewController
- (id)init {
self = [super initWithNibName:#"WeightPopoverViewController" bundle:nil];
if (self) {
}
return self;
}
#end
And my subclass of NSPopover:
#import <Cocoa/Cocoa.h>
#interface WeightPopoverController : NSPopover <NSPopoverDelegate> {
NSTextField *dateLabel;
NSTextField *weightLabel;
}
#property (strong) IBOutlet NSTextField *dateLabel;
#property (strong) IBOutlet NSTextField *weightLabel;
#end
#import "WeightPopoverController.h"
#implementation WeightPopoverController
#synthesize weightLabel;
#synthesize dateLabel;
#end
This is the code in my NSView subclass that opens up the popover:
#interface WeightGraphViewController () {
WeightPopoverController *popover;
WeightPopoverViewController *vc;
}
...
-(void)mouseEntered:(NSEvent *)theEvent {
// initialize the popover and its view controller
vc = [[WeightPopoverViewController alloc] init];
popover = [[WeightPopoverController alloc] init];
// configure popover
[popover setContentViewController:vc];
[popover setDelegate:popover];
[popover setAnimates:NO];
// set labels
for (id key in (id)[theEvent userData]) {
[popover.weightLabel setStringValue:[(NSDictionary*)[theEvent userData] objectForKey:key]];
[popover.dateLabel setStringValue:key];
}
// set the location
(redacted, irrelevant)
// show popover
[popover showRelativeToRect:rect ofView:[self window].contentView preferredEdge:NSMaxYEdge];
}
-(void)mouseExited:(NSEvent *)theEvent {
[popover close];
popover = nil;
}
In WeightPopoverViewController.xib, I've set the File's Owner to WeightPopoverViewController and connected the view to the custom NSView. In this xib I also have an Object set to WeightPopoverController with the dateLabel and weightLabel connected to their text fields and the contentViewController set to File's Owner.
I think where I am going wrong is likely related to how I have configured my class / instance variables for the NSPopover, but from the research I've done and documentation I've read I can't seem to crack where I've gone wrong. Any help would be appreciated.
UPDATE:
I removed the NSPopover subclass from code and from IB. I put my outlets in my NSViewController and connected them in IB. However, I'm still not able to set the string values. The following won't compile with the error "Property 'weightLabel' not found on object of type NSPopover*'".
#interface WeightGraphViewController () {
NSPopover *popover;
...
}
-(void)mouseEntered:(NSEvent *)theEvent {
vc = [[WeightPopoverViewController alloc] init];
popover = [[NSPopover alloc] init];
[popover setContentViewController:vc];
[popover.dateLabel setStringValue:#"test"];
}
I have the property definition exactly as I had it in my NSPopover subclass, but now in my NSViewController. This is actually what I had before, and since I wasn't able to set the properties from the NSViewController, I figured I needed to do it through a subclass of NSPopover. This is why I thought I am having an issue with how I have configured my class / instance variables.
You seem to be creating two popovers, one in code (popover = [[WeightPopoverController alloc] init]) and one in Interface Builder (In this xib I also have an Object set to WeightPopoverController). Have a think about what you’re trying to achieve.
I would also advise against subclassing NSPopover. I believe this is causing confusion and is unnecessary. Instead, put the outlets to your dateLabel and weightLabel in the popover’s content view controller.
I've experienced something that I think is similar. The root problem is that the "outlets" connecting your view (XIB) to your controller are not initialized until after the view has been displayed. If the controller tries to set properties on any UI controls in the view before the popover has been opened, those changes are ignored (since all the controls will be nil).
Luckily, there's an easy solution (as mentioned in this answer): just invoke the view getter on your controller, and it will force the view to initialize sooner.
In other words:
popover = [NSPopover new];
myController = [[MyViewController alloc] initWithNibName:#"MyView" bundle:nil];
popover.contentViewController = myController;
[myController view]; // force view to initialize
...set some values on myController... // works because view is now loaded
[popover showRelativeToRect: ...];

UIPopovercontroller in UISplitViewController

My detailviewcontroller is called myDetailVC , with tableVC which is just a table on the side of myDetailVC.
in myDetailVC, I have a button that is supposed to show a popoverviewcontroller. However, it crashes when I press it.
in my .h file:
#property (strong, nonatomic) UIPopoverController *masterPopOverController;
and .m:
- (IBAction)goToPDF:(id)sender
{
viewPDFViewController *MPPWV =[[viewPDFViewController alloc] initWithNibName:nil bundle:nil];
self.masterPopOverController = [[UIPopoverController alloc] initWithContentViewController:MPPWV];
[self.masterPopOverController presentPopoverFromRect:[sender frame] inView:[sender superview] permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
[self.masterPopOverController setPopoverContentSize:CGSizeMake(100, 100) animated:NO];
}
Everything is connected fine in my xib, since I tested it by making it push to a viewcontroller instead of a popover. Can someone help me with this?
First, it looks like you're loading it up with a view controller that doesn't have anything in it:
viewPDFViewController *MPPWV =[[viewPDFViewController alloc] initWithNibName:nil bundle:nil];
Try specifying a Nib name, if you have one. It's possible that you don't have a NIB for that view controller. If that's the case you probably have a designated initializer -init method that you should be calling on it. Do that instead of calling -initWithNibName:
Second, it doesn't look like you're retaining the view controller you're loading into the pop over. It's possible that they require strong references. Try retaining it via property (or an ivar):
#property (nonatomic, strong) viewPDFViewController * MMPPWV;
Then when you access it do so via self:
self.MPPWV = [[viewPDFViewController alloc] initWithNibName:nil bundle:nil];

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";

How to access other viewcontrollers variables/properties in xcode

I know similiar questions have been asked before, but please bear with me as I am totally new at Objective C (have good knowledge on C).
My case is that I have a tab bar controller with two windows. When I press a button in "second" I change a variable changeName. I want to use the value of changeName in my "first" view controller but stumbled on to some problems:
To reach the other viewcontroller I found this from SO (unfortunately I forgot where):
-(NSString *)newName{
// Create a UIStoryboard object of "MainStoryBoard_iPhone" named storyboard
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:(NSString *)#"MainStoryBoard_iPhone" bundle:(NSBundle *)nil];
// Create a UIViewController object of the wanted element from MainStoryBoard_iPhone
UIViewController *secondview = [storyboard instantiateViewControllerWithIdentifier:(NSString *)#"secondBoard"];
return secondview.changeName;
}
Which I have in my first.m. MainStoryBoard_iPhone is the name of the .xib/storyboard-file.
but no. Error says
Property 'changeName' not found on object of type 'UIViewController *'
In my second.h I have
#property (readwrite, retain) NSString *changeName;
and in second.m
#synthesize changeName;
All help is appreciated, but please keep in mind that I have only used OOP for two days.
Thank you all in advance
EDIT: And what input should I have here?
- (void)viewDidLoad
{
[super viewDidLoad];
mylabel.text = Name; // or [mylabel.text Name] or something?
}
You need to cast the view controller returned to your customized second view controller (as UIViewController does not have the property changeName).
So do the following :
// Create a UIViewController object of the wanted element from MainStoryBoard_iPhone
SecondViewController *secondview = (SecondViewController *)[storyboard instantiateViewControllerWithIdentifier:(NSString *)#"secondBoard"];
I assume SecondViewController is the name of your second controller.
In my case I wanted to pass a string from FirstViewController to set a text field in SecondViewController, so:
1- In SecondViewController.h
#property (strong, nonatomic) IBOutlet UITextField *someTextField;// this is linked to a UITextField
2- In FirstViewController.m:
#import "SecondViewController.h"
3- I will do this after a press of a button in the FirstViewController:
SecondViewController *SecondView = [[SecondViewController alloc]
initWithNibName:#"SecondViewController" bundle:nil];
//set fields values
SecondView.someTextField.text = #"HeLLO from First";
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController: SecondView];
//show AdView
[self.navigationController presentViewController:nav animated:YES completion:nil];
4- This is it!
5- There is a case, if I'm in FirstViewController and wanted to set a string property in the SecondViewController with the same way, the string won't get the value till you press a some kinda button in SecondViewController, it won't get it like in viewDidLoad for example! don't know why.

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.
}