I've been trying to use a UIButton action to call a method in a different class (AppViewController). I first tried creating an instance of the view controller in the UIButton's calling class (caller.m) and then calling the method, but that kept resulting in EXC_BAD_ACCESS.
I'm realizing I need to point to the same instance of the view controller and am now trying to make sure the view controller instance is properly declared in caller.m.
I have a declaration of AppViewController *viewController in the AppDelegate, so my thought is to refer to that same instance from caller.m.
#import "caller.h"
#import "AppDelegate.h"
#implementation caller
- (id)initWithFrame:(CGRect)frame {
...
[btnSplash addTarget:viewController action:#selector(loadSplashView) forControlEvents:UIControlEventTouchUpInside];
....
}
However, viewController still shows up as undeclared. I tried a few other things, but know I'm probably missing something basic.
::::UPDATE::::
Okay, so it turns out I needed to create the following so the target "viewController" was actually declared and pointing to the correct instance:
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
AppViewController* viewController = appDelegate.viewController;
The method in the view controller class is now being properly called.
For a more clearly explained and more general version of this question, go here:
Objective-c basics: Object declared in MyAppDelegate not accessible in another class
There are multiple ways for objects to initiate actions, communicate with other objects and/or observe changes they are interested in including:
UIControl target/action bindings
Protocols
Key/Value Observing (KVO)
Notifications
I don't think notifications are what you want in this case. Notifications are most appropriate when the object posting the notification does not care what object(s) are observing the notification and there can be one or more observers. In the case of a button press you would typically only want a specific object to handle the action.
I would recommend using a protocol. You'll see lots of protocols in use in the iOS frameworks, basically any class that has a delegate property usually defines a protocol that delegate objects need to conform to. The protocol is a contract between the two objects such that the object defining the protocol knows that it can communicate with the object conforming to the protocol with out any other assumptions as to its class or purpose.
Here's an example implementation. Apologies if any typos/omissions.
In caller.h (I assumed caller is a UIViewController):
#class Caller
#protocol CallerDelegate
- (void)userDidSplashFromCaller:(Caller *)caller;
#end
#interface Caller : UIViewController
id <CallerDelegate> delegate;
#end
#property (nonatomic, assign) id <CallerDelegate> delegate;
#end
In caller.m:
#implementation Caller
#synthesize delegate;
- (void)viewDidLoad {
// whatever you need
// you can also define this in IB
[btnSplash addTarget:self forAction:#selector(userTouchedSplashButton)];
}
- (void)dealloc {
self.delegate = nil;
[super dealloc];
}
- (void)userTouchedSplashButton {
if (delegate && [delegate respondsToSelector:#selector(userDidSplashFromCaller:)]) {
[delegate userDidSplashFromCaller:self];
}
}
in otherViewController.m:
// this assumes caller is pushed onto a navigationController
- (void)presentCaller {
Caller *caller = [[Caller alloc] init];
caller.delegate = self;
[self.navigationController pushViewController:caller animated:YES];
[caller release];
}
// protocol message from Caller instance
- (void)userDidSplashFromCaller:(Caller *)caller {
NSLog(#"otherVC:userDidSplashFromCaller:%#", caller);
}
[EDIT: CLARIFICATIONS]
I realized after looking at your question and code again that I made some assumptions that may not be true in your code. You most likely should still use a protocol but the exact way to integrate my example depends on your app. I don't know what class Caller is in your app but whatever it is, it is dealing with UIButtons so it is most likely a view controller or a view.
Your comment about not having the correct instance of your appViewController makes me wonder if you understand the difference between classes and instances of a class. If my answer doesn't help you, please post some more code showing how you create and present your view controller as well as how you are configuring the button and I can try to clarify my answer.
You should post a NSNotification when clicking the button that will be caught and executed in the AppViewController.
So this should be:
In the sender class:
[btnSplash addTarget:self
action:#selector(loadSplashView)
forControlEvents:UIControlEventTouchUpInside];
-(void)loadSplashView:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"notif_name" object:some_sender_object];
}
In the target class:
Register to get the notification at view's load:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(some_function:) name:#"notif_name" object:nil];
Define the action to take in this class:
-(void) some_function:(NSNotification *)notif {
//do something
// to access the object do: [notif object]
}
Communication between various objects of your app is a design level decision. Although iOS provides neat ways of doing this at code-time (properties) - it's through hard-coupling.
True inter-object communication does not bind objects at compile time - this is something that can only be assured by following design patterns.
Observers & Delegates are two most commonly used patterns, and it's worth for you to learn when to use which one - see Observer vs Delegate.
Related
I have two view Controllers in my project ViewController, SettingsView. Here I am trying to update the ViewController's label, when i click on the SettingsView's back button. NSLog is working fine, but the label is not updating...
Please help me....
SettingsView.m
-(IBAction)backToMain:(id) sender {
//calling update function from ViewController
ViewController * vc = [[ViewController alloc]init];
[vc updateLabel];
[vc release];
//close the SettingsView
[self dismissModalViewControllerAnimated:YES];
}
ViewController.m
- (void)updateLabel
{
NSLog(#"Iam inside updateLabel");
self.myLabel.text = #"test";
}
Could you please tell me whats wrong with my code? Thank you!
You have to implement protocols for that. Follow this:
1) In SettingView.h define protocol like this
#protocol ViewControllerDelegate
-(void) updateLabel;
#end
2) Define property in .h class and synthesis in .m class..
#property (nonatomic, retain) id <ViewControllerDelegate> viewControllerDelegate;
3) In SettingsView.m IBAction
-(IBAction)backToMain:(id) sender
{
[viewControllerDelegate updateLabel];
}
4) In ViewController.h adopt protocol like this
#interface ViewController<ViewControllerDelegate>
5) In viewController.m include this line in viewDidLoad
settingView.viewControllerDelegate=self
Your label is not updating because , you are trying to call updateLabel method with a new instance.
You should call updateLabel of the original instance of viewcontroller from which you have presented your modal view.
you can use a delegate mechansim or NSNotification to do the same.
Delegate mechnaism would be clean. NSNotification is quick and dirty.
You are not exactly calling the correct vc. This is because you are creating a new instance of that class and calling the updateLabel of that instance.
You have a few options.
Either implement it as a delegate callBack (delegate messagePassing, or delegate notification - however you want to call it) to notify that class instance to call the updateLabel method.
Use the original instance VC as a dependency injection into the class that you are on right now, and use that instance to call the updateLabel
Use NSNotifications / NSUserDefaults to communicate between viewControllers and setup a notification system for your actions. This is quite easy, but not really great in the long run.
I would RECOMMEND option 1 (or) option 2.
Simply declare like this in SettingsView class:
UILabel *lblInSettings;// and synthesize it
Now assign like below when you presenting Settings viewController:
settingsVC.lblInSettings=self.myLabel;
Then whatever you update in lblInSettings it will be present in MainView obviously....
no need for any delegate methods or updating methods.
Means if you assign at the time of dismissing like
lblInSettings.text=#"My new value";
then self.myLabel also will be updated.
Let me know if you have any queries?
I am relatively new to iPhone development so please excuse what I hope is an easy task/question.
I have an app with a tab bar with several views each controlled by a MVC group. On loading the app the first mvc group of the tabbar gets displayed. In the viewDidAppear method of this first view controller I have a login screen modally displayed like so:
- (void ) viewWillAppear:(BOOL)animated
{
LoginViewController *loginvc = [[LoginViewController alloc] init];
loginvc.delegate = self;
if (*xxxxx*) {
[loginvc setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
[self presentModalViewController:loginvc animated:NO];
}
[loginvc release];
}
For the conditional if statement I would like a method to be called which checks that the user is logged in. I have provisionally (just for testing purposes) placed this method in the loginvc view controller however this cannot be a long term solution as the loginvc gets dealloc-ed once its dismissed (its the modal view).
At the moment I have a class set up like the following but don't know if this is correct, and don't know where to instantiate it (should this be in my main app delegate method application:didFinishLaunchingWithOptions: ?), and don't know how to change the userIsLoggedIn variable:
LoginClass.h file
#import <Foundation/Foundation.h>
#interface LoginClass : NSObject {
BOOL userIsLoggedIn;
}
-(BOOL)checkIfUserIsLoggedIn;
#end
LoginClass.m file
#import "LoginClass.h"
#implementation LoginClass
- (id)init
{
self = [super init];
if (self) {
// Initialization code here.
userIsLoggedIn = NO;
}
return self;
}
-(BOOL)checkIfUserIsLoggedIn
{
return userIsLoggedIn;
}
#end
My question is how should I create an object of this class which exists for the whole time the application is alive. This is so that when the app is launched, the login screen is displayed, but after successful login the 'userIsLoggedIn' variable gets set to 'YES' so that if the first mvc is called again the login screen doesn't get displayed modally again.
I hope I have made sense and hope someone can help with the code. If I am approaching this wrong please let me know alternative strategies (for example should I even be calling the modal display from the viewDidAppear of the first mvc?).
Thanks in advance,
Andy
The App Delegate is the place where your Login class should be. It's always reachable by your view controllers and "lives" for the whole time your app is running. Check the docs to see how to access your App Delegate from your view controllers.
Your Login Class should also look like this:
#import <Foundation/Foundation.h>
#interface LoginClass : NSObject {
BOOL userIsLoggedIn;
}
#property (nonatomic) BOOL userIsLoggedIn;
-(BOOL)checkIfUserIsLoggedIn;
#end
// Implemenatation
#import "LoginClass.h"
#implementation LoginClass
#synthesize userIsLoggedIn;
- (id)init
{
self = [super init];
if (self) {
// Initialization code here.
userIsLoggedIn = NO;
}
return self;
}
-(BOOL)checkIfUserIsLoggedIn
{
return userIsLoggedIn;
}
#end
To set the login state, simply do something like this:
[[yourAppDelegateVar instanceOfLoginClass] setUserIsLoggedIn:YES];
or
[[yourAppDelegateVar instanceOfLoginClass] setUserIsLoggedIn:NO];
You can use the Singleton pattern, which is totally suitable for your problem.
Sure, you may use the AppDelegate for that as suggested below (as UIApplication is itself a singleton and therefore the AppDelegate -- the object that is the delegate of your UIApplication singleton -- only exists in one instance too) but is it better practice to keep the AppDelegate only for what it is designed for (i.e. the delegate of the UIApplication, namely managing everything that is related to app events like going to the background or back to the foreground, launching, receiving push notifs, and so on) to keep code clear and readable.
You better use another dedicated singleton for each Service you need to implement: this is better design practice.
For example implementing a LoginService singleton class (See here in the Apple doc for implementation details) that will hold the -(BOOL)loginWithUsername:(NSString*)username password:(NSString*)password; method (that checks that your login/pwd is OK, potentially communicating with your WebService for this), a #property(readonly) BOOL loggedIn; property, probably a #property(readonly) NSString* currentUsername; if needed, and so on.
In my app there are two tabbars. In tab-0 parent class is "FirstViewController" and in tab-1 parent class is "SecondViewController". In "SecondViewController" i have declared protocol and custom delegate method. i want to pass the information in "FirstViewController"(FVC). So FVC has to assigned as a delegate.
Now my doubt is, right now i am in "SVC". How can i assign "FVC" as a delegate of "SVC"?
In "SVC"
[[self delegate] sendCoordinates:self];
Definition of method is in "FVC". To execute this method, first i need to assign "FVC" as a delegate.
I hope I am clear in explaining my problem.
Thanks in advance.
You need to set the delegate. Let me demonstrate:
`SVC.h`
#interface SVC
{
id _delegate;
}
- (void)setDelegate:(id)delegate; //sets the delegate
- (id)delegate; //gets the delegate
#end
#protocol SVCDelegate
- (void)sendCoordinates:(SVC *)svc;
#end
Then, in SVC.m, you call the delegate in exactly the same way as you showed in your question, so [[self delegate] sendCoordinates:self];
Now, in FVC, you'll need to #import SVC.h and initialise the object.
SVC*svcObject = [[SVC alloc] init];
[svcObject setDelegate:self]; //self now refers to FVC - I think you're missing this one
//work with the object and when you're done, get rid of it in dealloc:
[svcObject setDelegate:nil];
[svcObject release];
In the same class, implement - (void)sendCoordinates:(SVC *)svc and it will be called after you set the delegate.
I think you're missing the setDelegate: stage, which is why it doesn't work.
Hope it helps!
Note: In SVC, remember to retain the delegate, or it will become nil and no delegate methods will never be called. Don't forget to release that delegate once you're done.
i am getting the web services from the .net web server.
while in the process (getting data) i am displaying a subview with activity indicator.
After completing getting data i need to close that view.
i have two classes one is myclassviewcontroller,webservices
Basically i am writing code to get web services webservices.
In webservices class at
-(void)connectionDidFinishLoading:(NSURLConnection *)connection i call myclass like this.
myclassviewcontroller *obj = [[myclassviewcontroller alloc]init];
[obj mymethod];
At myclassviewcontroller i write this code for my method.
(void)mymethod {
[loadview removeFromSuperview];
}
the method is executed but view is not removed.
I already declared it in myclassviewcontroller.h class also.
i am checking this by keeping some text in NSlog
But if i calling this mymethod in myclassviewcontroller.m using timer then it removes view.
what the wrong.
can any one please help me.
I think it may be understand what is my problem.Let me place comment if not.
Thank u in advance.
I believe the problem with your code is how you access the myclassviewcontroller. It must have already been on the screen while the data was loading, so creating a new instance of that class and calling a method against one of it's uninitialized members (loadview) does nothing.
myclassviewcontroller *obj = [[myclassviewcontroller alloc]init];
// here object has just been initialized
// (it is not the same instance as the one on screen)
[obj mymethod];
If obj was a reference to the actual viewcontroller that is on screen, you could easily call:
[obj.loadview removeFromSuperview];
or
[obj mymethod]; // if you wanted to add more code in that function
So, the real problem is that you accessing a different instance of myclassviewcontroller than the one which is actually on screen. You need a variable holding some reference to the correct instance of myclassviewcontroller to access the loadview ivar.
In webservices.h:
#interface webservices : NSObject {
...
// This ivar will have to be set when webservices is initialized
myclassviewcontroller * viewController;
}
#property (nonatomic, retain) myclassviewcontroller * viewController;
and webservices.m would need to #synchronize viewController.
Then in connectionDidFinishLoading: you can just call [viewController.loadview removeFromSuperview];
the problem could be that you instantiate your myclassviewcontroller when "loadview" is already allocated by your "main" class but "invisible" in your myclassviewcontroller, so your new instance of myclassviewcontroller doesn't really know who "loadview" is...
i mean: loadview is allocated and added to the mainView (in the same class where you allocate
"myclassviewcontroller"...)
but then you try to remove it not in your mainView, but in myclassviewcontroller...
try to modify your method this way:
(void)mymethod {
if (loadview!=nil){
NSLog(#"I'm here...");
[loadview removeFromSuperview];
}
}
to see if "loadview" exist when and WHERE you call the method (in myclassviewcontroller)
luca
I'm trying to figure out how I can call a function from another one of my classes. I'm using a RootViewController to setup one of my views as lets say AnotherViewController
So in my AnotherViewController im going to add in on the .h file
#class RootViewController
And in the .m file im going to import the View
#import "RootViewController.h"
I have a function called:
-(void)toggleView {
//do something }
And then in my AnotherViewController I have a button assigned out as:
-(void)buttonAction {
//}
In the buttonAction I would like to be able to call the function toggleView in my RootViewController.
Can someone clarify on how I do this.
I've tried adding this is my buttonAction:
RootViewController * returnRootObject = [[RootViewController alloc] init];
[returnRootObject toggleView];
But I dont think that's right.
You'll want to create a delegate variable in your AnotherViewController, and when you initialize it from RootViewController, set the instance of RootViewController as AnotherViewController's delegate.
To do this, add an instance variable to AnotherViewController: "id delegate;". Then, add two methods to AnotherViewController:
- (id)delegate {
return delegate;
}
- (void)setDelegate:(id)newDelegate {
delegate = newDelegate;
}
Finally, in RootViewController, wherever AnotherViewController is initialized, do
[anotherViewControllerInstance setDelegate:self];
Then, when you want to execute toggleView, do
[delegate toggleView];
Alternatively, you could make your RootViewController a singleton, but the delegate method is certainly better practice. I also want to note that the method I just told you about was Objective-C 1.0-based. Objective-C 2.0 has some new property things, however when I was learning Obj-C this confused me a lot. I would get 1.0 down pat before looking at properties (this way you'll understand what they do first, they basically just automatically make getters and setters).
I tried out the NSNotificationCentre - Works like a charm - Thanks for your reply. I couldn't get it running but the NS has got it bang on.
[[NSNotificationCenter defaultCenter] postNotificationName:#"switchView" object: nil];