How does UIImage get constructed when created from an XIB? - iphone

I'm trying to do some fanciness with XIBs and that includes wanting to somehow get and store the paths of images loaded from the xib. To do this, I made some categories and did some method swizzling to override all the UIImage constructors to save their path before calling their parent constructor.
But due to Apple's black box BS with all their XIB stuff, absolutely none of the exposed constructors for UIImage seem to get called when I create a UIImageView through [UIViewController initWithNib...].
Does anybody know what function call happens or how they do this? I can't find any information whatsoever that exposes what initWithNib actually does behind the scenes.
Thanks!
EDIT:
If you're in a similar situation, you may try using the accessibilityLabel / accessibilityHint which is automatically populated with the image path. The only issue is that accessibility needs to be enabled or these values are nil.

I know the objects constructed from a Nib are being unarchived according to the NSCoding protocol; you need to override initWithCoder: in this case.
You could use swizzling to replace UIImageView's initWithCoder: method, then snoop around to see if an image name or path is available in any of the coder's keys. This might be more effective than hacking UIImage itself, since for all we know UIImageView could be using a custom subclass that you don't have access to.

the initWith... methods are meant for programatically creating UIImageView objects.
Sounds like you want to catch things as they are instantiated from XIB files. That would be the parent class UIView's [initWithCoder:] method.
As the UIView documentation says:
initWithCoder: - Implement this method if you load your view from an
Interface Builder nib file and your view requires custom
initialization.

You are both close to right - I tried doing this with UIImageView which works for initWithCoder as you guys are both suggesting. However, UIImage doesn't get initWithCoder called for some reason, instead it uses initWithCGImageStored:(CGImageRef)cgImage scale:(CGFloat)scale orientation:(UIImageOrientation)orientation.
If and when I actually get the path out of this as I desire I'll post it up here. Thanks for the help, gents.

Related

UIViewController encodeWithCoder fails when view contains a UIButton with custom image

Basically, my problem is exactly what it says in the title. When I try to encode a subclass of UIViewController, calling [super encodeWithCoder] gives an NSInvalidArgumentException. Specifically, I get -[UIImage encodeWithCoder:]: unrecognized selector sent to instance XxXXXXXX.
The only image image in the view is on a UIButton, which is also supposed to conform to NSCoding, and the stack trace includes a call to [UIBUtton encodeWithCoder]. The button is created programmatically with [UIButton buttonWithType:UIButtonTypeCustom], and the image is set with setImage: forState:. I really have no idea what is going on here. Am I missing something obvious, or does UIButton just not really conform to NSCoding?
I can't imagine why you would want to be archiving view objects within your app, but you probably need to add NSCoding support yourself by writing a category on UIImage.
For details see: iPhone - Why does the documentation say UIImageView is NSCoding compliant?
View objects support NSCoding because the view loading system uses it to load objects from Nib files. But the UINib class does some additional work that NSKeyedArchiver does not.
If you just want to store state between launches, it's better to store data and not view state. Especially if you ever plan to update your app and make any changes whatsoever to the view layout. (This is the motivation behind Model/View/Controller separation.)

How to pass references to init method of object in a nib?

I have a UIView subclass which currently assembles itself completely programatically. It has a custom initWithFrame:bundle: initializer which is necessary because it uses the bundle passed in to load image resources.
I want to make this view a subview in a larger nib file, but then initWithCoder will be called when the nib loading code gets to it instead of my custom initializer. Is there any way I can place this view in a nib and still have my custom initialization occur?
Yes, you can override initWithCoder: too if your bundle is known ahead of time (read: You don't need it passed in as a parameter). Otherwise, nope.
If you can wait until awakeFromNib, you can do your initialization in there. You still have the problem of not being able to pass the bundle into the method, though. Since awakeFromNib is called after initialization and setup of all outlets and actions, it might be too late for you. Maybe it's better to redesign around the nib-loading system anyway?

iphone dev: UIImageview subclass interface builder - how to call custom initializer

I messing with iphone developement. I have a uiimageview subclass that I want to use to detect touches. I have sucessfully added to interfacebuilder and I can detect a touch in my UIImageview subclass within my application so all is good on that front. however my UIImageView subclass has a custom initializer which is not called when it is created in interface builder.
if I manually initialize the UIImageview and add it programmatically I think it will work but then I lose the ability to 'see' my positioning in Interface builder.
how can I either
1) 'see' a uiimageview in interface builder that is added in code? (not possible?)
2) call my custom initializer when the subclass is instantiated in interfacebuilder.
thanks
Hi thanks for suggestions. I think I'm getting closer to understanding the relationship between the xib and the viewcontroller.
I now am sucessfully adding my UIImageView subclass programmatically and using my custom initiializer which overrides InitWithFrame.
I think I read that the xib calls 'awakeFromNib' so I could equally add my iniitialization code in there. I like the idea of adding it programmatically as I have more control (although harderto set up my IU)
so one more realted question. if I add an UIImageView subclass in interface builder. I can see it and detect touches on it. if I want to refer to it in the view controller class do I have a pointer to it? i.e. is there a variable name for it? the UIImageViews I create myself I obviuosly know what they are called.....
You likely have put your instructions in the wrong initializer.
According to the documentation, objects unarchived from a NIB are initialized with initWithCode: if they conform to the NSCoding protocol; objects that don't conform to NSCoding are initialized with init.
In this particular case, UIImageView does conform to NSCoding. It's likely that you have you intended for initWithFrame: to be called and put your instructions in that method.
Can you not simply put your initialisation logic in viewDidLoad? In particular,
-(void)viewDidLoad {
[super viewDidLoad];
// Put whatever was in your custom initialiser here.
}

