Communication between 2 ViewController via Protocol - objective-c

From the AppDelegate I'm communicating with MyViewController via a Protocol. So when this method gets called in AppDelegate.m:
- (void)thisMethodGetsCalled:(CustomData *)data {
//Do stuff
//Then call method via Protocol
[_exampleDelegate exampleMethod:data];
}
It calls this method in MyViewController.m
- (void)thisMethodGetsCalledInsideViewController:(CustomData *)data {
//Do stuff with data
}
//ExampleDelegate.h
#import <Foundation/Foundation.h>
#protocol SMMessageDelegate <NSObject>
- (void)thisMethodGetsCalledInsideViewController:(CustomData *)data;
#end
Everything works fine and as predicted "thisMethodGetsCalledInsideViewController" gets called after "thisMethodGetsCalled". Say MyViewController has never been instantiated then "thisMethodGetsCalledInsideViewController" never gets called. However MyViewController is instantiated and later dismissed with:
[self dismissViewControllerAnimated:YES completion:nil];
my app crashes when "thisMethodGetsCalled" is called. It tries to call "thisMethodGetsCalledInsideViewController" but that method resides inside MyViewController which is dismissed. Does anybody know how to fix this?

First thought:
Somewhere you're setting MyViewController as the delegate of the object that it conforms to the protocol of?
You have to either:
set the delegate value to nil when MyViewController is dismissed, or
set the delegate property to be a weak reference, i.e.
#property (nonatomic,assign) id<TheProtocol> delegate;
Hope that helps.

Related

UIViewController method from AppDelegate

