I have an application with tab bar and 3 different View Controller. one of these imanage a UItableView that I designed through Interface Builder (storyboard) and I set the it's view controller class in the Inspector -> inspector identity -> and I set class field there, hence, I have no control when this view controller get instantiated, as it's done through storyboard when user click on tab bar. notice, i'm new to objective C and iOS programming.
the issue that I'm facing, i'm also using remote notification. hence, when I receive a remote notification message in "didReceiveRemoteNotification" in the AppDelgate class. I need to update UI interface (above ViewController), but the issue I don't have a reference (pointer) to this ViewController from my AppDelgate class ( or do I?). the problem this ViewController instantiated by storyboard nor programmatically, otherwise I could have kept a reference to it.
I did some reading and I understand I could do this communication via NSNotification, but I think this will be an overkill for a problem that maybe arise just because I'm new to this and I don't have full understanding of iOS development.
Thanks,
NSNotifications are easy to use and are probably the right solution.
In the app delegate that needs to send the message, just put:
[[NSNotificationCenter defaultCenter] postNotificationName:#"MyNotification" object:someObjectYouWantToPassCouldBeAppDelegateOrRemoteNotificationObjectOrAnything];
In the view controller that is receiving the message, put:
-(void)viewDidLoad
{
[super viewDidLoad];
//you can add as many of these as you like to handle different notifications
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleNotification:) name:#"MyNotification" object:nil];
}
-(void)viewDidUnload
{
//make sure you remove every observer you've added here
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"MyNotification" object:nil];
[super viewDidUnload];
}
-(void)dealloc
{
//clean up in case viewDidUnload wasn't called (it sometimes isn't)
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
//use a different handler method for each notification
//the method name should match the selector in your observe call in viewDidLoad
-(void)handleNotification:(NSNotification *)notification
{
WhateverClassOfObjectYouWerePassing *object = notification.object;
//now you have a reference to the object that was passed from your app delegate
}
For different methods you want to call, just and a new notification name and a new handler method.
Your app Delegate will have a window property which points to the apps window.
Window Property has a -rootViewController property/method.
For your Tab Based Application it would return you the TabViewController.
Each TabViewController have a method -(NSArray *)viewControllers which returns the ViewControllers inside the Tab. These are arranged in the order.
To Access your applications AppDelegate use [[UIApplication sharedApplication]delegate]
Once you have these viewcontrollers you would know which all viewController these are since you have added it in the XIB files. and can perform your methods
1. Communicate two ViewControllers
If you want to communicate two ViewControllers, you should use #protocol as Apple recommended:
ViewController1.h
#interface ViewController1 : UIViewController<ViewController2Delegate, ViewController2DataSource>
#end
ViewController1.m
- (IBAction)goToViewController2:(id)sender{
if(viewController2 == nil) {
ViewController2 *viewController = [[ViewController2 alloc]
initWithNibName:#"View2" bundle:[NSBundle mainBundle]];
viewController2 = viewController;
}
//...
viewController2.delegate = self;
viewController2.dataSource = self;
[self.navigationController pushViewController:viewController2 animated:YES];
}
- (NSString)viewController:(ViewController2 *)controller itemForSomethingAtIndex:(NSInteger)index{
//Send to viewController2 what it needs
return [items objectAtIndex: index];
}
- (void)viewController:(ViewController2 *)controller didFinishEnteringItem:(NSString *)item{
//Handle the result from the viewController2
NSLog(#"result: %#", item);
}
ViewController2.h
#import <UIKit/UIKit.h>
// Define your delegate methods to return items to the delegate of this viewController
#protocol ViewController2Delegate <NSObject>
- (void)viewController:(ViewController2 *)controller didFinishEnteringItem:(NSString *)item;
#end
// Define your dataSource methods to send items from the dataSource to this viewController
#protocol ViewController2DataSource <NSObject>
- (NSString)viewController:(ViewController2 *)controller itemForSomethingAtIndex:(NSInteger)index;
#end
#interface ViewController2 : UIViewController
#property (nonatomic) id <ViewController2Delegate> delegate;
#property (nonatomic) id <ViewController2DataSource> dataSource;
#end
ViewController2.m
#import "ViewController2.h"
#interface ViewController2 ()
#end
#implementation ViewController2
#synthesize //...;
- (void)someMethod {
//Get someThing from controller1
NSString *item = [dataSource viewController: self itemForSomethingAtIndex:0];
//Return someThing to controller1
[delegate viewController: self didFinishEnteringItem: item];
}
2. Communicate backgroundTask with viewController
If you want to communicate a background task or handle a push notification, use #NickLockwood's answer. But this dont gonna work if the viewController its not loaded. In this case you should handle that in the AppDelegate:
//Get the appDelegate instance
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
//And call your custom method to show what it needs
[appDelegate customMethod];
Your custom method should call controllers consecutevly like:
AppDelegate > RootController > ViewController1 > ViewController2 > myMethod
//do something if viewController2 is visible to the user or push it before do something.
//if you use navigation controller, then you need to ask for the position and className
Related
I am trying to implement background refresh in my app. I have a method refreshMessages that is inside one of my UIViewControllers (I am using a UINavigationController). I need to access this controller from AppDelegate.m.
Usually I would do something like this:
UINavigationController *navigationController = [self.window.rootViewController navigationController];
to get the navigation controller and I can then access them from there. But surely this won't work now. The app is in the BACKGROUND and no window is showing. Any thoughts on how to get around this? Thanks!
Here is my background refresh:
-(void)application:(UIApplication *)application
performFetchWithCompletionHandler:
(void (^)(UIBackgroundFetchResult))completionHandler {
NSLog(#"Doing the background refresh");
UINavigationController *navigationController = [self.window.rootViewController navigationController];
NSLog(#"Number of controllers is %d", [[navigationController viewControllers] count]);
//There are zero controllers?!!! Must be because no window is showing
completionHandler(UIBackgroundFetchResultNewData);
}
Yes, you are right. The window will be nil since it will no longer be in the view hierarchy.
A cheeky solution would be to hold the reference of the view controller in the AppDelegate by creating a property in it. You can assign this when the application goes to the background by subscribing to the relevant notification in the view controller.
Edit: Code added
In your AppDelegate.h
#property (nonatomic, weak) YourViewController *yourViewController;
In YourViewController.m, somewhere in viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
appWillResignActive's implementation
-(void)appWillResignActive:(id)sender {
YourAppDelegate *delegate = [UIApplication sharedApplication].delegate;
delegate.yourViewController = self;
}
In your completion handler
[self.yourViewController refreshMessages]
Also, make sure you remove the observer once the view controller is deallocated.
I'm fairly new to Objective-C, programming. The one thing, I'm currently putting up with is passing a value from the first ViewController to the next one.
I've read this entry here and it didn't help me. Which is funny, since his answer has nearly 500 votes, so I must be the problem. What I did was the following.
I opened my PreviewViewController.m and added the following line #import "MainViewController.h" since I wanted to pass a value from the PreviewViewController to the `MainViewController. Then, when I switch the layouts ( which successfully works ) I want to pass a value.
MainViewController *mainViewController = [[MainViewController alloc] initWithNibName:#"MainViewController" bundle:nil];
mainViewController.userId = #"539897197";
As you can see, I want to pass the userId. For that, I also created a property in the MainViewController.h
#property ( nonatomic, strong ) NSString *userId;
Now, In my MainViewController.m I want to access the userId. But when I log it, the console tells me it is null. However, when I set the variable right before the NSLog it works, so it seems like the passing is the problem.
Additionally in the MainViewController.m I have the following line
#synthesize userId = _userId;
but even when I removed that line and changed the NSLog to NSLog(#"%#",self.userId); the same problem occurred.
How can I successfully pass the variables? Which step am I doing wrong?
EDIT
This is how I switch the layouts
UIViewController *viewController = [[MainViewController alloc]init];
MainViewController *mainViewController = [[MainViewController alloc] initWithNibName:#"MainViewController" bundle:nil];
mainViewController.userId = #"539897197";
[self presentViewController:viewController animated:YES completion:NULL];
Why not create a custom initializer and pass it in that way? Something like
- (id)initWithUserId:(NSString *)aUserId {
self = [super initWithNibName:#"MainViewController" bundle:nil];
if (self) {
self.userId = aUserId
}
return self;
}
Then you can just do:
MainViewController *mvc = [[MainViewController alloc] initWithUserId:#"1234"]
[self presentViewController:mvc animated:YES completion:nil];
If you are using storyboard then you should use prepareForSegue: method to pass data between view controllers. First Create the segue from your PreviewViewController to MainViewController, just control drag from your view controller to next viewcontroller to create segue. Use UINavigationController if you are using push segue. Use this method to segue and pass data
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:#"yourSegueIndentifier"]){
MainViewController *mvc = (MainViewController *) segue.destinationViewController;
mvc.userId = #"539897197";;
}
}
UIViewController *viewController = [[MainViewController alloc]init];
MainViewController *mainViewController = [[MainViewController alloc] initWithNibName:#"MainViewController" bundle:nil];
mainViewController.userId = #"539897197";
[self presentViewController:mainViewController animated:YES completion:NULL];
use mainViewController rather than viewController when presenting the view controller
Here we have two ways for passing data.
First is Custom Delegate
Second is NSNotification
Here we have two view controllers.
ViewController
and
SecondViewController
in SecondViewController
.h
#import <UIKit/UIKit.h>
#class SecondViewController;
#protocol SecondViewControllerDelegate <NSObject>
- (void)secondViewController:(SecondViewController *)secondViewController didEnterText:(NSString *)text;
#end
#interface SecondViewController : UIViewController
#property (nonatomic, assign)id<SecondViewControllerDelegate> delegate;
#property (nonatomic, strong) IBOutlet UITextField *nameTextField;//It must connect as outlet connection
- (IBAction)doneButtonTapped:(id)sender;
#end
.m
#import "SecondViewController.h"
#interface SecondViewController ()
#end
#implementation SecondViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
//Either use NSNotification or Delegate
- (IBAction)doneButtonTapped:(id)sender;
{
//Use Notification
[[NSNotificationCenter defaultCenter] postNotificationName:#"passingDataFromSecondViewToFirstView" object:self.nameTextField.text];
//OR Custom Delegate
[self.delegate secondViewController:self didEnterText:self.nameTextField.text];
[self.navigationController popViewControllerAnimated:YES];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
in ViewController
.h
#import <UIKit/UIKit.h>
#import "SecondViewController.h"
#interface ViewController : UIViewController<SecondViewControllerDelegate>
#property (nonatomic, strong) IBOutlet UILabel *labelName; //You must connect the label with outlet connection
- (IBAction)gotoNextView:(id)sender;
#end
.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//addObserver here...
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textFromPreviousViewControllerNotificationReceived:) name:#"passingDataFromSecondViewToFirstView" object:nil];
// Do any additional setup after loading the view, typically from a nib.
}
//addObserver Method here....
- (void)textFromPreviousViewControllerNotificationReceived:(NSNotification *)notification
{
// set text to label...
NSString *string = [notification object];
self.labelName.text = string;
}
- (IBAction)gotoNextView:(id)sender;
{
//If you use storyboard
SecondViewController *secondViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"SecondViewController"];
//OR If you use XIB
SecondViewController *secondViewController = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
secondViewController.delegate = self;
[self.navigationController pushViewController:secondViewController animated:YES];
}
//Calling custom delegate method
- (void)secondViewController:(SecondViewController *)secondViewController didEnterText:(NSString *)text
{
self.labelName.text = text; //Getting the data and assign the data to label here.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
For your understanding the code I create a simple passing data from one second view controller to first view controller.
First we navigate the view from first view controller to second view controller.
After that we send the data from second view controller to first view controller.
NOTE : You can either use NSNotification or Custom Delegate method for sending data from One View Controller to Other View Controller
If you use NSNotification, you need to set the postNotificationName for getting data in button action method.
Next you need to write addObserver in (sending data to your required View Controller) ViewController and call the addObserver method in same View Controller.
If you use custom delegate,
Usually we go with Custom Protocol Delegate and also we need to Assign the delegate here.
Very importantly we have to set the Custom Delegate Method in the Second View Controller.Because where we send the data to first view controller once we click the done button in second view controller.
Finally we must call the Custom Delegate Method in First View Controller, where we get the data and assign that data to label.Now you can see the passed data using custom delegate.
Likewise you can send the data to other view controller using Custom Delegate Methods
I tried and got the solution for passing the value between viewcontroller.
MainViewController *mainVC = [[self storyboard] instantiateViewControllerWithIdentifier:#"MainViewController"];
mainVC.userId = #"539897197";
[self presentViewController:mainVC animated:YES completion:Nil];
or using this way . You don`t use the storyboard use the following its working fine
MainViewController *mainVC = [[MainViewController alloc] init];
mainVC.userId = #"539897197";
[self presentViewController:mainVC animated:YES completion:Nil];
I am trying to implement JASidePanels where CenterViewController slides and reveals LeftViewControler which contains a TableView. Once the user selects a row in the TableView, I'd like the CenterView to regain it's position by sliding back and also have a method (within CenterViewController) do be called with a parameter from LEftViewController to update the CenterView. Can somebody please help my with this?
Thank you.
There are two parts to solving your problem:
first showing center panel when user select a row:
[self.viewController showCenterPanelAnimated:YES];
// add this method to your tableView row
passing a message back to center panel with new instruction, this can be done by creating delegate or notification. to keep it simple i will use Notification:
in your left panel class:
// Add to your tableView row method
NSNotification *msg = [NSNotification notificationWithName:#"leftPanelMsg" object:#"Hello"];
[[NSNotificationCenter defaultCenter] postNotification:msg];
in your center panel class:
add Observer in viewDidLoad and another method when message is passed back:
- (void)viewDidLoad {
[super viewDidLoad];
// method listen to meesssage with specfic name and calls selector when it get hit
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(msgResponder:) name:#"leftPanelMsg" object:nil];
}
-(void)msgResponder:(NSNotification *)notification {
NSLog(#"name:%# object:%#", notification.name, notification.object);
}
If you want to use delegates, then you have to add a protocol to the LeftViewController.h file, e.g.
#protocol LeftViewControllerDelegate <NSObject>
-(void)useThisValue:(NSString *)value;
#end
#interface LeftViewController : UITableViewController
#property (weak, nonatomic) id <LeftViewControllerDelegate> delegate;
#end
In your tableView:didSelectRowAtIndexPath: method, you can then add
[self.delegate useThisValue:menu[indexPath.row]];
Your CenterViewController will become the delegate of the LeftViewController, so in your CenterViewController.h file, import the LeftViewController.h and add:
#interface CenterViewController : UIViewController <LeftViewControllerDelegate>
- (void)useThisValue:(NSString *)value;
In your CenterViewController.m file, import both the AppDelegate.h and your RootViewController.h file. In its viewDidLoad method, you have to get a reference to the current instances of your JASidePanelController subclass (RootViewController) and the leftViewController:
RootViewController *rootViewController = (RootViewController *)[[[[UIApplication sharedApplication] delegate] window] rootViewController];
LeftViewController *leftViewController = (LeftViewController *)rootViewController.leftPanel;
and then make the CenterViewController the delegate of the LeftViewController:
leftViewController.delegate = self;
[super viewDidLoad];
Implement the delegate method however you wish, for example:
- (void) useThisValue:(NSString *)value
{
self.label.text = value;
}
I have to acknowledge and thank Kevin McNeish here for pointing out to me how to properly get the instances of the RootViewController and the LeftViewController in order to get the delegate pattern to work with JASidePanels.
Did you see these methods in JASidePanels
// toggle them opened/closed
- (void)toggleLeftPanel:(id)sender;
- (void)toggleRightPanel:(id)sender;
JASidePanels is a good implementation, you can set the left, center and the right view controllers. So lets say that you have a UITableViewController as the left VC and according to row selection you load the center VC. You should have #import "UIViewController+JASidePanel.h" in your left VC.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.row)
case 1:
{
self.sidePanelController.centerPanel = [[UINavigationController alloc] initWithRootViewController:[[FirstViewController alloc] init]];
[self.sidePanelController toggleLeftPanel:nil];
break;
}
case 2:
{
self.sidePanelController.centerPanel = [[UINavigationController alloc] initWithRootViewController:[[SecondViewController alloc] init]];
[self.sidePanelController toggleLeftPanel:nil];
break;
}
}
There is a current view as UIViewController which call "LoginView" but I'm not in, I'm in a NSObject class and I want to call, display an other UIViewController which is call "MapView". How can I do this?
The problem is like above screenshot.
At your IBAction or specific method write this:
UIWindow *window=[UIApplication sharedApplication].keyWindow;
UIViewController *root = [window rootViewController];
UIStoryboard *storyboard = root.storyboard;
CustomViewController *vcc =(CustomViewController *) [storyboard instantiateViewControllerWithIdentifier:#"storyBoardID"];
[root presentModalViewController:vcc animated:YES];
I am assuming you're trying to access your UIViewController member from a UIViewController class from a NSObject class. Easy just pass UIViewController member to the NSObject class. In this case a self. What that lets you do is you can change, edit, remove, whatever you want to do in your UIView from another class. The following is an example of that.
Calling the NSObject Class from your UIViewController class
#implementation myViewControllerClass
- (void) viewDidLoad {
//Pass in the UIViewController object, in this case itself.
[[myNSOBjectClass alloc] startViewController:self];
....
}
Then from your NSObject
#interface myNSOBjectClass{
//Global access to the view controller.
UIViewController *viewController;
}
...
#implementation myNSOBjectClass
...
//function that the caller calls to pass their UIViewController object
- (void)startViewController:(UIViewController *)callerViewController{
viewController = [[UIViewController alloc]init];
//puts the caller's view controller to the global member.
viewController = callerViewController;
...
}
Now you have the view controller at your fingertips!
Cheers :)!
I used like this in my NSObject class:
[[[UIApplication sharedApplication] delegate].window.rootViewController presentViewController:yourMapViewContorller animated:YES completion:nil];
I hope it's useful.
You shouldn't be instantiating and displaying view controllers from within a model. Views should be driven by models.
In this case you mentioned LoginView as your starting point. When some condition is satisfied (successful login perhaps?) you should update the underlying model accordingly, and then display the MapView.
From within LoginView:
MapView *mapView = [[MapView alloc] init];
If your app uses a navigation controller:
[self.navigationController pushViewController:mapView animated:YES];
Otherwise:
[self presentViewController:mapView animated:YES completion:<nil or block>];
Try this Code. It'll help to you......
In your button click action You have to send your UINavigationController & current ViewController. Because NSObject class not found that controller.
In your Button Action put this Code:
[demo login_method_called:self.navigationController withCurrentViewController:self];
In your NSObject .h class put this code:
#import <Foundation/Foundation.h>
#import "Home_ViewController.h"
#interface Method_Action_class : NSObject
- (void)login_method_called:(UINavigationController*)navigation withCurrentViewController:(UIViewController*) controller;
#end
In your NSObject .m class put this code:
#import "Method_Action_class.h"
#implementation Method_Action_class
-(void)login_method_called:(UINavigationController*)navigation withCurrentViewController:(UIViewController*) controller
{
Home_ViewController *home = [[Home_ViewController alloc] initWithNibName:#"Home_ViewController" bundle:nil];
[navigation pushViewController:home animated:YES];
}
#end
And Build your code.
I have created an obersver in view controller class and declare a method of push view controller and post notification using NSNotification centre from NSObject subclass and it is working well.
in view controller:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dismissPickerView) name:kNotificationDismissPicker object:nil];
in subclass of NSOject:
[[NSNotificationCenter defaultCenter] postNotificationName:kMoveToAchievementView object:nil];
I have a similar question posted prior to this one, but I now have a more clear question and more information along with code. I currently have a ViewController (SignUpViewController) with a UITextField and a UIButton. I also have another ViewController (ProfileViewController) that has a UINavigationBar. I want to be able to type a Username in the TextField in the SignUpViewController, tap the UIButton, then have the naviBar text in the ProfileViewController become set to the text in the SignUpViewController's TextField. Problem is, I can't access the UITextField from the ProfileViewController. I currently have an NSString in my AppDelegate called "titleString" and am trying to use that as some sort of a solution. Here is my code below if my question has completely thrown you off, as this is sort of difficult to explain over stack overflow:
SignUpViewController:
- (IBAction)submitButton {
ProfileViewController *profileVC = [[ProfileViewController alloc] initWithNibName:nil bundle:nil];
[self presentViewController:profileVC animated:YES completion:nil];
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.titleString = #"Profile";
appDelegate.titleString = usernameTextField.text;
}
- (void)viewDidLoad {
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[super viewDidLoad];
}
ProfileViewController:
- (void)viewDidLoad {
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
self.title = appDelegate.titleString;
[super viewDidLoad];
}
it all works fine until I tap the submitButton in the SignUpViewController. What is going on here?
There's several things you could do here to pass data between view controllers.
1) set up a delegate method. The profileViewController would be the delegate of the signInViewController. When the sign in button is pressed, the signInViewController calls the delegate method which the profileViewController is listening for, which passes the title to the profileViewController.
In signInViewController.h:
#protocol SignInDelegate
#required
- (void)didSignInWithTitle:(NSString*)title;
#end
#interface SignInViewController : UIViewController
#property (nonatomic, assign) id<SignInDelegate> delegate;
Then your ProfileViewController would be set as the delegate when you allocate it:
signInViewController.delegate = profileViewController
This is your ProfileViewController.h:
#import "SignInViewController.h"
#interface ProfileViewController : UIViewController <SignInDelegate>
Lastly, make sure your ProfileViewController implements the - (void)didSignInWithTitle:(NSString*)title; method.
2) You could use NSNotificationCenter to post a custom notification with the title attached. This would be useful if you had several other viewControllers that would want to set the title like the profile.
#define UPDATE_NAVBAR_TITLE #"UPDATE_NAVBAR_TITLE"
When the signInViewController is done:
[[NSNotificationCenter defaultCenter] postNotificationName:UPDATE_NAVBER_TITLE object:nil];
Then, make sure you add the ProfileViewController as an observer:
[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(navbarUpdated) name:UPDATE_NAVBAR_TITLE object:nil];
For what you're asking I recommend the first one. Good Luck!