How do I call the original function from the overloaded function in a category?

In Objective-C, I have a category for a class:
#interface UILabel(CustomInit)
- (id)initWithCoder:(NSCoder *)coder;
#end
What I'm doing is writing a custom init function that does some extra stuff, and what I'd like to do, is in this custom init function, call the UILabel's base initWithCoder. Is this possible? How so?
EDIT
Thanks. Ok, so my plans moot. Can't just overload initWithCoder. Is there a way to achieve the same functionality (where all UILabels get this added initialization step) without overloading initWithCoder? Or perhaps is there sample code for the UILabel's initWithCoder that I can just rewrite with the added code?
EDIT
Ok, so to be clear about what I'm trying:
Can I embed a custom font in an iPhone application?
has an answer in which someone manually adds a custom font on the iphone using the private GraphicServices function GSFontAddFromFile. I tried this code and it worked great for manually setting the font of a label. However, if you try setting the font in Interface Builder, it doesn't load properly, it just drops down to the system font. What I wanted to do was load the font manually and set the label's font automatically with the chosen font in IB. This way I don't need to make an outlet for every label I put down. I also don't have to write a ridiculous label subclass (which was also suggested in that thread and does a large amount of custom drawing) which I found rather grotesque. Now I could still make a subclass for all my labels, but then there's the case of embedded labels in other UI objects, ie UIButtons. I'd like the embedded labels to also not be broken.
Any suggestions would be great. Thanks.
From the Mac OS X Reference Library:
When a category overrides an inherited
method, the method in the category
can, as usual, invoke the inherited
implementation via a message to super.
However, if a category overrides a
method that already existed in the
category's class, there is no way to
invoke the original implementation.
How do you guys feel about this?
Grab the original method address for initWithCoder at runtime and store it in a static variable. Do a method swizzle on it to replace the classes implementation with the my initWithCoder. And then in my initWithCoder, I would call the original method stored in the static variable.
You can put it in a category and call this class initialization step at the start of the program, making sure it can't be called twice, or if it is it does nothing.
It seems dangerous, but I feel like it should work.
Method swizzling should work as kidnamedlox suggested .
Your exact same question was discussed in this Stanford itunes class by Evan Doll
https://podcasts.apple.com/us/podcast/iphone-application-programming-spring-2009/id384233222

How to detect when a UIView has changed size?

I have a UIViewController that is initialised with a correct frame, however somewhere in my code the frame gets mangled and I'm having difficulty finding out where.
In situations like this it is usually handy to watch a variable in the debugger, however I have no way of accessing the controller->view->frame property in my variable view, since it isn't a variable, it's a property (surprisingly enough)
Drilling into the UIView in the variables display shows a few things but nothing I can relate to the frame, I thought perhaps that would be in layer but it isn't.
Is there any way to watch for changes in a private API? I guess not, since the variables are essentially 'hidden' and so you can't specify exactly what to watch.
Alternatively, what other approach could I use? I already tried subclassing UIView, setting my UIViewController's view to point to this subclass and breaking on the setFrame method but it didn't seem to work.
EDIT: the subclassing UIView method DID work, I just had to set the view to point to my test subclass in viewDidLoad and not the init method. Leaving this question open as I'm not sure if this is the best way of approaching this kind of problem...
Subclass your the view you want to track and rewrite the setFrame method:
#implementation MyTableView
- (void)setFrame:(CGRect)frame;
{
NSLog(#"%#", frame);
[super setFrame:frame];
}
#end
Then use the debugger to add a breakpoint to it and check when it gets called. Eventually, you'll see when the frame gets changed and where does the change comes from.
I discovered this can be done using key value observers.
http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/KeyValueObserving/KeyValueObserving.html
You could create an ivar, view2, and just assigned it to your view in your loadView method. That should enable you to watch it like a normal variable.