This is my 1st question in stackoverflow.com.
I'm trying to call upon a method [- (void)alterTime] from ViewController.m in AppDelegate.m. Yes, I have #imported "ViewController.h" in AppDelegate.h. Importing the controller into AppDelegate.m makes no difference. Also, "Background fetch" is ON.
Here is the code inside AppDelegate.m that I'm trying to implement:
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
{
NSLog(#"Current Time Altered");
//Get current view controller
ViewController *mainViewController = (ViewController *)self.window.rootViewController;
[mainViewController alterTime];
//Cleanup
completionHandler(UIBackgroundFetchResultNewData);
}
This is the piece of code giving me problems:
[mainViewController alterTime];
It states: "No visible #interface for 'ViewController' declares the selector 'alterTime'".
So that error means that it failed to find the method name (selector), in the class ViewController.
Make sure in your ViewController header you have the alterTime method declared there, effectively making it public.
If that is okay, then double check to make sure that your rootViewController is of type (owned by) ViewController
Declare - (void)alterTime in your ViewController.h file.
#interface ViewController : UIViewController
- (void)alterTime;
#end

Pass a reference to ViewController in prepareForSegue

I have 2 view controllers, call them ViewController1 and ViewController2. A modal segue is invoked from ViewController1 when I want to load ViewController2. I have a method in ViewController1 that needs to be called at some point when ViewController2 is showing. My idea is to have a property in ViewController2 that is a reference to ViewController1 so that I can get access to the method.
#property (strong, nonatomic) ViewController1 *vc1Reference;
This property would be set in the prepareForSegue method like so:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // 1
if ([[segue identifier] isEqualToString:#"sequeToVC2"]) { // 2
ViewController2 *vc2 = segue.destinationViewController; // 3
vc2.vc1Reference = (ViewController1*)segue.sourceViewController; // 4
}
}
However line 4 gives me this error: Implicit conversion of an Objective-C pointer to 'int *' is disallowed with ARC.
How am I supposed to set the reference?
You are on the right track, but the correct way to do this is to use a delegate.
You declare a delegate property in your vc2 #interface:
#property (nonatomic, weak) id <vc2delegate> delegate //[1] (in vc2.h)
And you set the delegate in your prepareForSegue:
vc2.delegate = self; //[2] (in vc1.m)
('self' is the correct reference for vc1, from vc1)
In vc2 you define a protocol, which is the method that you expect vc1 to respond to from vc2. Put this in vc2.h, above your #interface
#protocol vc2delegate //[3] (in vc2.h)
- (void) delegateMethod;
#end
Then you have to ensure you implement that method in vc1. Also you need to let vc1 know to conform to the delegate. Import vc2 into vc1.h, and on your #interface line in vc1 add the protocol name in angle brackets:
#import vc2.h
#interface vc1 <vc2delegate> //[4] (in vc1.h)
This arrangement allows vc2 to pass a method to vc1 without having to #include vc1 or know anything else about it.
more detail...
This is the correct form of your
#property (strong, nonatomic) ViewController1 *vc1Reference;
Note the use of weak reference. You don't want to make a strong reference as you don't really want to have anything to do with the delegate except to know it can handle methods you specify in your protocol. The delegate is often the object that created the delegator, creating a strong reference back in the other direction can cause memory leaks as neither object can go out of existence.
this is the correct form of your line:
vc2.vc1Reference = (ViewController1*)segue.sourceViewController;
Note that we are NOT using type/casting in 1 or 2. For maximum code reuse/decoupling we dont want to make any suppositions on the type of object at either end of the segue.
I am assuming that your 'prepareForSegue' is in vc1. If it is not then the line would look like this:
vc2.delegate = segue.sourceViewController
This is the protocol declaration. It goes in the header file for vc2. vc2 is publishing it's expectations of any object that chooses to become its delegate. vc2 will be sending messages according to this protocol so any delegate needs to respond in the correct way. You can guard against failure in vc2 by using this kind of message-passing
if (self.delegate respondsToSelector:#selector('delegateMethod')
{
[self.delegate delegateMethod];
}
(that is an example of the kind of message passing you would use in vc2 to communicate back to vc1. you can obviously pass paremeters and get returned results back if need be)
this is a helper for the compiler which can issue you with warnings if you fail to implement the protocol.
Finally somewhere in your object definition you need to implement the method:
- (void) delegateMethod
{
// someaction;
}
I ran into something similar the other day. I ended up creating a delegate for vc2, and using
vc2.delegate = self;
in the segue instead. Would this solve your problem? If you need help setting up the delegate, let me know and I'll do my best to help!
Just add a delegate to your the ViewController that you are accessing the delegate on, XCode / Obj-C seems to do the right thing afterwards.
Before:
#interface ViewController2 : UIViewController
#end
After:
#interface ViewController2 : UIViewController
#property (nonatomic, weak) id <UIPageViewControllerDelegate> delegate;
#end

Objective-C delegate function not working correctly

I have an app that has three views. All three views have an Ad Banner at the bottom of the screen. The first view creates an audio streamer which is paused when the Ad Banner on this view is clicked. I'm trying to use the AdBanner delegate methods on the second view to stop/start the audio. When the Ad Banner is selected the AdBanner delegate methods should call my custom delegate functions. The code compiles and runs but doesn't function correctly.
Using NSLog I've determined that the Ad Banner is calling its delegate function correctly but this isn't calling the custom delegate.
Hope this makes sense. Any help would be appreciated. Here is my code.
SecondViewControler H-file
#import "SecondViewController.h"
#protocol demoViewControllerDelegate <NSObject>
#required
-(void)stopSent;
-(void)startSent;
#end
#interface SecondViewController ()
{
id<demoViewControllerDelegate> delegate;
}
#property (nonatomic, assign) id<demoViewControllerDelegate> delegate;
#end
SecondViewController M-file
#implementation SecondViewController
#synthesize delegate;
Protocols
- (BOOL)bannerViewActionShouldBegin:(ADBannerView *)banner willLeaveApplication:(BOOL)willLeave {
[delegate stopSent];
return YES;
}
- (void)bannerViewActionDidFinish:(ADBannerView *)banner{
[delegate startSent];
}
FirstViewController H-file
#import <QuartzCore/QuartzCore.h>
#import "iAd/iAd.h"
#import <MessageUI/MessageUI.h>
#import "AudioStreamer.h"
#import "Reachability.h"
#import "SecondViewController.h"
#import "MFAppDelegate.h"
#import "MFSideMenu.h"
Class secondViewConroller;
#interface DemoViewController : UIViewController <ADBannerViewDelegate,demoViewControllerDelegate> {
}
#end
FirstViewController M-file
-(void)stopSent{
if (isPlaying) {
[streamer stop];
wasPlaying=true;
}
}
-(void)startSent{
if (wasPlaying) {
[streamer start];
isPlaying=true;
}
}
Your protocol methods need to be implemented in the class that you've designated as your delegate target.
It looks like your DemoViewController (or FirstViewController) is the object you've designated as the delegate, since you've given the interface the "<ADBannerViewDelegate,demoViewControllerDelegate>" designations.
Then, from your Second View Controller, you can call the object you designated and set as a delegate by doing:
[delegate startSent];
and
[delegate stopSent];
in the appropriate locations, which appear to be "bannerViewActionShouldBegin" and "bannerViewActionDidFinish", respectively.
You should also make sure that the delegate is properly set, therefore instead of:
[delegate startSent];
you should actually do this:
if(delegate)
[delegate startSent];
else
NSLog( #"delegate is null; we should figure out why" );

How to Call a Controller Class (delegate) method from View Class Event in Objective C/Cocoa

lets say I have an NSWindow Class, that has several events for mouse and trackpad movements
for example this code
IMHO, I think this should be good programming, it is similar to pointing an action of a button to its method in controller.
in MyWindow.m Class I have (which in IB I have set the window class to it)
#implementation MyWindow
- (void)swipeWithEvent:(NSEvent *)event {
NSLog(#"Track Pad Swipe Triggered");
/* I want to add something like this
Note that, Appdelegate should be existing delegate,
not a new instance, since I have variables that should be preserved*/
[AppDelegate setLabel];
}
#end
and in My AppDelegate.h I have
#interface AppDelegate : NSObject <NSApplicationDelegate>
{
IBOutlet NSTextField *label;
}
-(void)setLabel;
#end
and in my AppDelegate.m I have
-(void)setLabel
{
[label setStringValue:#"swipe is triggered"];
}
I have tried #import #class, [[... alloc] init], delegate referencing in IB (I made an object class of MyWindow - thanks to the answer of my previous question )
that latter seems the closest one, it works if both of the classes are delegates, so I could successfully call the "setLabel" action from a button in a second controller (delegate class)'s IBAction method,
but this View Events seem not communicating with the delegate's action although their code is executing.
You are sending a message to the AppDelegate class, not the instance of your AppDelegate class which is accessible from the NSApplication singleton ([NSApplication sharedApplication])
[AppDelegate setLabel];
This is wrong, to get the delegate do this:
AppDelegate* appDelegate = (AppDelegate*)[[NSApplication sharedApplication] delegate];
then send the message to that instance:
[appDelegate setLabel];

UIButton set touch handler in code

I want to accomplish touching a UIButton and having code run in a different class than the owner.
I realize I can do a touchUpInside to the button's owner (ClassA) and then call the method inside ClassB that I want called, but is there any way to expedite this?
ideas:
have ClassB be the delegate for the ClassA->UIButton
set the touchUpInside call in programming to used the function inside ClassB
I'm not sure how to accomplish either of these ideas :( Input is mas appreciated!
One option is to set the button up using
[myButton addTarget:yourOtherClass action:#selector(mySelector:) forControlEvents:UIControlEventTouchUpInside];
but this is a bit dangerous because target is not retained so you could send the message to a deallocated object.
You could instead set up a protocol
MyController.h
#protocol MyControllerDelegate
- (void)myController:(MyController *)controller buttonTapped:(UIButton *)button;
#end
#interface MyController : UIViewController
#property (nonatomic, assign) id <MyControllerDelegate> delegate;
- (IBAction)buttonTapped:(UIButton *)button;
#end
Then in your implementation
MyController.m
- (IBAction)buttonTapped:(UIButton *)button
{
if ([self.delegate respondsToSelector:#selector(myController:buttonTapped:)]) {
[self.delegate myController:self buttonTapped:button];
}
}
As the method defined in the protocol was not optional I could have instead done a check for (self.delegate) to make sure it is set instead of respondsToSelector.