How to send data to the RootViewController - iphone

Here is my problem:
I have a basic application with a UIViewController embedded in a NavigationController. It is also the RootViewController of the application. From there I have a push segue to a normal UIViewControllerand a second push segue to a UITableViewController with its own UIViewController for the detailed view.
In the root view there is an instance of the class whose purpose is to send message with a define protocol.
In the table view the user will select the type of message he wants to send and in the detail view the content of that particular type of message.
Now the user has specified everything and I want him to push a "send" button. That button must do two things: pop back to the root view and send the user defined message by the protocol class instance.
I can do the pop back just fine with:
[self.navigationController popToRootViewControllerAnimated:true];
but I have no idea how to send the message (a class instance) back to the root view. The application is still fresh so I can completely change the structure if this one is not correct.
The best for me would be to access the protocol class instance from everywhere (I will need it in the other UIViewController) but I am not sure how to do that so that's why I thought of sending the message back to the root view.
If you know how to do one of the two above please give me a hand!
Cheers.
EDIT: Technically the NavigationController is the initial ViewController so I am not really sure who is the RootViewController anymore.

First: You could do: (only works when your view has been added to a the window)
[self.view.window.rootviewcontroller doSomething];
Second option is to define a property on your appDelegate:
#property (nonatomic, strong) UIViewController *root;
And call it through:
AppDelegate *appDelegate= (YourAppDelegateClass *) [[UIApplication sharedApplication] delegate];
[appDelegate.root doSomething];

you can Create an application object and assign the message to it. and use it on your root view controller. if i understood your question correctly this might help.

You could try:
UIApplication *myApp = [UIApplication sharedApplication];
UIWindow *frontWindow = [myApp.windows lastObject];
UIViewController *myRootViewController = frontWindow.rootViewController;

You Can also send Notification whenever you move to root view controller
by adding observer to it.

one way is to use protocol and other way is to pass your root view controller instance to your tableviewcontroller(via view controller) using your custom init method like:
UIViewController.m
- (id)initWithRoot:(id)rootInstance
withNibNameOrNil:(NSString *)nibNameOrNil
bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
self.root = (RootView *)rootInstance;
}
}
RootViewController.m
viewcontrollerInstance = [[viewcontroller alloc] initWithRoot:self withNibNameOrNil:#"viewcontroller"
bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:viewcontrollerInstance animated:YES];
UITableViewController.m
- (id)initWithRoot:(id)rootInstance
withNibNameOrNil:(NSString *)nibNameOrNil
bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
self.root = (RootView *)rootInstance;
}
}
ViewController.m
tableViewInstance = [[tablecontroller alloc] initWithRoot:self withNibNameOrNil:#"tablecontroller"
bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:tableViewInstance animated:YES];
UITableViewController.m
now on table view controller use your root instance(come from view controller) to call the function of root view controller like:
[self.root displayMessage:message];
sorry for the typo. hope this will help.

Related

ViewDidLoad executes slowly while pushing viewcontroller

