I've created a protocol and matching delegate.
The methods I've defined don't seem to fire, but throw up no errors.
ResourceScrollView.h
//Top of File
#protocol ResourceScrollViewDelegate
- (void)loadPDFWithItem:(NSString *)filePath;
#end
//Within Interface
id<ResourceScrollViewDelegate> _scrollViewDelegate;
//Outside Interface
#property (nonatomic, retain) id<ResourceScrollViewDelegate> scrollViewDelegate;
ResourceScrollView.m
//Inside Implementation
#synthesize scrollViewDelegate=_scrollViewDelegate;
//Within a button tapped method
[_scrollViewDelegate loadPDFWithItem:filePath];
ResourcesViewController.h
//Top of File
#import "ResourceScrollView.h"
#interface ResourcesViewController : UIViewController <ResourceScrollViewDelegate>
ResourcesViewController.m
//Inside Implementation
- (void)loadPDFWithItem:(NSString *)filePath{
NSLog(#"PDF %#",filePath);
}
For some reason I'm not getting the NSLog.
There are no errors or crashes, it simply does not fire.
Have I made any errors that could account for this behaviour?
Have you forgotten to set the ResourcesViewController Object to scrollViewDelegate property?
Related
The original question remains below this update:
So further research indicates that my
"...missing setter or instance variable"
log messages are due to an unhinged .xib.
I originally thought that might be the case which is why I went through the process of re-connecting the outlets and properties in the graphic interface builder, but that seems to have been insufficient to repair the connections.
I restored the outlets as properties rather than iVars and reconnected again, still to no avail. So I'm in the process of remaking the .xib's from scratch. Stay tuned for the results.
Original question follows:
Having declared and synthesized properties in parent and sheet classes, and attempted therein to access the properties by their respective class.property names, Xcode rejects the code.
I posted a similar question recently and deleted it after being told there was not enough info to make a response, so I include here below a mini-app which shows how the relevant setup was in the real app of over 2000 lines of Objective-C, which built and ran properly before I attempted to add the Parent / Sheet properties feature.
I've indicated the compiler error messages with a prefix of ////. When I comment out the erroneous lines, the app with its .xib's builds and runs, dysfunctionally of course.
ParentClass.h
// ParentClass stuff belongs in the original main window controller
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#interface ParentClass : NSObject
{
IBOutlet NSTextField * messageTextField;
IBOutlet NSButton * proceedButton;
}
#property (assign) IBOutlet NSWindow * window;
#property (strong) NSMutableString * parentPropInfo;
- (IBAction) awakeFromNib;
- (IBAction) doCreate:(id)sender;
#end
ParentClass.m
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#import "ParentDelegate.h"
#import "ParentClass.h"
#import "SheetClass.h"
#implementation ParentClass
ParentDelegate * MyDelegate; // only confirms termination requests
NSWindowController * SheetController;
#synthesize parentPropInfo;
- (IBAction)awakeFromNib {
MyDelegate = [NSApplication sharedApplication].delegate;
MyDelegate.ParentController = self; // BTW, this property assignment works!
SheetController = [[SheetClass alloc] initWithWindowNibName: #"SheetClass"];
messageTextField.stringValue = #"Click Proceed button";
}
- (IBAction)doProceed*emphasized text*:(id)sender {
parentPropInfo = #"Hello!".mutableCopy; // to be read by the sheet
[NSApp runModalForWindow:SheetController.window];
// Sheet is active now until it issues stopModal, then:
messageTextField.stringValue = SheetController.sheetPropInfo; // set by the sheet
////above gets ERROR "Property sheetPropInfo not found on object of type 'NSWindowController *'"
messageTextField.stringValue = SheetController.window.sheetPropInfo;
////above gets ERROR "Property sheetPropInfo not found on object of type 'NSWindow *'"
[NSApp endSheet: SheetController.window];
[SheetController.window orderOut:self];
}
#end
SheetClass.h
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#import "ParentClass.h"
#interface SheetClass : NSWindowController
{
IBOutlet NSTextField * propTextField;
IBOutlet NSButton * cancelButton;
}
#property (assign) IBOutlet NSWindow * window;
#property NSMutableString * sheetPropInfo;
- (IBAction)awakeFromNib;
- (IBAction)doCancel:(id)sender;
#end
SheetClass.m
#import "SheetClass.h"
#import "ParentClass.h"
#implementation SheetClass
#synthesize sheetPropInfo;
- (IBAction)awakeFromNib {
propTextField.stringValue = self.window.sheetParent.parentPropInfo; // set by the parent
////above gets ERROR "Property parentPropInfo not found on object of type 'NSWindow *'"
sheetPropInfo = #"Goodbye!".mutableCopy; // to be read by the parent
}
- (IBAction)doCancel:(id)sender {
[NSApp stopModal];
}
#end
I can find nothing in Apple documentation or extensive (three weeks now!) online search to offer any insight as to my abysmal ignorance. I apologize for the overwhelming batch of code needed to illustrate my problem! Where shall I obtain the information I need?
The error messages are perfectly clear. Just read them and think about them. Let's just take the first one. You are saying:
messageTextField.stringValue = SheetController.sheetPropInfo;
...and getting this response from the compiler:
// Property sheetPropInfo not found on object of type 'NSWindowController *'
Well, think about the expression SheetController.sheetPropInfo and why the compiler cannot make sense of it. You have declared SheetController as follows:
NSWindowController * SheetController;
So that is all the compiler knows: SheetController is an NSWindowController. Well, sure enough, just as the compiler says, sheetPropInfo is not a property of NSWindowController. It is a property of SheetClass (which is not the same as NSWindowController; it is a subclass of NSWindowController).
If you know that SheetController is in fact a SheetClass instance, you need to tell the compiler that fact. You must either declare SheetController as a SheetClass or cast it down from an NSWindowController to a SheetClass.
I want to create a custom delegate for NSWindow.
CustomWindow is subclassed to get notified about NSWindowDelegate events.
Now I want to create delegate for this CustomWindow.
I tried following code:
CustomWindow.h
#class CustomWindow;
#protocol CustomWindowDelegate
- (void)method1:(CustomWindow *)sender userInfo:(NSMutableDictionary*) userInfo;
- (void)method2:(CustomWindow *)sender event:(NSEvent *)theEvent;
- (void)method3:(CustomWindow *)sender;
#end
#interface CustomWindow : NSWindow <NSWindowDelegate>
#property (nonatomic) id <CustomWindowDelegate> delegate;
#end
mainDocument.h
#import "CustomWindow.h"
#interface mainDocument : NSDocument
#property (assign) IBOutlet CustomWindow *mainWindow;
#end
mainDocument.m
#import "mainDocument.h"
#implementation mainDocument
- (void)method1:(CustomWindow *)sender userInfo:(NSMutableDictionary*) userInfo
{
...
...
}
- (void)method2:(CustomWindow *)sender event:(NSEvent *)theEvent
{
...
...
}
- (void)method3:(CustomWindow *)sender
{
...
...
}
#end
Its working as per expectations however its giving following warnings:
'retain (or strong)' attribute on property 'delegate' does not match the property inherited from 'NSWindow'
'atomic' attribute on property 'delegate' does not match the property inherited from 'NSWindow'
Property type 'id' is incompatible with type 'id _Nullable' inherited from 'NSWindow'
Auto property synthesis will not synthesize property 'delegate'; it will be implemented by its superclass, use #dynamic to acknowledge intention
How can I get rid of these warnings ?
Any helps are greatly appreciated.
NSWindow already has a delegate property and it uses its delegate for different purposes than you're using yours for. The errors are conflicts between your declaration of your delegate property with the declaration of the inherited property.
The simplest solution is for you to rename your property to customDelegate or something like that. Also, the general convention is for delegate properties to be weak, so you should probably declare yours as weak, too.
In general, one could combine a new delegate protocol with NSWindowDelegate and re-use the existing delegate property. In your case, though, since you've declared CustomWindow to conform to NSWindowDelegate, it seems like you're planning on making the window object its own delegate. So, that would conflict with this approach. But, for completeness, if you were going to do that you'd declare your protocol as an extension of NSWindowDelegate:
#protocol CustomWindowDelegate <NSWindowDelegate>
Your property declaration would have to have the same attributes as NSWindow's declaration of its delegate property. So:
#property (nullable, assign) id<CustomWindowDelegate> delegate;
Finally, since you're relying on NSWindow to actually provide the storage and accessor methods of the property, you'd fix the last warning by putting this in the #implementation of CustomWindow:
#dynamic delegate;
I'm having trouble calling an external method from a NSTextField Subclass. I have a NSTextField subclass that I'm monitoring text with and when textDidChange I'm calling an external method that is of a NSObject class. For some reason my method doesn't get called. I'm hoping someone could explain why. My code is below. Thanks.
EDIT: Fixed code to what drewag suggested.
MyTextField.h
#import <Cocoa/Cocoa.h>
#class ObjectController;
#interface MyTextField : NSTextField <NSTextFieldDelegate> {
ObjectController *objectController;
}
#property (strong, nonatomic) ObjectController *objectController;
#end
MyTextField.m
#import "MyTextField.h"
#import "ObjectController.h"
#implementation MyTextField
#synthesize objectController;
- (void)textDidChange:(NSNotification *)notification{
[objectController methodFromOtherClass];
}
#end
ObjectController.h
#import <Foundation/Foundation.h>
#interface ObjectController : NSObject {
}
- (void)methodFromOtherClass;
#end
ObjectController.m
#import "ObjectController.h"
#implementation ObjectController
- (void) methodFromOtherClass {
NSLog(#"Testing");
}
There are two likely scenarios causing this issue:
objectController is nil when textDidChange: is called
textDidChange: is not actually being called because you didn't connect it up correctly.
I don't think anyone can help you beyond that since you did not post your connection code nor where you are setting objectController.
Probably forgot to assign the delegate to self or whatever object that implements the textDidChange? Happens to me a lot.
I fixed the issue by creating an instance of ObjectController instead of the above process.
ObjectController *objectController = [ObjectController new];
[objectController methodFromOtherClass];
-(IBAction)ok
{
//send message to the delegate with the new settings
[self.delegate setHeight:_height Width:_width Mines:_mines];
[self.delegate dismissViewControllerAnimated:YES completion:nil];
}
the first message to the delegate wouldn't work until i imported ViewController.h, but the second one worked without the import.
if i add -(void)setHeight:(int)h Width:(int)w Mines:(int)m; as required in the optionsViewController protocol will that mean that i no longer have to import the root .h file.
i intend to use delegation to send messages in other parts of the program so i want to make sure i am using it correctly and not importing things when i don't need to.
Thank you.
if i add -(void)setHeight:(int)h Width:(int)w Mines:(int)m; as required in the optionsViewController protocol will that mean that i no longer have to import the root .h file.
Yes! You could also add it as #optional and it would work (remember to check if the delegate -respondsToSelector: in that case). The whole idea is that your object regularly knows nothing about the delegate object - except that it conforms to the protocol (ie implements the #required and possibly the #optional methods).
Added for clarification (on my phone, which is a pain in the butt):
//OptionsViewController.h
//this object does NOT have to import
//the calling viewControllers .h file
//which is what I think the OP does
#protocol optionsViewControllerProtocol;
#interface OptionsViewController : UIViewController
#property (nonatomic, assign) id<optionsViewControllerProtocol> delegate; //should be id, could be UIViewController too, if absolutely necessary (better design to make it id)
#end
#protocol optionsViewControllerProtocol <NSObject>
#required
-(void) setHeight: (NSInteger) height;
#end
//viewController.h
#import "optionsViewController.h" //necessary to get the protocols definitions
#interface OptionsViewController: UIViewController <optionsViewControllerProtocol>
//.....
If you define your delegate property to be of class UIViewController*, then the compiler will recognize the dismissViewControllerAnimated:completion: method without you needing to import anything, since that's a standard method for that class.
For a custom method, i.e. setHeight:Width:Mines:, you absolutely need to import the header file, or have it imported somewhere up the import chain.
Example: You have MyProtocol.h, and you want SomeClass to have a delegate property that conforms to that protocol. If you #import "MyProtocol.h" in SomeClass.h, you don't need to re-import it in SomeClass.m.
// SomeClass.h
#import "MyProtocol.h"
#interface SomeClass : NSObject
#property (weak, nonatomic) id<MyProtocol> delegate;
#end
//SomeClass.m
#import "SomeClass.h"
#implementation SomeClass
- (void)someMethod
{
[self.delegate myProtocolMethod];
}
#end
The bulk of the code for my app is in a 'm' file called MyViewController. The app implements a custom UIView which contains a UIWebView object. The code for the UIView and UIWebView is kept in a separate 'm' file called CustomUIView.
I have managed to override clicks on URL hyperlinks in the UIWebView object using a delegate. However, I would like to have these clicks launch a method that is stored in my main app code. This method is called "popupView", and takes a single argument, "inputArgument". The inputArgument is the text of the URL the user clicks on. In fact, this method is the very same one that causes my custom UIView to launch.
Anyway, what I'd like to do is have my overridden URL clicks cause the popupView method to launch, thus causing another UIView to open on top of the one that was clicked on.
The problem is that the 'm' file where the URL clicks are detected can't see the 'popupView' method as it is included in the MyViewController 'm' file. How do I call the popupView method from another 'm' file?
Directly
Declare MyViewController's method -popupView: in MyViewController.h.
#import MyViewController.h in CustomUIView.m.
Give CustomUIView a reference to the [one] instance of MyViewController, for example by way of an #property declared in CustomUIView.h.
For (1), the #interface of MyViewController (in MyViewController.h) should look a bit like this
#interface MyViewController : UIViewController
{
//....
}
- (void)popupView:(NSString *)urlText;
//....
#end
For (2), UIViewController.m should have the following somewhere near the top
#import "CustomUIView.h"
#import "MyViewController.h"
For (3), the #interface in CustomUIView.h should look something like
#interface CustomUIView : UIView
{
//....
}
#property (nonatomic, weak) MyViewController *viewController;
#end
This property will need to be set some time after the instance of CustomUIView owned by MyViewController is created. If your CustomUIView is in MyViewController.xib, you can set this property on it by adding the keyword IBOutlet to the property's declaration like this
#property (nonatomic, weak) IBOutlet MyViewController *viewController;
and pointing this property to "File's Owner" in the XIB. If instead, you create the CustomUIView programmatically, you can set this property on it as soon as you have initialized it.
Delegate
This, however, is far from being a best practice. It would be much better to make use of the delegate pattern. To do this, you'll need to
Define a delegate protocol.
Add a "delegate" #property to CustomUIView.
Call the delegate methods on the delegate object at the appropriate times.
Implement the protocol in MyViewController.
Set the "delegate" #property of the instance of CustomUIView owned by the MyViewController instance to be the MyViewController instance.
Let's call our delegate protocol something imaginative like CustomUIViewDelegate. For (1), we'll declare it at the top of CustomUIView.h as follows:
#class CustomUIView;
#protocol CustomUIViewDelegate <NSObject>
- (void)customUIView:(CustomUIView *)customView didSelectURLText:(NSString *)urlText;
#end
Notice that we've had to forward declare our class CustomUIView so that the compiler is able to make sense of the type of the first argument in the protocol method customUIView:didSelectURLText:.
For (2), we'll do something quite similar to (3) above: Your CustomUIView #interface will look something like
#interface CustomUIView : UIView
{
//....
}
#property (nonatomic, weak) id<CustomUIViewDelegate> *delegate;
#end
Again, if we're going to set this property in Interface Builder, we'll need to use the IBOutlet keyword to announce it to IB:
#property (nonatomic, weak) IBOutlet id<CustomUIViewDelegate> *delegate;
For (3), we need to call the delegate method customUIView:didSelectURLText: on our delegate object self.delegate at the appropriate time.
In your question, you wrote
I have managed to override clicks on URL hyperlinks in the UIWebView object using a delegate.
So, let's say that CustomUIView has an instance method
- (void)didSelectURL:(NSURL *)url
{
//....
}
which you call when the user selects a link in the UIWebView. The CustomUIView's delegate needs to be informed of this:
- (void)didSelectURL:(NSURL *)url
{
//...
if ([self.delegate respondsToSelector:#selector(customUIView:didSelectURLText:)]) {
{
[self.delegate customUIView:self didSelectURLText:url.absoluteString];
}
}
Notice that we check first whether the CustomUIView instance's delegate object implements the selector of interest (customUIView:didSelectURLText:) by calling respondsToSelector: on it.
For (4), we'll need first to add <CustomUIViewDelegate> to MyViewController's #interface declaration and be sure to #import CustomUIView.h into the file where we use the symbol CustomUIViewDelegate. Our MyViewController's #interface will look something like this:
#import "CustomUIView.h"
#interface MyViewController : UIViewController <CustomUIViewDelegate>
{
//....
}
//....
#end
More importantly, we need to implement the CustomUIViewDelegate protocol in MyViewController's #implementation; so far we've only declared that MyViewController adopts it.
To do this, since our protocol consists of only one method, we'll need only to add our own implementation of -customUIView:didSelectURLText:. Our MyViewController's #implementation will look something like this:
#import "MyViewController.h"
#implementation MyViewController
//....
- (void)popupView:(NSString *)urlText
{
//....
}
#pragma mark - CustomUIViewDelegate
- (void)customUIView:(CustomUIView *)customView didSelectURLText:(NSString *)urlText
{
[self popupView:urlText];
}
//....
#end
Finally, for (5), we'll need to set the delegate property of the instance of CustomUIView owned by the MyViewController instance. I don't know enough about MyViewController's relationship with its CustomUIView instance to do describe how to do this definitively, but I'll provide an example: I'll assume that you programmatically, in -[MyViewController loadView] add the CustomUIView as a subview of MyViewController's view. So your implementation of -loadView looks a bit like this:
- (void)loadView
{
[super loadView];
//....
CustomUIView *customView = //....
//....
[self.view addSubview:customView];
//....
}
All that remains to do at this point is to set the delegate #property of the local variable customView to self:
customView.delegate = self;
Edit: Updated (5) in light of new information about the relationship between CustomUIView and MyViewController.
In your comment, you write that your CustomUIView is added as a subview of cvc.view where cvc is an instance of CustomUIViewController in CustomUIView's method -[CustomUIView show]. On account of this, you note that writing customView.delegate = self; is the same as writing self.delegate = self, which is clearly not what you want to do.
You want to set the CustomUIView's delegate property to be the instance of MyViewController. Consequently, your method -[CustomUIView show] should look something like
- (void)show
{
//....
[cvc.view addSubview:self];
self.delegate = mvc;
}
where mvc is the instance of MyViewController.
Well, since you are writing the CustomUIView, why not implement a constructor like initWithPopupDelegate:(MyViewController *)delegate and keep a reference to the MyViewController instance that way in an instance variable, then call the method on that.
(Add #class MyViewController; at the top CustomUIView.h, and #import "MyViewController.h" at the top of CustomUIView.m so the compiler knows the class you are using.)
Alternatively, if there is ever only one MyViewController instance, you can define a class method for MyViewController, e.g., + (MyViewController *)instance, and have that return a reference to the one instance (which you store in a class variable and set the first time when you create the instance, see “singleton pattern”). But without knowing the specifics of your code, I would suggest the first solution (delegate) as simpler and more flexible.