Anybody successful with NSProxy of UIView (e.g., UILabel?) - iphone

I am experimenting in adding functionality to my UIViews (configuring CALayers according to state) by setting up a NSProxy subclass to stand in for any UIView I choose. Here's what I've tried:
In my NSProxy subclass, I have the following code:
#pragma mark Initialization / Dealloc
- (id)initWithView:(UIView *)view
{
delegate = view;
[delegate retain];
return self;
}
- (void)dealloc
{
[delegate release];
[super dealloc];
}
#pragma mark Proxy Methods
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[anInvocation setTarget:delegate];
[anInvocation invoke];
return;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
return [delegate methodSignatureForSelector:aSelector];
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
BOOL rv = NO;
if ([delegate respondsToSelector:aSelector]) { rv = YES; }
return rv;
}
And, using my NSProxy subclass this way:
UILabel *label = [[HFMultiStateProxy alloc] initWithView:[[[UILabel alloc] initWithFrame:cellFrame] autorelease]];
label.text = text;
label.font = font;
label.textAlignment = UITextAlignmentCenter;
label.backgroundColor = [UIColor clearColor];
label.opaque = NO;
[self addSubview:label];
Seems to work until I hit the addSubview: line.
Turning message tracing on ( instrumentObjcMessageSends(YES); ) shows the forwarding for each of the previous messages working until deep inside of the addSubview:, where this series of method calls show up in the log (the first message shown here was invoked via the proxy):
- UILabel UIView _makeSubtreePerformSelector:withObject:
- UILabel UIView _makeSubtreePerformSelector:withObject:withObject:copySublayers:
- CALayer CALayer sublayers
- NSMethodSignature NSMethodSignature methodReturnType
- NSMethodSignature NSMethodSignature _argInfo:
- NSMethodSignature NSMethodSignature _frameDescriptor
+ UILabel NSObject resolveInstanceMethod:
- UILabel NSObject forwardingTargetForSelector:
- UILabel NSObject forwardingTargetForSelector:
- UILabel NSObject methodSignatureForSelector:
- UILabel NSObject methodSignatureForSelector:
- UILabel NSObject class
- UILabel NSObject doesNotRecognizeSelector:
And I get the following error:
2011-02-20 16:38:52.048 FlashClass_dbg[22035:207] -[UILabel superlayer]: unrecognized selector sent to instance 0x757d470
if I do not use an NSProxy subclass and instead use a UILabel subclass (HFMultiStateLabel), it works fine. Here is the message trace that occurs once addSubview: is called (HFNoteNameControl is the superview of the label):
- HFNoteNameControl UIView addSubview:
- HFNoteNameControl UIView _addSubview:positioned:relativeTo:
- HFMultiStateLabel UIView superview
- HFMultiStateLabel UIView window
- HFNoteNameControl NSObject isKindOfClass:
- HFNoteNameControl NSObject class
- HFNoteNameControl UIView window
- UIWindow NSObject isKindOfClass:
- UIWindow NSObject class
- HFNoteNameControl UIView _shouldTryPromoteDescendantToFirstResponder
- HFMultiStateLabel UIView _isAncestorOfFirstResponder
- HFMultiStateLabel UIView _willMoveToWindow:withAncestorView:
- HFMultiStateLabel UIView _willMoveToWindow:
- HFMultiStateLabel UIView willMoveToWindow:
- HFMultiStateLabel UIView _makeSubtreePerformSelector:withObject:withObject:copySublayers:
- CALayer CALayer sublayers
- HFMultiStateLabel UIView willMoveToSuperview:
- HFMultiStateLabel UIView _unsubscribeToScrollNotificationsIfNecessary:
- HFMultiStateLabel UIView _makeSubtreePerformSelector:withObject:
- HFMultiStateLabel UIView _makeSubtreePerformSelector:withObject:withObject:copySublayers:
- CALayer CALayer sublayers
- CALayer CALayer superlayer
I can verify that each of the methods up until -superlayer are called successfully when using NSProxy. For some reason, with the NSProxy, superlayer on UILabel is being called instead of CALayer. Perhaps somewhere something gets confused and UILabel is inserted into the sublayers instead of its CALayer?
Does the UIKit do some sort of optimizations that bypass the normal mechanism that NSProxy hooks into?
PS I have only tried this in the Simulator, not the device. Would that behavior be any different?

