On Objective-C delegates, clarification needed - objective-c

If my UIView (MyUIView) declares and #synthesize(s)
#property (nonatomic, weak) id<UIScrollViewDelegate> delegate;
and MyController is declared as
#interface MyController : UIViewController <UIScrollViewDelegate>
and the following is true (inside MyController)
if ([self.view isKindOfClass:[MyUIView class]]){
NSLog(#"yes"); // We see this
}
Why is the following a problem?
self.view.delegate = self; // Does not see "delegate"
Meanwhile, if i manually create an instance of MyView, it works:
MyView *c = [MyView new];
c.delegate = self; // works fine

Because the view property of UIViewController is declared as UIView *, and UIView doesn't have a delegate property. Note that property (and method) lookup by the compiler is not dynamic, as it's done at compile time.
Use
((MyUIView *)(self.view)).delegate = self;
instead.

Because just by checking that the view property is a MyUIView does not automatically cast it to one.
You want something like this:
if ([self.view isKindOfClass:[MyUIView class]]) {
MyUIView *myView = (MyUIView*)self.view;
myView.delegate = self;
}
Notice how after checking, I create a variable of type MyUIView* which the compiler will then be able to "see" the delegate property on it. It's all about types :-).

[self.view isKindOfClass:[MyUIView class]] is a run-time check and can determine what kind of object has actually been assigned to view. On the other hand, the compiler is complaining because it doesn't have that information and only knows that view is some kind of UIView.
Use a cast on self.view to tell the compiler that you know what's going to be stored there.

Related

Objective-C: why a custom object will be a zombie

I'm developing an app in Objective-C using ARC.
My simplified code looks like this:
ClassA (.m)
MyCustomClass *obj = [[MyCustomClass alloc] initWithValue1:#"abc" value2:1000];
MyViewController *vc = [[MyViewController alloc] initWithObject:obj];
// "vc" will become the first item of a UITabBarController
MyViewController (.h)
- (id)initWithObject:(MyCustomClass *)obj {
...
localReferenceToOjbect = obj;
...
}
- (void)viewWillAppear:(BOOL)animated {
// do something with "localRefernceToObject" <---
}
launching the app will result in a call to a zombie: when the ViewController is shown, the "obj" will be already deallocated and so i can't use it anymore.
my workaround is:
ClassA (.h)
#interface ClassA : UIViewController {
MyCustomClass *obj;
}
ClassA (.m)
obj = [[MyCustomClass alloc] initWithValue1:#"abc" value2:1000];
MyViewController *vc = [[MyViewController alloc] initWithObject:obj];
// "vc" will become the first item of a UITabBarController
is this the right way?! i don't think so: why i've to store an istance of an object that is useless for ClassA?
i can't get an explanation on what's actually happening. could you help me?
You're right in the fact that it is not logical to keep around a reference to obj in ClassA.
But if you need to keep around the reference to obj for MyViewController to use it, retain it in MyViewController, not in ClassA, because that's MyViewController that will use it.
The easiest way to do this is to transform your localReferenceToObject you use in MyViewController into a #property(retain) propertyToObject; (or #property(strong) propertyToObject if you use ARC) and access it in your MyViewController.m with self.propertyToObject (instead of localReferenceToObject, to be sure to call the property's setter and thus really retain the object).
This way, the object will be retained and kept around while your MyViewController instance is still alive.
[EDIT] If you want this property to be private, you can declare it in the class extension so that it is not accessible from other classes, as in the below example. See here in Apple's documentation for more details.
In your MyViewController.h header file
#interface MyViewController : UIViewController
// Here you write the public API in the .h / public header
// If you don't want your property to be visible, don't declare it there
#end
In your MyViewController.m file
#interface MyViewController ()
// This is the private API, only visible inside the MyViewController.m file and not from other classes
// Note the "()" to declare the class extension, as explained in Apple doc
#property(nonatomic, retain) MyCustomClass* referenceToObject; // Note: use strong (which is a synonym of retain) if you use ARC
#end
#implementation MyViewController
#synthesize referenceToObject = _referenceToObject; // not even needed with modern ObjC and latest LLVM compiler
- (id)initWithObject:(MyCustomClass *)obj
{
self = [super init];
if (self) {
...
self.referenceToOjbect = obj;
...
}
return self;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// do something with "self.refernceToObject"
}
// This memory management code is only needed if you don't use ARC
-(void)dealloc
{
self.referenceToObject = nil; // release memory
[super dealloc];
}
Personally, as suggested by Apple in some WWDC sessions, I now really rarely use instance variables and prefer the use of properties instead, either public in the .h or private in the .m.
If you use ARC, you can still use an instance variable instead of a property as ARC will retain it for you, but as long as you make sure your instance variable is declared as strong and not weak.

Property not set in drawRect method - iOS

I have been seeing some strange behavior when I try to access a class variable or a property in my drawRect method..
In my .h file I have the following
#interface DartBoard : UIView
{
Board * board;
int index;
}
#property (readwrite, assign, nonatomic) NSNumber * selectedIndex;
#end
In my .m file I have the following
#implementation DartBoard
#synthesize selectedIndex;
-(id)init
{
self.selectedIndex = [NSNumber numberWithInt:5];
index = 123;
return self;
}
- (void)drawRect:(CGRect)rect {
NSLog(#"selectedIndex: %d",[self.selectedIndex intValue]);
NSLog(#"index: %d",index);
}
#end
the output is
2012-06-12 19:48:42.579 App [3690:707] selectedIndex: 0
2012-06-12 19:48:42.580 App [3690:707] index: 0
I have been trying to find a solution but have had no luck..
I found a similar question but there was no real answer to the issue
See: UIView drawRect; class variables out of scope
I have a feeling drawRect is different that normal methods and is not getting the scope of the class correctly but how do I fix it?
Cheers
Damien
I have a feeling drawRect is different that normal methods and is not getting the scope of the class correctly
No, there is nothing special about -drawRect:.
There are two possibilities:
1. Your -init method is not being called.
You didn't say how this view gets created -- if you are manually calling [[DartBoard alloc] init], or if it is getting unarchived from a nib file.
If it's coming from a nib, UIView's unarchiving doesn't know that your init method should be called. It will call the designated initializer instead, which is -initWithFrame:.
So, you should implement that method instead, and make sure to call super!
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.selectedIndex = [NSNumber numberWithInt:5];
index = 123;
}
return self;
}
2. There might be two instances of your view: one that you are manually initing, and another one that comes from somewhere else, probably a nib. The second instance is the one that is being drawn. Since its variables and properties are never set, they show up as zero (the default value).
You could add this line to both your -init and -drawRect: methods, to see what the value of self is. (Or, check it using the debugger.)
NSLog(#"self is %p", self);

What UIView setter does #synthesize create

How to make custom UIView setter. For example:
1) We create property:
#property (retain) IBOutlet UILabel *myLabel
2) we make setter (the same as #synthesize would create):
- (void)setMyLabel:(UILabel *)anObject
{
[myLabel release];
myLabel = [anObject retain];
}
Is it correct, or should I check if the new view are not the same as the current with
- (void)setMyLabel:(UILabel *)anObject
{
if(anObject != myView){
[myLabel release];
myLabel = [anObject retain];
}
}
Just myView and anObject are object pointers. So should we check them with -isEqual method then? Or we don't need to check it at all? What code does #synthesize generates by defaults?
Thanks.
Only the second version (with the if statement) is correct. In your first version, imagine that anObject and myLabel actually point to the same object (i.e., the pointers are the same). In that case, you would release the object, which would cause it to be deallocated if no other object had retained it. The subsequent attempt to retain the deallocated object would cause a crash.

Why am I getting an error for this?

Why am I getting these errors?
alt text http://img39.imageshack.us/img39/2203/help.tif
It says:
Error: Request for member "jokeTableView" in something not a struction or union
What does that mean? And why is it breaking. I tried reading about initWithStyle but I just could catch up on it
Here is my .h file:
#import <UIKit/UIKit.h>
#interface TableViewController : UITableViewController {
NSMutableArray *jokes;
IBOutlet UITableView *jokeTableView;
}
#property (nonatomic, retain) NSMutableArray *jokes;
#end
Thanks!
Your object (TableViewController) has no property named jokeTableView.
In order to access jokeTableView with the special dot operator, it needs to be a property. Otherwise you have to access it using Key-Value-Coding compliant methods or directly using the -> operator (or just use it as an ivar and no reference to self):
jokeTableView.delegate = self;
or
self->jokeTableView.delegate = self;
or
[self jokeTableView].delegate = self;
or
#property (retain) UITableView *jokeTableView;
// later...
self.jokeTableView.delegate = self;
Also note, however, that you are setting an outlet in the initializer and this won't work. You'll have to set this in the -[TableViewController awakeFromNib] method since self->jokeTableView will be nil when the initializer is actually called (which happens in IB prior to serializing the object into the nib file).
Since you are doing this at init time, the outlets should be NULL, so this initialization shouldn't do anything. This should be done at awakeFromNib time at the earliest.

How to add an object to a programmatically bound NSMutableArray?

I have an NSDocument which has the following structure:
#interface MyDocument : NSDocument
{
NSMutableArray *myArray;
IBOutlet NSArrayController *myArrayController;
IBOutlet MyView *myView;
}
#end
I instantiate the NSArrayController and the MyView in MyDocument.xib, and have made the connections to the File's Owner (MyDocument), so I am pretty sure that from the point of view of Interface Builder, I have done everything correctly.
The interface for MyView is simple:
#interface MyView : NSView {
NSMutableArray *myViewArray;
}
#end
Now, in MyDocument windowControllerDidLoadNib, I have the following code:
- (void)windowControllerDidLoadNib:(NSWindowController *) aController
{
[super windowControllerDidLoadNib:aController];
[myArrayController setContent:myArray];
// (This is another way to do it) [myArrayController bind:#"contentArray" toObject:self withKeyPath:#"myArray" options:nil];
[myView bind:#"myViewArray" toObject:myArrayController withKeyPath:#"arrangedObjects" options:nil];
}
In the debugger, I have verified that myViewArray is an NSControllerArrayProxy, so it would appear that my programmatic binding is correct. However, when I try to add objects in MyView's methods to the MyView myViewArray, they do not appear to update the MyDocument's myArray. I have tried both of the following approaches:
[myViewArray addObject:value];
[self addMyViewArraysObject:value];
(The second approach causes a compiler error, as expected, but I thought that the Objective-C runtime would "implement" this method per my limited understanding of KVO.)
Is there something wrong with how I'm trying to update myViewArray? Is there something wrong with my programmatic binding? (I am trying to do this programmatically, because MyView is a custom view and I don't want to create an IB palette for it.)
The problem is that you're mutating your array directly. Implement indexed accessor methods and call those.
KVO overrides your accessor methods (as long as you conform to certain formats) and posts the necessary notifications. You don't get this when you talk directly to your array; anything bound to the property won't know that you've changed the property unless you explicitly tell it. When you use your accessor methods, KVO tells the other objects for you.
The only time to not use your accessor methods (synthesized or otherwise) is in init and dealloc, since you would be talking to a half-inited or -deallocked object.
Once you're using your own accessor methods to mutate the array, and thereby getting the free KVO notifications, things should just work:
The view, when mutating its property, will automatically notify the array controller, which mutates its content property, which notifies your controller.
Your controller, when mutating its property, will automatically notify the array controller, which mutates its arrangedObjects property, which notifies the view.
I can see two possibilities here:
First, do you instantiate the NSMutableArray object (and release it) in your MyDocument class? It should look something like this:
- (id)init
{
if ((self = [super init]) == nil) { return nil; }
myArray = [[NSMutableArray alloc] initWithCapacity:0];
return self;
}
- (void)dealloc
{
[myArray release];
[super dealloc];
}
Second, did you declare myViewArray as a property in MyView? It should look something like this:
// MyView.h:
#interface MyView : NSView
{
NSMutableArray * myViewArray;
}
#property (assign) NSMutableArray * myViewArray;
#end
// MyView.m:
#implementation MyView
#synthesize myViewArray;
#end
Other than that, it looks to me like you have done all of the binding properly.
update: How about using the NSArrayController to add items to the array:
// MyView.h:
#interface MyView : NSView
{
NSMutableArray * myViewArray;
IBOutlet NSArrayController * arrayController;
}
#property (assign) NSMutableArray * myViewArray;
- (void)someMethod;
#end
// MyView.m:
#implementation MyView
#synthesize myViewArray;
- (void)someMethod
{
id someObject = [[SomeClass alloc] init];
[arrayController addObject:[someObject autorelease]];
}
#end
The problem appears to be that I had been binding MyView's myViewArray to the NSArrayController's arrangedObjects property instead of its content property.
When binding to arrangedObjects, I found that the actual object pointed to by myViewArray was an instance of NSControllerArrayProxy. I didn't find a definitive answer as to what this object actually does when I searched online for more information on it. However, the code examples I found suggest that NSControllerArrayProxy is intended to expose conveniences for accessing the properties of objects in the array, rather than the objects (in the array) themselves. This is why I believe that I was mistaken in binding to arrangedObjects.
The solution was to instead bind MyView's myViewArray to the NSArrayController's content property:
- (void)windowControllerDidLoadNib:(NSWindowController *) aController
{
[super windowControllerDidLoadNib:aController];
[myArrayController setContent:myArray];
[myView bind:#"myViewArray" toObject:myArrayController withKeyPath:#"content" options:nil];
}
Although this appears to work, I am not 100% sure that it is correct to bind to content in this case. If anyone can shed some light on programmatically binding to the various properties of an NSArrayController, I would welcome comments to this answer. Thanks.
First of all, there's nothing wrong with binding to arrangedObjects: an NSTableColumn, for instance, should have its content bound to arrangedObjects only, and its contentValues to arrangedObjects.someProperty.
The common mistake is to regard arrangedObjects as the content of an arrayController but that, as you have seen, will lead to grief: arrangedObjects is a representation of the way the arrayController has currently arranged the objects in its content, not the content itself.
That said, the way to bind an array to an arrayController is:
[self.myArrayController bind:NSContentArrayBinding
toObject:self
withKeyPath:#"myView.myViewArray"
options:nil];
Are you sure, by the way, your view needs to hold the myViewArray? That usually falls under the responsibility of a controller or model object.
Now you can add objects by calling addObject on the arrayController, since that is the controller's responsibility.
[self.myArrayController addObject: anObject]