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.
Related
I am aware that this topic has been covered already in many places, but while going through the different answers, I could not find a solution that fits my case.
Basically what I am trying to do is very simple. I have a view controller with a table view, and a + button that fires a second view controller where the user can enter, say a name, and this name is then added to the first view controller table view. Think about Contacts on the iPhone where you can add a new person (or Amazon where you can add a new credit card).
My question is - what is the best way to return this string (in this case) back to the view controller where the table is?
People suggested using NSDefaults, Delegate, or singleton none of which are really good for this case (each one for its own reason). I really just need to return a simple string.
Thank you for your help.
If you are navigating from View Controller A ---> View Controller B, which is your case here, and then you want to pass information from B -> A, it is recommended to use a loose coupling, like delegation. There are a couple of things as you discussed, like NSUserDefaults, singleton, NSNotification, and may be many more.
But delegation is the better and standard approach to do it.
Here's what you need to do (please note I am typing this off the top of my head so the compiler may have some issues with my syntax).
In your child view controller interface:
#protocol ChildDelegate <NSObject>
- (void)didConfirmName:(NSString*)name
#end
#interface ChildViewController : UIViewController
...
#property (nonatomic, assign) id<ChildDelegate> delegate;
And implementation, inside the method called when the user confirms whatever they need to confirm:
- (void)myCustoMethod
{
...
if ([self.delegate respondsToSelector:#selector(didConfirmName:)])
[self.delegate didConfirmName:NAME_STRING];
}
And in your parent view controller, when you are instantiating the child view controller, assign SELF as the delegate:
ChildViewController *vc = [[ChildViewController alloc] init...];
vc.delegate = self;
And implement:
- (void)didConfirmName:(NSString*)name
{
// Do whatever you want with the name here...
}
Also make sure you tell the compiler that you are implementing the protocol :
#interface ParentViewController () <ChildDelegate>
#end
You don't return the new thing to the TableView. What you need is to update the source of information that feeds your tableView.
So where the data live is the place you need to go and add it, when you will come back to the UITableViewController you may need to tell the UITableView to reload it's data.
There is a handle on each UIViewController to it's parent if you absolutely need to communicate with it.
UIViewController *parentVC = aViewController.parentViewController;
Assuming that the ViewController that needs to get updated is the root view controller of the app, you can do the following:
YourViewController * yvc = [(YourAppDelegate *)[[UIApplication sharedApplication] delegate] viewController];
[yvc updateString:#"Updated String"];
Remember to:
#import "YourAppDelegate.h"
But Honestly I would use the delegate pattern or a NSNotification.
Three good options:
1) Have the first view controller pass itself to the second view controller, so that the second controller can send messages to the first. Ideally, create a protocol that the first controller adopts, so that the second controller doesn't depend on the type of the first controller but only cares that it implements the protocol:
#protocol Nameable
#property(copy) NSString* name;
#end;
#interface FirstViewController <Nameable>
//...
#end
#implementation FirstViewController
#synthesize name;
//...
#end
Then the first controller can do this:
SecondViewController *second = [[SecondViewController alloc]
initWithNibName:nil]; second.thingToName = self;
[self.navigationController pushViewController:second animated:YES];
And when the time comes, the second controller can do this:
self.thingToName.name = nameString;
The details aren't really that important -- the main thing to know here is that if you want to send a message to an object, you first need a pointer to the object. When the first view controller sets second.thingToName = self, it's providing that pointer.
2) Have the first view controller create a data object in which the second view controller can store the data, like this:
#interface Person <Nameable>
//...
#end
#implementation Person
#synthesize name;
//...
#end
Now the first view controller can create a new Person and pass that:
SecondViewController *second = [[SecondViewController alloc]
Person *person = [[Person alloc] init];
[self.people addObject:person];
initWithNibName:nil]; second.thingToName = person;
[person release];
[self.navigationController pushViewController:second animated:YES];
This is similar to the first approach, only the thing that's receiving the name isn't the view controller here, it's some sort of data container (Person).
You can see the value of the protocol here, too -- notice that the SecondViewController class doesn't change at all between the first and second approaches. It doesn't care whether it's talking to a view controller or an instance of Person or anything else... as long as the thing implements the Nameable protocol, it's happy.
3) Reverse the direction of communication. Instead of making the second view controller send the string, have its parent get the string. This may well be the simplest solution, although it does require that the parent has some way to know that the child is done. It'd go something like this:
#implementation FirstViewController
//...
- (IBAction)addNewName:(id)sender
{
self.secondController = [[SecondViewController alloc] initWithNibName:nil bundle:nil];
[self.navigationController pushViewController:self.secondController animated:YES];
}
- (void)viewWillAppear
{
if (self.secondController != nil) { // we must be returning from the child
self.name = self.secondController.name;
self.secondController = nil;
}
}
#end
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.
So, basically, I have four different views that are switched via a tab bar at the bottom of the screen. I have a view controller set up for each one, and they all work fine. The only problem is that I have no idea how to share information between the different views-for example, I have an IBAction on one that takes the output of a UISegmentedControl and stores the selection as an integer (1,2,3,4). The problem is, I don't know how to access that integer from the first view controller (the one that actually does stuff with that information). I'm sure this is a very basic problem but my google-fu doesn't seem to be working here. Thanks!
Using the MVC method, information should not be stored in the view controllers. You should have a separate object which stores the information, the view controllers load it, and the views display it. The easiest way to do this would be to store the information in your application's delegate. Then, whenever the view controller needs to find/change some information, it can use [[UIApplication sharedApplication] delegate] to get the delegate, and load/change the information as needed. Load the information to update the display in viewDidLoad or viewWillDisplay:, and change it in action methods.
Edit Example:
DelegateClass.h
#interface DelegateClass : NSObject {
//ivars
float number1;
}
#property (assign) float number1;
...
#end
DelegateClass.m
#import "DelegateClass.h"
#implementation DelegateClass
#synthesize number1;
...
#end
MyViewController.m
#import "DelegateClass.h"
#implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
DelegateClass *delegate = [[UIApplication sharedApplication] delegate];
float number1 = delegate.number1;
...
}
- (IBAction)actionMethod:(id)sender {
float number1 = sender.floatValue;//get new value
((DelegateClass*)[[UIApplication sharedApplication] delegate]).number1 = number1;
}
...
#end
You could have your views expose their variables to the UITabBarController. The UITabBarController can have methods that these views can call to see these variables.
Or, you could have your views directly set the values of variables in the UITabBarController. Not recommended (though a lot less typing :)
Just did a quick search on Google, btw, and came across this: Accessing parent view controller (custom) properties. Looks like there's sample code to be had there. :)
Hope this helps!
I use a general theme in all my apps where I have certain data that needs to be accessed practically in all main classes. I find it really easy to use and practically error free. (Thanks again to all the folks here in the forum for the super idea!)
I declare this variable in all the classes where I need it...
#class WebiDataManager;
#interface FirstViewController : UIViewController<MFMessageComposeViewControllerDelegate, SetupDelegate> {
WebiDataManager *webiDataManager;
...
In this code I get it from the AppDelegate...
#import "WebiAppDelegate.h"
- (void)viewWillAppear:(BOOL)animated {
// D_IN;
[super viewWillAppear:animated];
//get the dataManager global Object, so we always have a structured accesss to the data!
WebiAppDelegate *mainDelegate = (WebiAppDelegate *)[[UIApplication sharedApplication]delegate];
self.webiDataManager = mainDelegate.webiDataManager;
By the way, this also works great for coredata, since through this global variable I always have access to all the main coredata.
Hope I could explain it clearly enough...
Newbie question about design patterns in objC.
I'm writing a functionality for my iphone app which I plan to use in other apps too. The functionality is written over two classes - Viewcontroller1 and Viewcontroller2. Viewcontroller1 is the root view of a navigation controller and it can push Viewcontroller2. Rest of the app will use only ViewController1 and will never access Viewcontroller2 directly. However, triggered
by user events, Viewcontroller2 has to send a message to the
rest of the app.
My question is what is the best way of achieving it?
Currently, I use two level of delegation to send the message out from Viewcontroller2. First send it to Viewcontroller1 and then let Viewcontroller1 send it to rest of the app or the application delegate. So my code looks like -
//Viewcontroller1.h
#protocol bellDelegate
-(int)bellRang:(int)size;
#end
#interface Viewcontroller1 : UITableViewController <dummydelegate> {
id <bellDelegate> delegate;
#end
//Viewcontroller1.m
#implementation Viewcontroller1
-(void)viewDidLoad {
//some stuff here
Viewcontroller2 *vc2 = [[Viewcontroller2 alloc] init];
vc2.delegate = self;
[self.navigationController pushViewController:vc2
animated:YES];
}
-(int)dummyBell:(int)size {
return([self.delegate bellRang:size]);
}
//Viewcontroller2.h
#protocol dummyDelegate
-(int)dummyBell:(int)size;
#end
#interface Viewcontroller2 : UITableViewController {
id <dummyDelegate> delegate;
#end
//Viewcontroller2.m
#implementation Viewcontroller2
-(int)eventFoo:(int)size {
rval = [self.delegate dummyBell:size];
}
#end
That's the proper way of doing things ! If all your delegate method are to be invoked on viewController2, you could have only one protocol and directly assigned the delegate from viewController1 to viewControler2 but it will block you the first time tou need to invoke the delegate from viewControler1.. bad design then !
I would say the implementation is fine, but this isn't necessarily a case where you should add another level of abstraction to get reusability, because it's just a general and recommended way to pass messages around objects, i.e. "delegation" Apple's documentation on delegation.
Also, regarding your problem case where you have to send a message "to the rest of the app", you should consider notifications, they may be very time-saving in some situations. (Apple's documentation on notifications, SO Question about Delegation vs Notifications)
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];