I was trying to solve the same issue - use NSProxy with UIView (in my case UITableViewCell) when I encountered this problem. I logged all calls to the console:
...
App[2857:c07] MyHeaderCell: --- method signature for: _unsubscribeToScrollNotificationsIfNecessary:
App[2857:c07] MyHeaderCell: --- _unsubscribeToScrollNotificationsIfNecessary:
App[2857:c07] MyHeaderCell: --- method signature for: _makeSubtreePerformSelector:withObject:
App[2857:c07] MyHeaderCell: --- _makeSubtreePerformSelector:withObject:
App[2857:c07] +[MyHeaderCell superlayer]: unrecognized selector sent to class 0x1331f8c
App[2857:c07] CRASH: +[SMSHeaderCell superlayer]: unrecognized selector sent to class 0x1331f8c
App[2857:c07] Stack Trace:...
It crashes on the unrecognized selector exception.
Normally, the object is asked the - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel method first and when that is returned, it invokes the - (void) forwardInvocation:(NSInvocation *)invocation in the proxy. This way we can redirect the messages. If there is not NSMethodSignature returned, the doesNotRecognizeSelector: method is called on the object. So we get even unrecognized selector calls.
This works for instance methods, but this crash is caused by a class method, which we have no power over - the object itself is not called (the class is). I wanted to force the runtime to call my proxy class even for class methods by overriding the getter of my NSProxy subclass
- (Class) class
{
return _myRealClass;
}
Which did not work. So NSProxy is not enough to do this. Right now I'm trying to use the NSObject instead of NSProxy to achieve all the desired behavior and since NSObject has the + (BOOL)resolveClassMethod:(SEL)sel method which might be useful. I will edit this post once I found out if NSObject is better suited for this.
//Edit
It seems that the problem is that with NSProxy, superlayer is being called on UIView instead of CALayer.
So it really seems like a UIKit shortcut problem - they're not sending a regular message call (speed optimization I would guess).
Anyways, this I am searching for a way to get around this now.

I gave up trying. I've come to the conclusion that NSProxy is such an underused object that it's potential for uses beyond Apple examples has not been fully explored nor debugged. In short, I believe that NSProxy is not ready to be used as a generic way to extend an object's functionality without subclassing or adding a category.
In the old days, I would have used a poseAsClass call to implement my desired functionality.
My solution ended up something like this:
I added a category to UIView that added additional properties. These property implementations forwarded their set & get messages to a "addOn" property of the UIView that I also put into the category. The default value of this "addOn" property in the UIView's category implementation is, of course, nil. (I could have implemented a static hash table to enable associating an AddOn instance for any UIView, but it struck me as a risky ploy to manage with the retain counts properly.)
The "AddOn" class had extra code in it to directly manipulate the UIView, and it added extra drawing code in it.
For each type of UIView that I wanted to add this added functionality, I had to subclass it with code that:
a) Created an instance method and corresponding property code for the "AddOn" class
b) Subclassed any functions I covered to give the "AddOn" code a chance to add its functionality.
Each of these subclasses has essentially the same code in it to forward the desired functionality to the AddOn instance.
SO, I ended up minimizing code duplication as much as I could, but each of the UIView's descendant subclasses that enable use of the the "AddOn" functionality ends up duplicating code.
It appears that I could have further minimized code duplication by using class method manipulation functions, but that learning curve and further obfuscation of the code deterred me from following that path.

