I think I understand the logic behind a delegate. I got more the problem to use it. How many steps are involved? Do I have to use existing delegates? Or can I use my one ones?
In my example I got the AppDelegate that created many views (Objects / View Controllers) of the same type. Each view should somehow call a method on the AppDelegate to close itself. This would happen when a button within the view is touched. The method call would include the reference of the view (self).
So far I know from other languages responders, event listeners and so on. They are so simple to use.
Can anybody help me. I just found massive examples with a lot of code in the web. It can't be that hard to just call a parent in Objective C.
I think you should use for this the NSNotificationCenter
in you AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
...
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(buttonPushed:) name:#"ButtonPushedNotification" object:nil];
}
- (void)applicationWillTerminate:(UIApplication *)application
{
...
...
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
this is the selector it will be called when the notification happens (we are still in the AppDelegate.m)
- (void)buttonPushed:(NSNotification *)notification {
NSLog(#"the button pushed...");
}
and in the ViewController.m when the button pushed (inside the method), you should post a notification like this:
{
...
[[NSNotificationCenter defaultCenter] postNotificationName:#"ButtonPushedNotification" object:nil];
...
}
You can create your own:
In MyView1.h:
#class MyView1;
#protocol MyView1Delegate <NSObject>
- (void)closeMyView1:(MyView1 *)myView1;
#end
#interface MyView1 : NSObject
{
id<MyView1Delegate> _delegate;
}
#property (assign, nonatomic, readwrite) id<MyView1Delegate> delegate;
...
#end
In MyView1.m:
#interface MyView1
#synthesize delegate = _delegate;
...
// The method that tells the delegate to close me
- (void)closeMe
{
....
if ([_delegate respondsToSelector:#selector(closeMyView1:)])
{
[_delegate closeMyView1:self];
}
}
#end
In AppDelegate.h:
#import "MyView1.h"
#interface AppDelegate <MyView1Delegate>
{
MyView1 *_myView1;
}
...
#end
In AppDelegate.m:
- (void)someCreateViewMethod
{
_myView1 = [[MyView1 alloc] initWithFrame:NSMakeRect(0, 0, 100, 200)];
[_myView1 setDelegate:self];
...
}
An easy way to get what you want is to just start with one view. Then, have each other view be presented modally. When the button in the view is pressed do
[self dismissModalViewControllerAnimated:YES];
And here's something I made a while ago when I was starting iPhone development that might help you with delegates
Delegates
//In parent .m file:
//assign the delegate
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"segueName"])
{
childController *foo = segue.destinationViewController;
foo.delegate = self;
}
}
//implement protocol method(s):
- (void) methodName:(dataType*) dataName
{
//An example of what you could do if your data was an NSDate
buttonLabel.titleLabel.text = [[date description] substringToIndex:10];
}
//In parent .h file:
//import child header
#import "ChildName.h"
//indicate conformity with protocol
#interface ParentName : UIViewController <ChildNameDelegate>
//In child .h file
//declare protocol
#protocol ChildNameDelegate
- (void) methodName:(dataType*) dataName;
#end
//declare delegate
#property (unsafe_unretained, nonatomic) id<ChildNameDelegate> delegate;
//In child .m file
//synthesize delegate
#synthesize delegate;
//use method
- (IBAction)actionName:(id)sender
{
[delegate methodName:assignedData];
}
Related
I have been struggling with this for a few days and have received valuable help on the way from S.O. I have made the simplest possible project to reduce the possibilities of it being a typo.
All my project is, is a ViewController that holds a container view hooked to a childViewController. The "parent" ViewController is set as the delegate of the childViewController. In the viewDidLoad of the child I am passing a value which is just a string. This string should be passed on to the parent and printed on the console. Here are the files.
ViewController.h
#import <UIKit/UIKit.h>
#import "ChildViewController.h"
#interface ViewController : UIViewController <ChildViewControllerDelegate>
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#property NSString *myValueRetrieved;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
ChildViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"ChildVC"];
controller.delegate = self;
NSLog(#"Here is my value: %#",self.myValueRetrieved);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void) passValue:(NSString *)theValue{
self.myValueRetrieved = theValue;
}
#end
ChildViewController.h
#import <UIKit/UIKit.h>
#protocol ChildViewControllerDelegate;
#interface ChildViewController : UIViewController
#property (weak)id <ChildViewControllerDelegate> delegate;
#end
#protocol ChildViewControllerDelegate <NSObject>
- (void) passValue:(NSString*) theValue;
#end
ChildViewController.m
#import "ChildViewController.h"
#interface ChildViewController ()
#property NSArray *colors;
#end
#implementation ChildViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.delegate passValue:#"Hello"];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#end
Am I right to think that when the app is launched, the console should log the following message: "here is my value: hello". Am I doing something wrong in terms of logically not getting delegation or is it just a silly typo somewhere? tx
You're assuming that the view is loaded when the view controller is instantiated. That's now how it works. The view gets loaded when it's needed (like to add to the parent view).
But you can force the view to load and make this work. Call -loadViewIfNeeded on the child view controller right after setting the delegate. That will probably get you what you want:
controller.delegate = self;
[controller loadViewIfNeeded];
NSLog(#"Here is my value: %#",self.myValueRetrieved);
Or, if you do want to call back the delegate in viewDidLoad, then you'd need to move the NSLog to the -passValue: method, since the primary view controller's viewDidLoad method will have already finished running.
To do this make ParentController a delegate of ChildController. This allows ChildController to send a message back to ParentController enabling us to send data back.
For ParentController to be delegate of ChildController it must conform to ChildController's protocol which we have to specify. This tells ParentController which methods it must implement.
In ChildController.h, below the #import, but above #interface you specify the protocol.
#class ChildController;
#protocol ViewControllerBDelegate <NSObject>
- (void)addItemViewController:(ChildController *)controller didFinishEnteringItem:(NSString *)item;
#end
next still in the ChildController.h you need to setup a delegate property and synthesize in ChildController.h
#property (nonatomic, weak) id <ChildControllerDelegate> delegate;
In ChildController we call a message on the delegate when we pop the view controller.
NSString *itemToPassBack = #"Pass this value back to ParentController";
[self.delegate addItemViewController:self didFinishEnteringItem:itemToPassBack];
That's it for ChildController. Now in ParentController.h, tell ParentViewController to import Child and conform to its protocol.
import "ChildController.h"
#interface ParentController : UIViewController
In ParentController.m implement the following method from our protocol
- (void)addItemViewController:(ChildController *)controller didFinishEnteringItem:(NSString *)item
{
NSLog(#"This was returned from ChildController %#",item);
}
The last thing we need to do is tell ChildController that ParentController is its delegate before we push ChildController on to nav stack.
ChildController *ChildController = [[ChildController alloc] initWithNib:#"ChildController" bundle:nil];
ChildController.delegate = self
[[self navigationController] pushViewController:ChildController animated:YES];
I have successfully implemented a file-drop functionality in my app. The Application window has a few NSTabView objects where dropping on them does not work. Anywhere else in the window the file-drop works fine.
I have tried to make the app delegate a delegate for the NSTabView, but this did not help.
Anyone have a setup for the NSTabView not to filter out the drop-actions so the whole window can be transparent to the file-drop actions ?
For a more generic solution than olekeh's I made it IB friendly so you can hook it up to any object that complies with the NSDraggingDestination protocol.
#import <Cocoa/Cocoa.h>
#interface DropFilesView : NSView
#property (nullable, assign) IBOutlet id<NSDraggingDestination> dropDelegate;
#end
#implementation DropFilesView
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
}
-(void) awakeFromNib {
[self registerForDraggedTypes:
[NSArray arrayWithObjects:NSFilenamesPboardType,
(NSString *)kPasteboardTypeFileURLPromise,kUTTypeData, NSURLPboardType, nil]]; //kUTTypeData
[super awakeFromNib];
}
-(NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender{
return [self.dropDelegate draggingEntered:sender];
}
- (BOOL)performDragOperation:(id < NSDraggingInfo >)sender {
return [self.dropDelegate performDragOperation:sender];
}
#end
I found the solution to this !! - I am posting it here for others who might need.
The NSTabView object has for each of its tabs an NSTabViwItem.
Under each of those, there is a regular NSView - that I subclassed with the following code: - The code assumes that you already have "draggingEntered" and "performDragOperation" in your AppDelegate as this class just forwards these messages to the app delegate. You will also need to put the declarations for those methods in you AppDelegate.h
// DropFilesView.h
#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
#interface DropFilesView : NSView
#end
and the implementation:
// DropFilesView.m
#import "DropFilesView.h"
#implementation DropFilesView
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
}
-(void) awakeFromNib {
[self registerForDraggedTypes:
[NSArray arrayWithObjects:NSFilenamesPboardType,
(NSString *)kPasteboardTypeFileURLPromise,kUTTypeData, NSURLPboardType, nil]]; //kUTTypeData
[super awakeFromNib];
}
-(NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
{
AppDelegate* del = [AppDelegate sharedAppDelegate];
return [del draggingEntered:sender];
}
- (BOOL)performDragOperation:(id < NSDraggingInfo >)sender {
AppDelegate* del = [AppDelegate sharedAppDelegate];
return [del performDragOperation:sender];
}
#end
In Interfacebuilder, I set the new class for all the NSView objects covering areas where drop does not work, to this new one.
A similar approach can be used for NSImageView and the WebView classes. However, for the last one, do not use [super awakeFromNib] to prevent the default drag-and drop handling for the web view object.
I'm quite new to Mac programming (not to Objective C).
I'm developing a small application, that shows some data and opens a second window on button press.
In the second window is a textfield and a submit button. If the submit button is pressed, the window should close + the value of the textfield needs to be passed to the first window.
I think the best method for that is a simple Delegate. I tried that but i can't change the label in the first window using the second window..
The delegate however seems to work as i can call methods from the other class and send data to it. It just won't change the label.
As this is my first try on Delegates, im pretty sure I've done something stupid here^^
or is there a better solution? Can't be to complicated to change a label from an second window.. right?
ViewController.h (FirstController)
#import <Cocoa/Cocoa.h>
#class ViewController;
#protocol ViewControllerDelegate
-(void)sayHello:(ViewController *)ViewController;
#end
#interface ViewController : NSViewController
{
IBOutlet NSTextField *txtlabel;
}
#property (nonatomic, assign) id delegate;
-(void)helloDelegate;
-(void)reciveVar:(NSString*)strvar;
#end
ViewController.m (FirstController)
#import "ViewController.h"
#implementation ViewController
#synthesize delegate;
-(id)init {
self = [super init];
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
txtlabel.stringValue=#"TEST";
}
-(void)helloDelegate
{
[delegate sayHello:self];
}
-(void)reciveVar:(NSString*)strvar
{
NSLog(#"recived: %#", strvar);
txtlabel.stringValue=strvar; // DOSENT WORK!!
}
#end
secondController.h
#import <Cocoa/Cocoa.h>
#import "ViewController.h"
#interface secondController : NSViewController <ViewControllerDelegate>
{
IBOutlet NSTextField *txtfield;
}
-(IBAction)submit:(id)sender;
#end
secondController.m
#import "firstController.h"
#implementation secondController
-(void)viewDidLoad
{
[super viewDidLoad];
ViewController *custom = [[ViewController alloc] init];
// assign delegate
custom.delegate = self;
[custom helloDelegate];
}
-(void)sayHello:(ViewController *)ViewController
{
NSLog(#"Hiya!");
}
-(IBAction)submit:(id)sender
{
NSString *txtval= txtfield.stringValue;
NSLog(#"submit: %#", txtval);
ViewController *custom = [[ViewController alloc] init];
// assign delegate
custom.delegate = self;
[custom reciveVar:txtval];
}
#end
LOG Output:
Hiya!
submit: test
recived: test
(so i guess the delegate works..)
SOLVED. (Thanks to Phillip Mills)
NSNotification is way simpler and efficient than Delegates in this case.
ViewController.m
[...]
- (void)viewDidLoad
{
[super viewDidLoad];
txtlabel.stringValue=#"TEST";
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleUpdatedData:)
name:#"DataUpdated"
object:nil];
}
-(void)handleUpdatedData:(NSNotification *)notification
{
NSLog(#"recieved %#", notification);
txtlabel.stringValue=[notification object];
}
secondController.m
-(IBAction)submit:(id)sender
{
NSString *txtval= txtfield.stringValue;
NSLog(#"submit: %#", txtval);
[[NSNotificationCenter defaultCenter] postNotificationName:#"DataUpdated"
object:txtval];
}
In my ViewController.m I have my void "Save Data":
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize myDatePicker, myTextField;
-(void)saveData
{
NSArray *value = [[NSArray alloc] initWithObjects:[myTextField text],[myDatePicker date], nil];
[value writeToFile:[self getFilePath] atomically:YES];
}
And I want to use my void "Save Data" in my AppDelegate.m :
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self.ViewController saveData];
}
But Xcode doesn't recognize the "ViewController" neither "saveData".
I don't know if I have to #import something in my AppDelegate, please help.
How you access your viewcontroller from the app delegate will depend on how many viewcontrollers you have / where it is in your current navigation hierarchy, etc etc. You could add a property for it, or perhaps it will be your app delegate's rootViewController.
However, you'd probably be better off listening out for a notification in your viewcontroller when the app enters the background. This means that the logic you need can be entirely self contained within your viewcontroller, and your app delegate doesn't need to know anything about it.
In your viewcontroller's initialization method, you can register to receive notifications:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
Then, implement the method:
- (void)applicationDidEnterBackground:(NSNotification *)notification
{
[self saveData];
}
And make sure you also remove yourself as an observer in your viewcontroller's dealloc method:
-(void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
}
In your "appDelegate.m" implementation file at the top do this import ->
#import "ViewController.h"
This will make the custom class ViewController visible to the appDelegate.
In order to be able to call the -(void)saveData method from another class (in our case the other class is the AppDelegate class), this method needs to be declared in the public interface. That means ->
#import "ViewController.h"
#interface ViewController ()
-(void)saveData;
#end
Good day everyone,
I am not able to call the method refresh in my ViewController when using a Storyboad. However it works if I create the project without the Storyboard, just a NIB. Why it doesn't work and how to overcome this issue?
Here is my code:
AppDelegate.h
#import <UIKit/UIKit.h>
#class ViewController;
#interface AppDelegate : NSObject <UIApplicationDelegate>
{
UIWindow *window;
ViewController *viewController;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet ViewController *viewController;
#end
AppDelegate.m
#import "AppDelegate.h"
#import "ViewController.h"
#implementation AppDelegate
#synthesize window;
#synthesize viewController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
NSLog(#"app will enter foreground");
[viewController refresh:NULL];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
#end
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
-(IBAction) refresh:(id)sender;
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (void)loadData {
// contains information the ViewController makes use of
}
-(IBAction)refresh:(id) sender {
[self loadData];
}
#end
The viewController property in your app delegate are not populated when using a storyboard. Include the value in your log statement for confirmation - it will be null.
Register your view controller for the notification instead, and let it handle it's own refreshing.
In viewDidLoad:
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(refresh:)
name:UIApplicationDidBecomeActiveNotification
object: nil];
You'll want to remove this observer in dealloc / viewDidUnload as well.