I was trying to push a viewcontroller B into navigation controller from A and then assigning some properties of B in A.
In this case, the assigning of properties was done and then viewDidLoad of viewcontroller A was executed.
Here, assigning properties in A should be done only after viewDidLoad of A has done.
For example,
[b.navController pushViewController:a animated:YES];
a.status = #"loaded";
Here, status was assigned first and then viewDidLoad of A was executed.
This happens only in iOS 7 whereas in iOS6 it works fine.
Can anyone please let me know where the problem is?
UPDATE: For me in some cases in iOS7, Push view is not working. How cna I debug and fix it?
Just access the viewcontroller.view (set any thing immediately after the alloc) property after the alloc init;
Which will loadview/viewdidload.
See Apple Documentation
In my experience, a UIViewController view is loaded lazily, no matter which iOS version you're working on. If you want to trigger a view load, and therefore its UIViewController viewDidLoad, you should access the view after the UIViewController is allocated. For example:
UIViewController *aViewController = [[CustomViewController alloc] init];
[aViewController view];
Make sure you don't code it as
aViewController.view
since that would generate a compiler warning.
So, in your case it would have to be something like this:
...
CustomViewController *a = [[CustomViewController alloc] init];
[b.navController pushViewController:a animated:YES];
[a view];
a.status = #"loaded";
Let me know if you have further problems with it.
You can know when a View Controller has been pushed onto the stack by implementing the UINavigationControllerDelegate and setting yourself as the delegate self.navigationController.delegate = self; then you will get this callback after every push
navigationController:didShowViewController:animated:
So you can check if the shown viewController is the one your interested in, then set your a.status.
I would suggest you call a delegate method once the view is loaded.
Set the delegate to be controller B.
and after viewDidLoad finishes (in controller A) call the delegate method. You can even pass parameters as you wish to the delegate.
Here's some example code:
Controller B:
a.delegate = self;
[b.navigationController pushViewController:a animated:YES];
Implement the delegate method:
- (void)controllerIsLoaded:(ControllerA *)controllerA status:(NSString *)status
{
a.status = status;
}
Controller A .h file:
#class ControllerA;
#protocol ControllerADelegate <NSObject>
- (void)controllerIsLoaded:(ControllerA *)controllerA status:(NSString *)status;
#end
#interface ControllerA : UIViewController
#property (nonatomic, weak) id <ControllerADelegate> delegate;
Controller A .m file:
- (void)viewDidLoad:(BOOL)animated
{
[super viewDidLoad:animated];
/* your viewDidLoad code here... */
if ([_delegate respondsToSelector:#selector(controllerIsLoaded:status)])
[_delegate controllerIsLoaded:self status:#"Loaded"];
}
Turn off animation for ios7, in my case its work
[b.navController pushViewController:a animated:NO];
a.status = #"loaded";
No documentation provides enough information to know exactly when viewDidLoad would be called.
UIViewController's documentation just says this
This method is called after the view controller has loaded its view hierarchy into memory
I would suggest that you create a custom initializer like this
- (id)initWithStatus:(NSString *)status {
}
But, if you are trying to use this variable to check if the viewController's view has 'loaded' or not, it may not be possible to do that because the pushViewController or presentViewController methods are not guaranteed to be synchronous.
Even in iOS 6, there was no explicit guarantee that the view would be loaded as soon as that method returned.
Please write the code in viewWillAppear method instead of viewDidLoad in next class i.e. where you are pushing the object to
-(void)viewWillAppear:(BOOL)animated
{
}
I'm understand of your question like this:
B *b = [[B alloc] init];
b.status = #"loaded";
[self.navigationController pushViewController:b animated:Yes];
If you want to pass a value from one controller to another means, you have to assign a value before using pushViewController method.

Segues not working programmatically

I migrated a project from using XIB's to Storyboard, according to these instructions: https://stackoverflow.com/a/9708723/2604030
It went good.
But I can't make the segues work programmatically, and I need to use them this way, because I have 2 buttons that link to the same ViewController, with different types, hope you understand why from this image.
There are 2 difficulty mode buttons. The code I use:
`- (IBAction)btnNormalAct:(id)sender {
LevelController *wc = [[LevelController alloc] initWithNibName:#"LevelController" type:0];
[self.navigationController pushViewController:wc animated:YES];
}
- (IBAction)btnTimedAct:(id)sender {
LevelController *wc = [[LevelController alloc] initWithNibName:#"LevelController" type:1];
[self.navigationController pushViewController:wc animated:YES];
}`
This worked when I used XIB's, and I am sure I linked everything correctly in the storyboard's VCs. The seagues works if I make them from the storyboard. But how can I manage this situation.
ALSO: are those lines good when changing from XIB's to Storyboard? Is that the right way to do this change (the way shown in the link above)?
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
}
You can use the PrepareForSegue method to set things on the incoming view controller before it is called:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Make sure your segue name in storyboard is the same as this line
if ([[segue identifier] isEqualToString:#"YOUR_SEGUE_NAME_HERE"])
{
// Get reference to the destination view controller
LevelController *vc = [segue destinationViewController];
// Pass any objects to the view controller here, like...
[vc setType:1];
}
}
Don't use the button actions. Connect the segues to the buttons and give the segues unique identifiers. Then implement prepareForSegue:sender: in your controller. When the method fires, check the seque identifier and set the appropriate type on the `destinationViewController'.
When using a storyboard you should instantiate your controllers from the storyboard rather than using initWithNibName:bundle:. This is done by giving each view controller a unique identifier and then calling instantiateViewControllerWithIdentifier: (or, for the initial view controller, just instantiateInitialViewController) on the storyboard which you can get from the current controller (or if required with storyboardWithName:bundle:).

Replacing View Controllers programmatically?

I'm using a library (ViewDeck) to have a sliding view. I believe my problem would apply to any other library.
I have the following code in my Initial View Controller:
#import "InitialViewController.h"
#implementation InitialViewController
- (id)initWithCoder:(NSCoder *)aDecoder
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard"
bundle:nil];
self = [super initWithCenterViewController:[storyboard
instantiateViewControllerWithIdentifier:#"middleViewController"]
leftViewController:[storyboard
instantiateViewControllerWithIdentifier:#"leftViewController"]];
return self;
}
#end
There are times when I need to present a ViewController modally and then, depending on the user's input, swap the current View Controllers;
For example, I would need to swap middleViewController for mainViewController and leftViewController for menuViewController.
I can't come up with a way for doing this. I thought of using delegates on the Initial View Controller, but I believe that delegate code is lost when I call self = [super initWith...] on the code above.
Is there a way to swap these controllers? Should I be replacing the rootViewController? If so, how?
I ended up using SWRevealViewController instead of ViewDeck. Their implementation is, I believe, cleaner and easier to customize.

How can one create a generic (container)controller that will recieve as input another controller

In the app im creating there are many pages that look mostly the same with some part which is different. To handle this kind of situation i created a container controller that contains a subview. I want this subview to be filled by the contents of another controller (and its associated nib) which i will created dynamically as needed based on context.
I have the following method somewhere
- (void) someAction {
UIViewController* contentController = [[MyContentController alloc] init];
UIViewController* containerController = [[MyContainerController alloc] initWithContentController:contentController];
[navigationController pushViewController:pageController animated:YES];
[contentController release];
[containerController release];
}
In MyContainerController.m i retain the controller in a property
- (id)initWithContentController:(UIViewController *)aContentController {
if ((self = [super initWithNibName:#"MyContainerController" bundle:nil])) {
contentController = aContentController;
}
return self;
}
Later in viewDidLoad i do the following
- (void)viewDidLoad {
[super viewDidLoad];
[contentViewContainer addSubview:contentController.view];
}
contentViewContainer is the view that's supposed to hold the page specific info.
Unfortunatly this fails with EXC_BAD_ACCESS.
The funny thing is that if i alloc and init the content controller from within viewDidLoad everything works. It seems that i cant pass a contoller i allocated from another place.
Can anyone assist.
Since you are releasing contentController in the actionMethod
you have to retain contentController in you init method
- (id)initWithContentController:(UIViewController *)aContentController {
if ((self = [super initWithNibName:#"MyContainerController" bundle:nil])) {
contentController = [aContentController retain];
}
return self;
}
But, why do you need this? Controllers are supposed to control views and no other controllers. If you think you really need that then you want to use UINavigationController or UITabBarController maybe.
You can also load views without a controller (see here)
I personally think that having UIViewControllers inside of simple UIViewController is not a preferable approach
Hope it helps

Shake to open view modally

I have my 'shake' working fine (using motionEnded), based off of Apple's GLPaint code. When the user shakes the device (running 3.0 and up) I want to open a view controller modally using presentModalViewController.
In my appdelegate I have the notification (as per the GLPaint sample code):
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(shakeToOpenHiddenScreen) name:#"shake" object:nil];
In my shakeToOpenHiddenScreen I just want to open view 'x' modally but I don't think that my appdelegate will respond to presentModalViewController.
Is there a way around this?
To use presentModalViewController you have to use it from a UIViewController class, or subclass:
For example:
//RootViewController.m
[self.navigationController presentModalViewController:loginRegView animated:YES];
You can way around this problem by defining a navigation controller into your app delegate:
//yourApp_comAppDelegate.h
UINavigationController *nav;
...
#property(nonatomic,retain) UINavigationController *nav;
and synthesize it
#syntetize nav;
To use presentModalViewController you have to use it from a UIViewController class, or subclass:
For example:
//RootViewController.m
[self.navigationController presentModalViewController:loginRegView animated:YES];
You can way around this problem by defining a navigation controller into your app delegate:
//yourApp_comAppDelegate.h
UINavigationController *nav;
...
#property(nonatomic,retain) UINavigationController *nav;
synthesize it
//yourApp_comAppDelegate.m
#synthesize nav;
and now you can use the method:
//yourApp_comAppDelegate.m
[nav presentModalViewController:yourView animated:YES];
but, first you have to assign it somewhere, i will do it in the RootViewController
//RootViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
app = (yourApp_comAppDelegate *) [[UIApplication sharedApplication] delegate];
app.nav = self.navigationController
}
It should work, let me know :)
It is a method on UIViewController, so you should either have access to a saved view controller from your appDelegate, or else set up the notification to call one (addObserver:someVC).
"shake" isn't a standard notification name, so there should be some code elsewhere in your app that posts this notification, presumably also copied from the GLPaint sample.