I have never tried using NSProxy with views, but I have done something similar by using a custom view class to display another view. Maybe the system requires an actual view and not a proxy object. There are two ways you could use a "proxy" view:
Make the proxied view a subview of the proxy view. The proxy would take the frame, autoresizing mask, etc. from the proxied view, then add the proxied view as its subview and set its frame to be the proxy view's bounds, and its autoresizing mask so that it always fills the proxy view. When the proxied view is removed, any settings are copied back into it from the proxy view. Any properties not copied into the proxy are passed to the proxied view using forwarding.
The proxy view passes almost every message to the proxied view. The proxy view does not override the lock/unlockFocus, display, etc. methods. It overrides drawRect: to call drawRect: on the proxied view.

After trying the same thing, and searched for the error (which got me here), I tried to circumvent the problems... It wasn't pretty.
Identifying the root problem was easy. Somewhere in the framework, Apple is using direct pointer access to the variables in UIView subclasses. If you check the headers, the variables are declared with #package access identifier.
What I basically tried was:
Create a proxy class at runtime with ivars copied from the UIView class definition, and then set the values of these pointers to the objects in the UIView. Couldn't get far there.
Declare just the CALayer * in the proxy subclass, and only copy that pointer from the protected UIView instance. Worked, but I think it was buggy? It didn't work with auto layout at all, though, so I decided to move away from that solution.
The code I tried can be found in the RTLSegmentedControl repo under the proxy-pattern branch
I also wrote a blog post about the details.

Related

2 delegates of one class

I use delegates to run some methods.
I got class webrequests with 2 delegates: menuVC and mapVC.
When I in mapVC webrequests is accomplish delegates methods of mapVC class.
When I in menuVC webrequests is tried to accomplish delegates methods of mapVC class and crashed app.
Method in webrequests do not helps
if ([self.delegate isKindOfClass: [MenuViewController class]])
{
self.delegate = [MapViewController class];
}
Why it happens?
I use in both classes
self.webRequests = [WebRequests sharedInstance];
self.webRequests.delegate = self;
Original answer:
You can only have one delegate of an object at one time, unless the object is designed to allow for different delegate protocols (e.g. UITableView has both a "delegate" AND a "datasource").
You also can not set your delegate to a non-instantiated object like what you are doing on this line:
self.delegate = [MapViewController class];
This needs to be an actual allocated and instantiated object.
Like "self.mapViewController" that you just pushed or created.
For this question, you probably need to better explain what you are ultimately trying to do, since it looks like your current delegate is a MenuViewController object and you're trying to switch the delegate to a MapViewController object.
New answer:
Since you want things to happen in two different view controllers, the best way to do what you want to do is use a "NSNotification" (which allow multiple objects to observe -- or watch -- for things happening).
Here is a tutorial you can look at, to get a good start with it.

Xcode Dynamically loaded GUI for all views

