accessing superview properties - objective-c

I've searched for the answer to this question, and the answers I'm finding don't work.
I have a view that is a subclass of UIView to which I've added a property. I would like to access this property from the subviews created by this view. Is that possible?
I've tried referring to self.superview.propertyname but I get an error that propertyname is not found on object of type UIView. Well, right. I realize that since it's a subclass of UIView, it's a UIView, but how can I get it to know about the extra property I added?

You have a number of options, two of them are:
1. Casting:
#implementation SubviewView
- (void)blah
{
((CustomView *)self.superview).property = ...`
}
#end
2. Delegates:
#protocol SubviewViewDelegate
- (void)customView:(SubView *)sv modified:(...)value;
#end
#class SubView
#property (nonatomic, weak) id <CustomViewDelegate> delegate;
#end
#implementation SubviewView
- (void)blah
{
[self.delegate subView modified:...];
}
#end
#implementation CustomView
- (void)subView:(SubView *)sv modified:(...)value
{
self.property = value;
}
#end
Although the second option is more code, I think it is often better suited. Using delegates reduces coupling and works nicely with the Law of Demeter. For more info see this documentation.

Related

Custom delegate for NSWindow

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;

No Setter method for assignment to property - cocoa application

I'm fairly new to objective-c and have just encountered an error i've not seen before.
I'm trying to set a Text Field cell as 'selectable', but i get the error "No Setter method 'setIsSelectable' for assignment to property."
Here are the .h and .m files.
Thanks.
DataPanel.h
#import <Cocoa/Cocoa.h>
#interface DataPanel : NSPanel
#property (weak) IBOutlet NSTextFieldCell *textField;
#end
DataPanel.m
#import "DataPanel.h"
#implementation DataPanel
#synthesize textField = _textField;
- (void) awakeFromNib{
_textField.stringValue = #"1.1 Performance standards The overall objective of the performance standards in Section 1.1 is to provide acoustic conditions in schools that (a) facilitate clear communication of speech between teacher and student, and between students, and (b) do not interfere with study activities.";
_textField.isSelectable = YES;
}
#end
In Objective-C, BOOL properties which start with 'is' are usually the getter of the property only, and not the property itself.
Its a convention.
Just for general knowledge, you can do so yourself by declaring properties in the following manner:
#property (nonatomic, getter=isAvaiable) BOOL available;
So trying to set the above, while using isAvailable will not work, since it is the getter method, and you can't set a getter.
As for your question,
Try changing your code from _textField.isSelectable = YES; to either of the below, and it should work.
_textField.selectable = YES;
[_textField setSelectable:YES];
Good luck mate.

inherit methods declared in .m file

I now know there is no protected method in Objective-C and here is my problem.
I have two viewControllers with many functions and properties that are shared. My vision was to have a BaseViewController holding the shared methods and properties, and from it two classes will inherit and override the needed functionality while using the same variables,
I don't wish to convert the shared functions to public by placing them in the .h file
To help clarify my question I'm adding code :)
#interface BaseViewController ()
#property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *uiButtons;
- (void)setBtns:(NSArray *)p_btns; //tried with & without this line
#end
#implementation BaseViewController
- (void)setBtns:(NSArray *)p_btns {
uiButtons = p_btns;
//do something generic with the buttons (set font, image etc.)
}
#end
#interface DerivedViewController ()
#property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *buttonsConnectedToTheActualView;
#end
#implementation DerivedViewController
- (void) setBtns:(NSArray *)p_btns {
[super setBtns:p_btns];
//do something specific with the buttons (decide if they face up or down according to this class logic)
}
#end
The call to [super setBtns:p_btns]; raises an error:
DerivedGameViewController.m:No visible #interface for 'BaseViewController' declares the selector 'setBtns:'
How can I achieve this? Can someone post a snippet or point to my mistake (in code or concept).
Just create a second header with the protected methods declared in a category. Name and document the header appropriately.
UIGestureRecognizer.h and UIGestureRecognizerSubclass.h may server you as an example.

Calling a method from another 'm' file

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.

Can an inherited #property not satisfy a <protocol> #property?

I've got a protocol:
#protocol Gadget <NSObject>
#property (readonly) UIView *view;
- (void) attachViewToParent:(UIView *)parentView;
#end
And an "abstract" base class, with an implementation (as a getter, not shown) of -(UIView *)view:
// Base functionality
#interface AbstractGadget : NSObject {
UIView *view;
}
#property (readonly) UIView *view;
#end
But when I implement the Gadget protocol in a subclass of AbstractGadget, like so:
// Concrete
#interface BlueGadget : AbstractGadget <Gadget> {
}
- (void) attachViewToParent:(UIView *)parentView;
#end
#implementation BlueGadget
- (void) attachViewToParent:(UIView *)parentView {
//...
}
#end
I get a compiler error telling me "warning: property 'view' requires method '-view' to be defined." I can make this go away using #dynamic, or adding a stub method:
- (UIView *) view {
return [super view];
}
But I just want to know if I'm doing something that's not supported, something I shouldn't be doing, or if it's just a limitation / bug in the compiler?
By declaring the property as #dynamic you are telling the compiler that the property getter (and setter if required) are implemented elsewhere (potentially at runtime). This sounds like a perfectly reasonable use case to me.
See The Docs for more information.
I also came across this exact issue. This is one of situations that #dynamic is there for.
Here is the rule for variable, property and synthesize in objective-C:
If you have a property, you must have a #synthesize or you declare #dynamic and write the getter and setter method yourself.
So, because you have a property called view, you have to declare #synthesize. That should be it. Nothing to do with #protocol, inheritance