I have an app that has multiple viewControllers, where some of these viewControllers contain methods that run various tasks. What I need to do is when the initial viewController loads, is to call these methods in the other viewControllers such that they run in the background, however, I am having some difficulty doing this.
Let's say I have 4 viewControllers, A, B, C, & D, where A is the initial viewController, and in each viewController, I have aMethod, bMethod, cMethod, and dMethod respectively. Here is the relevant code:
Inside my opening viewController (AviewController):
in the .h file:
#import "BViewController"
#import "CViewController"
#import "DViewController"
#interface AViewController:UIViewController {
BViewController *bViewCon;
CViewController *cViewCon;
DViewController *dViewCon;
}
#property (nonatomic, retain) BViewController *bViewCon;
#property (nonatomic, retain) CViewController *cViewCon;
#property (nonatomic, retain) DViewController *dViewCon;
#end
In my .m file I have the following:
#import "BViewController"
#import "CViewController"
#import "DViewController"
#implementation AviewController
#synthesize bViewCon, cViewCon, dViewCon;
- (void) viewDidLoad {
[super viewDidLoad];
bViewCon = [[BViewController alloc] init];
[bViewCon bMethod];
...
}
However, I am getting the error message, "No visible #interface for 'BViewController' declares the selector 'bMethod'". I need to call the other methods from the other viewControllers the same way from this class (i.e. AViewController).
Thanks in advance to all who reply.
Have you considered using NSNotificationCenter? Set up the methods on the notifications and just ping them when you need them to run. This helps if your other view controller is instantiated and available, like buried in the navigation controller stack or on a separate tab.
To answer you question about that error, you need to declare the method you want to call in your header file. The error is stating it can't find a declaration for that method.
Example of notification center
// listen for notifications - add to view controller doing the actions
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(mySpecialMethod) name:#"SomeNotificationName" object:nil];
// when you want your other view controller to do something, post a notification
[[NSNotificationCenter defaultCenter] postNotificationName:#"SomeNotificationName" object:nil];
// you don't want this notification hanging around, so add this when you are done or in dealloc/viewDidUnload
[[NSNotificationCenter defaultCenter] removeObserver:self]; // this removes all notifications for this view
// if you want to remove just the one you created, you can remove it by name as well
To solve the error you're receiving, make sure that all of the methods are declared in the header (.h) files of each controller (otherwise, the compiler won't be able to see them).
As all of these controllers are children to AViewController (they are created by AViewController and kept as ivars on it), I wouldn't use NSNotificationCenter here (unless there are other objects that need to be notified in case of certain events occurring too, which aren't owned by AViewController).
Instead, I would just call the methods directly as you're attempting to do.
On another note, if these methods are starting ongoing tasks (running tasks in background), it may be best for you to move the method calls to the init: method of AViewController. (As on iOS 5, views CAN be unloaded and hence viewDidLoad: can be called potentially more than once... such as in the case of memory warnings and the view being off screened). I might go about doing something like this:
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle
{
self = [super initWithNibName:nibName bundle:bundle]; // your correct stuff here
if (self)
{
bViewCon = [[BViewController alloc] init];
[bViewCon bMethod];
// ... and so on for the other controllers
}
return self;
}
Edit
Although, as mentioned in a comment, UIViewControllers aren't exactly cheap in terms of memory... it honestly would likely be best to refactor this code to have a single controller (a subclass of NSObject instead of UIViewController, which is cheaper) to act as a manager for the tasks that are going to be running in the background. I imagine this will likely help you later down the road too, as it would help compartmentalize the tasks and purpose of each of your controllers (in such, UIViewControllers should primarily be responsible for managing a view (/view hierarchy in some cases) and associated tasks... if there are ongoing tasks that are occurring outside the scope of things associated with said view, it's probably a sign that the UIViewController shouldn't be handling them...
Related
I am trying to create a way to pass data from Controller A (CDCFaderController) to Controller B (CDCSendsViewController) this is because although Controller A owns a view, I need to send some of that views data to Controller B as it has some additional functionality I want to access.
My logic was to setup the delegate in Controller A and receive it in Controller B for use.
Currently I have this as a test to get it working:
Controller A .h
#protocol CDCSendsControllerDelegate <NSObject>
-(void)test:(NSString*)str;
#end
#interface CDCFaderController : UIViewController
<UIScrollViewDelegate, CDCControlDelegate, CDCFadersSideBarDelegate>
#property (nonatomic, weak) id <CDCSendsControllerDelegate> delegate; // delegate synthesis
Controller A .m
-(void)delegateTest {
[self.delegate test:#"Hello world!"];
}
And in the receiving Controller B:
Controller B .h
#interface CDCSendsViewController : UIViewController
<CDCControlDelegate, CDCSendsControllerDelegate>
Controller B .m
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"VDL CALLED");
CDCFaderController * faderController = [[CDCFaderController alloc] init];
faderController.delegate = self;
}
-(void) test:(NSString*)str{
NSLog(#"%#", str);
}
The idea i thought was that when i loaded Controller B it would pull through the print from Controller A and display the information?
In practice controller B wont be instantiated by the view as Controller A is the owner of the view. So I was wondering how to get B to call and pull data from A correctly to get this working? B is more of a background controller adding support functionality to A by using some of its passed data.
Hope this helps I can provide any further detail
I saw the above code. Actually your CDCFaderController is locally initialised in viewDidLoad method. ARC will release that controller object after viewDidLoad is executed as it is no longer used anywhere which will make the delegate nil as well.
Kindly make a property of CDCFaderController and initialise it. After that it should work fine.
EDIT: edited for clarity
Disclaimer: I'm new and pretty bad. But I have tried very hard and read lots of stuff to figure this out, but I have not...
I think my whole delegate pattern would work, except I can't figure out how to set the delegate property of ViewController to self in the MatchLetter class. The reason is because I can't figure out how to call code there. It's not a view controller, so viewDidLoad or prepareForSegue won't work.
This is what I've got:
ViewController.h
#import <UIKit/UIKit.h>
#class ViewController;
#protocol letterMatchProtocol <NSObject>
- (BOOL) isLetterMatch:(char) firstLetter;
#end
#interface ViewController : UIViewController
#property (nonatomic, weak) id <letterMatchProtocol> delegate;
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
char c = 'a';
// This is the method I want to delegate to MatchLetter, to have a BOOL returned
BOOL returnValue = [self.delegate isLetterMatch:c];
}
#end
MatchLetter.h
#import <Foundation/Foundation.h>
#import "ViewController.h"
#interface Delegate : NSObject <letterMatchProtocol>
#end
MatchLetter.m
#import "MatchLetter.h"
#implementation Delegate
// this is the code I think I need to run here, to set the delegate property...
// ViewController *viewController = [ViewController new];
// viewController.delegate = self;
// ... so that isLetterMatch can be run here from ViewController.m
// But I don't know where to put this code, or how to get it to run before the ViewController
// especially since there are no segues or views to load.
- (BOOL) isLetterMatch:(char)firstLetter {
if (firstLetter == 'a') {
return YES;
}
else {
return NO;
}
}
#end
Can somebody please tell me the best way to proceed? Thanks for reading
You asked "Where to set delegate = self? Or should I just use a different design pattern?".
Answer: Don't. An object should never be it's own delegate.
Your code is quite a mess.
Don't name a class "Delegate". A delegate is a design pattern. The whole point of a delegate is that any object that conforms to a particular protocol ("speaks the language") can serve as the delegate. You don't need to know what class of object is serving as the delegate, but only that it speaks the language you need.
An analogy: When you call the operator, you don't care who is working the operator desk. You don't care about his/her gender, religion, ethnic background, how tall they are, etc. You just care that they speak your language.
Likewise, when you set up a delegate, it doesn't matter what type of object gets set as the delegate. All that matters is that the object that is the delegate conforms to the protocol for that delegate.
A table view can have ANY object serve as it's delegate, as long as that object conforms to the UITableViewDelegate protocol. You usually make you view controller be the table view's delegate, but you don't have to. You could create a custom class that manages your table views, and have it be the delegate. There is no "TableViewDelegate" object class. There is instead a UITableViewDelegate protocol, and any object that conforms to the protocol can act as a table view's delegate.
Edit: Your question is confusing. I think what you're proposing is that your Delegate class would create a view controller and make itself the delegate for the view controller.
If that's what you are talking about, your thinking is backwards. The view controller is using the Delegate class as a helper class. Any given instance of a view controller class can create an instance of the Delegate class and set it as it's delegate if it desires. You might have 3 instances of ViewController at one time, each with it's own instance of your Delegate class.
Thus, the ViewController object is the one that should create and set up an instance of Delegate if it needs one:
- (void) viewDidLoad;
{
self.delegate = [[Delegate alloc] init];
//other setup here
}
I have an application i am making but here is the problem which i been spedning hours figuring out.
I have a Tab-Based app, The first tab has a UILabel Object which should display an NSString from the SecondViewController.m which has a Method:
-(IBAction) save: (id) sender{
// This String holds data from the secondViewController's textfield to be passed to the
// UILabel whch is on the firsViewController.m
NSString *data = self.addDataTextfield.text;
}
I have used many methods including Singletons but they dont work as i have read somewhere that singletons are meant to pass data from Parent to child and child cant send data to the parent controller in this case BUT the Only way would be to use Protocols which i am but i am kind of lost using this method. Here is what i have on my secondViewController.h
#import <UIKit/UIKit.h>
#import "singletonObj.h"
#import "AppDelegate.h"
// Using a Protocol to pass data Back to the parent view
#protocol passStringDelegate <NSObject>
-(void) enteredString: (NSString *)string;
#end
#interface SecondViewController : UIViewController <UITextFieldDelegate>
{
singletonObj *object; // This singleton isnt being used, Just there incase
}
#property (strong, nonatomic)IBOutlet UITextField*addDataTextField;
- (IBAction)Save:(id)sender;
#property (retain) id <passStringDelegate> delegate;
#end
SO my Question is, is there any way i cando this and pass data from this secondViewController using #protocol or at least can anyone show me how to Use NSNotificationCenter within this code to pass data ? i dont feel comftarble using the AppDelegate class to pass data as it seems to go against apple's way or prepareforSegue which doesnt work for me.
I been searching around but most i find do the data being sent from the parent to the child but i dont see Tabbed based examples where the ChildViewController can send an NSString to the ParentViewController to display that data on an UILAbel.
There are several ways to do this. From any of the tab bar controller's content controllers, you can access another of the controllers with something like self.tabBarController.viewControllers[0]. This will reference the controller in the first tab. So, if you want to pass a string back to the first controller from the second, create a string property in the first controller (say, passedInString), and in the second controller, have something like this:
FirstViewController *first = self.tabBarController.viewControllers[0];
first.passedInString = self.stringToPass;
Ok #user2994008, in a UITabBarController app setup, I would not use a delegation pattern at forces coupling between the child view controllers in some manner. Posting a notification will get the job done, the only problem is what should trigger the notification to be posted. In this case, a quick and dirty way is to just post the notification in viewWillDisapper: as that will get called when the user switches tabs.
SecondViewController.m
//SecondViewController.m
//this is the view with the UITextField in it
#import "SecondViewController.h
#implementation SecondViewController
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
NSLog(#"%s", __FUNCTION__);
//since this gets called when the user switches tabs, i'll post the
//notification from this method
//retrieve our text field's text and store it in a dictionary
NSDictionary *dict = #{"text":self.addDataTextField.text};
//add the dictionary as userInfo: and post notification
[[NSNotificationCenter defaultCenter] postNotificationName:#"myAwesomeNotification" object:nil userInfo:dict];
}
//all other second view controller methods here
#end
FirstViewController.m
//FirstViewController.m
#import "FirstViewController.h"
#implementation FirstViewController
-(void)viewDidLoad {
[super viewDidLoad];
//add an observer for the notification and tell it what method to call when it gets the notification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(actionTriggeredByNotification:) name:#"myAwesomeNotification" object:nil];
}
- (void)actionTriggeredByNotification:(NSNotification*)notification {
NSLog(#"%s", __FUNCTION__);
NSDictionary *userInfo = [notification userInfo];
NSString *data = userInfo[#"text"];
NSLog(#"Text data is: %#", data);
}
- (void)dealloc {
//be sure to remove observer
[[NSNotificationCenter defaultCenter] removeObserver:self];
//if using ARC, don't call super dealloc
}
//all other first view controller methods
#end
Also, drop the singleton stuff trying to pass data that way. That gets into trying to create what are in essence global variables and you don't really want that for passing simple strings around.
And while using notifications work ok here, some other solutions could be implemented using key-value observing (KVO) or adhering to the UITextFieldDelegate protocol and sending a message from one of those delegate methods. Even so, all these techniques still open up questions about when you actually want to retrieve the textfield's text.
Once you get your bearings, I'd HIGHLY recommend looking into ReactiveCocoa. It's a library designed specifically for solving problems like yours.
I have a simple test app to help me learn how to persist data from a NSMutableArray to a plist. Everything seems to be working well, until I try to save the data by calling a ViewController method called "saveData" in my AppDelegate.m file:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[ViewController saveData];
}
I get a "No known class method for selector 'saveData', although the method is clearly declared in ViewController.h, like so:
//
// ViewController.h
// PlistTest
//
// Created by Tim Jones on 10/30/13.
// Copyright (c) 2013 TDJ. All rights reserved.
//
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *nameLabel;
#property (weak, nonatomic) IBOutlet UILabel *timeLabel;
#property NSMutableArray *mainActivityArray;
- (IBAction)buttonHit:(id)sender;
-(NSString *) getFilePath;
-(void) saveData;
-(void) loadData;
#end
and implemented in ViewController.m, thusly:
//
// ViewController.m
// PlistTest
//
// Created by Tim Jones on 10/30/13.
// Copyright (c) 2013 TDJ. All rights reserved.
//
#import "ViewController.h"
#import "DataClass.h"
#interface ViewController ()
#end
#implementation ViewController
-(NSString *) getFilePath
{
NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
return [[pathArray objectAtIndex:0] stringByAppendingPathComponent:#"PlistTestData"];
}
-(void) saveData
{
[self.mainActivityArray writeToFile: self.getFilePath atomically:YES];
}
I imported the ViewController.h into AppDelegate.h.
I'm pretty green, so I expect the problem may be obvious to many here. Would sure appreciate some help.
Issue:
[ViewController saveData];
You are calling saveData method using class name ViewController.
But saveData is an instance method, not class method.
-(void) saveData;
Fixes:
1) Declare saveData as class method
+(void) saveData;
2) Call saveData using the object of ViewController.
ViewController *vControl = [[ViewController alloc] init];
[vControl saveData];
Previous answer (Midhun #2) can work, but I think you would be better off using the Application Did Enter Background Notification and skip the delegate.
Just add this to "view did load": it will call saveData whenever the app goes to background.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(saveData) name:UIApplicationDidEnterBackgroundNotification object:nil];
Hope this helps.
As others have noted, your issue is that your [ViewController saveData] method invocation clearly suggests that you're trying to call a "class method", and you undoubtedly want to call your "instance method" (because it's your instance of your view controller that has the data you want to save). To achieve that, you have two basic choices:
You can have your app delegate call the saveData method in your view controller.
In various comments, you mention that you've "tried instantiating the VC every way and in every place I can imagine." Don't. Midhun's example was a conceptual one, illustrating the difference between a class and instance method. But, while you want to call the instance method, you want to call this for your existing instance of your view controller, definitely not instantiating a new view controller.
So, you may ask, how do you get a reference to the existing instance of your view controller? What you'll want to do is to (a) create a property in your app delegate to hold the reference to the view controller with the saveData method; (b) have that view controller set that property of the app delegate. So, first, create a property in your app delegate's .h file to reference the view controller:
#property (weak, nonatomic) ViewController *viewController;
Obviously, don't forget the #import "ViewController.h" line early in the .h file.
Second, have the viewDidLoad method of the view controller update the app delegate's viewController property:
- (void)viewDidLoad
{
[super viewDidLoad];
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
delegate.viewController = self;
}
Again, don't forget to #import "AppDelegate.h" at the top of your ViewController.m file.
Having done that, then app delegate's applicationDidEnterBackground can now reference this property that you set in viewDidLoad
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self.viewController saveData];
}
Frankly, if you did this technique, I might suggest further refinements, notably employing a delegate-protocol pattern, but I'm going to defer that conversation until after you have mastered the above technique.
Even easier than the above is to eliminate this app delegate applicationDidEnterBackground code altogether, and just have your view controller, itself, respond to the system notification that is associated with the app entering background. And, needless to say, you put this code right in the view controller itself.
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(saveData) name:UIApplicationDidEnterBackgroundNotification object:nil];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
}
- (void)saveData
{
// save your data here
}
As you can see, I register to observe UIApplicationDidEnterBackgroundNotification in viewDidLoad, but also make sure to remove my observer in dealloc. I also made sure that my #selector method name exactly matched my method name (e.g., in my example, no parameters, and hence no colon).
I create an UITapGesture with a target which is NOT the current object. Later, when tapping, app crashes.
View controller .h:
#interface ViewController : UIViewController
{
IBOutlet UIImageView *iv;
}
#end
View controller .c:
#import "ViewController.h"
#import "Target.h"
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
Target *t = [[Target alloc] init];
UITapGestureRecognizer *tgr = [[UITapGestureRecognizer alloc] initWithTarget:t action:#selector(gestureDone:)];
[iv addGestureRecognizer:tgr];
[iv setUserInteractionEnabled:YES];
}
#end
Target.h:
#interface Target : NSObject
- (void)gestureDone:(UIGestureRecognizer *)gr;
#end
Target.c:
#implementation Target
- (void)gestureDone:(UIGestureRecognizer *)gr
{
NSLog(#"Gesture!");
}
#end
(My XIB file just contains one image...)
When tapping the image, it crashes. If for example I add an instance variable Target *t to my view controller (and remove local declaration in viewDidLoad), then no issue arises. When not doing that, I overrided dealloc in Target, put a NSLog there and saw that as soon as viewDidLoad finishes execution, the Target object is fred.
Am I doing anything wrong, or is it some issue? (Usually I'm not facing this problem because I use initWithTarget:self ...).
UIGestureRecognizer doesn't retain its target. Most objects that take a target/action pair do not retain their targets. This is mentioned in the Cocoa Fundamentals Guide/Communicating with Objects/The Target-Action Mechanism/The Target:
Control objects do not (and should not) retain their targets. However, clients of controls sending action messages (applications, usually) are responsible for ensuring that their targets are available to receive action messages. To do this, they may have to retain their targets in memory-managed environments. This precaution applies equally to delegates and data sources.
You need to make sure the target is retained some other way, such as by storing a reference to t in an instance variable of your ViewController.
Add an instance variable of the Target t to your ViewController class and make it strong. That way it stays in memory.