I am reasonably new to Xcode and Objective C.
I have successfully loaded all of the GUI objects on one of my views dynamically..
Now i want to repeat the same dynamically loaded content onto all or most of my views..
I have a method in the main view like this:
-(void)loadinfo:(id)sender{
//All dynamically loaded content etc..
}
I currently have the main view calling this method like so.
[self loadinfo];
So now i need to know (without copying and pasting the method into all of my views) how to call the method from the main view into other views?
I hope this all makes sense.
Edit
I am more knowledgeable in PHP so if i was to do the same thing in php i would make a file called functions.php and include that file into all of the pages.. Is it the same concept?
The concept is different from the approach you find with functions in PHP. In Objective C one works with objects. To share behavior (your PHP functions, called methods in Objective C) between objects you need to slot that behavior somewhere into your class hierarchy.
So for your specific case you would implement the loadinfo method in a generic class which is a subclass of NSView, say MyGenericView. Both your view1 and view2 classes would then subclass from that generic class and inherit the loadinfo method.
In case you want to divert from the implementation of loadinfo in your base class, you can override it partially by doing (in view1 or view2):
- (void) loadview {
[super loadview]; // perform the default implementation
[self doSomethingDifferint]; // perform subclass specific stuff
}
... or:
- (void) loadview {
[self doSomethingDifferint]; // perform subclass specific stuff
[super loadview]; // perform the default implementation
}
... or override completely by doing:
- (void) loadview {
[self doSomethingCompletelyDifferent]; // perform subclass specific stuff
}
Just on a side note: it's good practice to follow CamelCase standards when naming your classes and methods in Objective C, so your classes would be View1 and View2 and the method would be loadInfo.
Further, you might want to read up on generic OO principles and Objective C's specific aspects of it to take full advantage of the language and its features.
You should subclass the view that has all your dynamically-loaded UI content. So if you have:
#interface MyCustomView : UIViewController
{
}
#property (strong, nonatomic) UIView *aView;
-(void)someMethod;
#end
You can create a subclass that will inherit all of the properties, methods, etc:
#interface FirstView : MyCustomView
{
}
//all properties and methods of MyCustomView are inherited
#end
Do this for any number of views that you want to create.

Where to initialize custom UIView, instantiated in Interface Builder?

I have a subclass of UIView that's instantiated in a XIB file. I need it to do some initialization (settings some variables and creating a subview).
However, I do not always instantiate this view via Interface Builder. I do it programmatically too. In both cases, the initialization needs to be the same.
My designated initializer is initWithValues:.
The question is; where do I perform the initialization?
Since I have to perform it in 2 different locations, I figured I need to refactor it in a separate initialize method (or something like that), and call it from initWithValues:.
But when loading from IB, both initWithCoder: and awakeFromNib are called. From which method do I have to call initialize? Or do I have to call initWithValues: from initWithCoder: and do nothing in awakeFromNib?
You should use initWithFrame: when initializing views (since it's the designated initializer). Hence, if you have initWithValues: make sure you call initWithFrame: from it.
Something like this should work for initializing: ;)
- (void)initialize{
//init your ivars here
}
- (id)initWithCoder:(NSCoder *)aCoder{
if(self = [super initWithCoder:aCoder]){
[self initialize];
}
return self;
}
- (id)initWithFrame:(CGRect)rect{
if(self = [super initWithFrame:rect]){
[self initialize];
}
return self;
}
I was going to add a further explanation, but mplappert's answer is clear enough. Use awakeFromNib if necessary.
That depends on what you need to initialize. As soon as awakeFromNib gets called, all outlets and action connections of your view are established which is not the case in initWithCoder:. So if you need to rely on those connections, use awakeFromNib. Otherwise you can safely do all your initializing in initWithCoder:.
Unfortunately, the above answers don't take into account these things:
- (void) awakeAfterUsingCoder - and the fact it's called after anything is created by the coder (once for every Xib view).
awakeFromNib suffers from the same fate, I've noticed. (The reason I found this)
Another initializing issue is that initWithCoder and initWithFrame can be avoided for custom views. And if they are called, lazy loading (though not as important on views themselves), means you "might" be able to modify values. I believe I've done so in initWithCoder, but if you then initialize values in awakeFromNib, it's undone at least once.
I've gone so far as to:
- (void) awakeFromNib (or didMoveToSuperView);
{
BOOL called = NO;
if(!called)
{
called = YES;
}
}
Another method I use is to simply call the initializer needed, then call my own class or superclass-specific initializer.
I, too, am looking for a dependable one-time place I can rely on. Until then, I hope my headaches save the next person an hour or so.
Steve

unrecognized selector AGAIN

There are LOTS of iphone-related questions of this type. I know; I read them until I wasn't learning anything new, all in an effort to avoid (a) posting and (b) looking mighty green.
#interface CommonCostingClass : NSObject {
}
-(void) theCCC;
#end
That's the whole thing. As minimal as I could make it. I've even resorted to UIView instead of NSObject. Inside CommonCostingClass.m I have
#import "CommonCostingClass.h"
#implementation CommonCostingClass
-(void) theCCC {
// blah-blah
}
Again, that's all of it. Inside myViewController I coded
#import "CommonCostingClass.h"
- (BOOL) textFieldShouldReturn:(UITextField *)textField {
if (textField.tag == 4) {
[(CommonCostingClass *) self.view theCCC]; // <-ka-boom
}
// other stuff
}
The presence / absence of the cast makes no difference.
self.view generates
* -[UIView theCCC]: unrecognized selector sent to instance 0x5d3dd20
2010-07-20 11:30:54.732 Wind17[3233:207]
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[UIView theCCC]: unrecognized selector sent to instance 0x5d3dd20'
self generates the same message, with the substitution of "myViewController" for "UIView."
Clean all targets has no effect.
I know that neither UIView nor myView Controller "see" method "theCCC".
I don't know how to say, "It's there! It's there!"
Thank you for the help. Someday this situation will be funny and not embarrassing.
following the comments, on this line
[(CommonCostingClass *) self.view theCCC];
you are trying to perform -theCCC method on self.view. self.view is a UIView.
If you want your custom class to be a NSObject subclass as you have it now, you need to create and initialize a CommonCostingClass object in your view controller, then call -theCCC on it.
edit for unrecognized selector: Unrecognized selector means you are calling a method on a class that does not implement that method. In your case you are calling theCCC on UIView which does not implement or know of a theCCC method.
Seems like you really want
+(void) theCCC;
(note the "+") which is a class method, then you would just call
[CommonCostingClass theCCC]
There really is no mystery to "unrecognized selector" It means the first thing in the brackets (in your case self.view) does not understand (have the method) theCC. And why would it?
You have declared a type of class, but in order to make use of that class you have to have an instance somewhere. So how did you think an instance of CommonCostingClass was ever created?
At least two problems here
If you are going to use CommonCostingClass as a view, it needs to subclass UIView. In the code you're posting, CommonCostingClass is a subclass of NSObject. That could be a typo or it could indicate a much more fundamental issue with what you're trying to do here.
You need to make sure the that view property of your UIViewController is set to be an instance of CommonCostingClass. The easiest way to do this is using Interface Builder.

Creating an object to act as a delegate - Objective C

My question is simple actually, how do I create an object to act as a delegate, instead of including the delegate methods in my view?
For example, I have x functionality that requires delegate methods, and they're currently setup to use self as the delegate. I'd like to put those methods in their own object so that the delegate methods can be called and do stuff if the view has ended.
What's the best way?
for example, NSXMLParser delegate methods - they exist, the delegate is defined, but I dont want to call them as self in my view controller... what other option do I have?
You can specify another custom class to handle the delegate methods, if you wish. Simply create a class, call it MyXMLParserDelegate or something similar. Then, all you have to do is tell your NSXMLParser object that it should use an instance of your class as its delegate.
If you are using Interface Builder, add a new object to the XIB file, set its class to MyXMLParserDelegate, and then drag a connection from your NSXMLParser object's delegate selector to the new object.
If you are doing it programmatically, the basic operation looks like this:
MyXMLParserDelegate * myDelegate = [[MyXMLParserDelegate alloc] init];
[someXMLParser setDelegate:myDelegate];
Keep in mind, however, that delegates are not retained, so in order to do this without leaking memory, you should add an ivar of type MyXMLParserDelegate to your viewController class, and then do the following:
// in your #interface block:
{
...
MyXMLParserDelegate * myDelegate;
}
// in your init method:
myDelegate = [[MyXMLParserDelegate alloc] init];
// in your awakeFromNib method (or anywhere else it seems appropriate):
[someXMLParser setDelegate:myDelegate];
// in your dealloc method:
[myDelegate release];
Check out this answer, I think it covers what you need: How to use custom delegates in Objective-C