For a concept I'm developing, I need to load XIB files manually and by using class and instance method swizzling I have been able to intercept calls to imageCustomNamed, imageCustomWithContentsOfFile and imageCustomWithCGImage for the UIImage class and initCustomWithImage for UIImageView. What I want to to is detect the image name and replace it with some content rendered on the fly in place of the one set in IB at design time.
for example:
when XIB has an UIImageView object whose Image property is set to "About.png", I need to intercept the loading of that image and replace with another one depending on certain condition. It would be ok even to replace the image after the UIImageView object has loaded the image set at design time, but looks like the original name of the UIImage used to set the content of UIImageView is not stored anywhere.
I cannot use IBOutlets as I don't know the content of XIB file before hand; this is a class that should work on any XIB not just a particular one.
The custom methods are in fact being called in placed of the default ones, but looks like that when loading XIb the system uses imageCustomWithCGImage which accept a CGImageRef as argument; from it there is no way to know the origin (i.e: the image file name)
Any idea on how I can intercept the loading of images?
In OS 3, at least, you can override UIImageNibPlaceholder's initWithCoder:. Replace it with something like this:
-(id)hack_UIImageNibPlaceholder_initWithCoder:(NSCoder*)coder
{
NSString * name = [coder decodeObjectForKey:#"UIResourceName"];
[self release];
return [[UIImage imageNamed:name] retain];
}
I'm not sure what happens if you load nibs from other bundles (e.g. frameworks).
Perhaps you can set another property of UIImageView in xib file, and then reference that property? For example label, or tag...
It's not a good idea to swizzle a private class(UIImageNibPlaceholder)'s method.
But you can overload its superclass(UIImage)'s method. Seems like UIImageNibPlaceholder did't implement 'awakeAfterUsingCoder', so you can safely do this until Apple did it one day.
#import "UIImage+xib.h"
#implementation UIImage (xib)
- (id)awakeAfterUsingCoder:(NSCoder *)coder{
NSString* imageName = [coder decodeObjectForKey:#"UIResourceName"];
if(imageName){
return [UIImage imageNamed:imageName];
}
return self;
}
#end
Related
I have a table view cell with two image views, one image view is a placeholder, and on top of the other image is the actual image I load from the documents directory. I am having issues with the image being incorrectly displayed when the table view resuses the cell.
I solved my problem by using the below method in the cell class, but I have read that this can cause performance issues, any ideas on a better solution?
- (void)prepareForReuse
{
[[self imageView] setImage:nil];
}
First of all, you are not calling [super prepareForReuse] like you should.
And the documentation is pretty clear, you should be setting the image in tableView:cellForRowAtIndexPath: because it's content.
If you set the image in tableView:cellForRowAtIndexPath: there is no point to set it to nil in prepareForReuse.
Imagine the following flow.
You scroll down, cell 0 is put onto the queue.
prepareForReuse sets imageView.image to nil
tableView:cellForRowAtIndexPath: dequeues the cell and sets imageView.image to image1
You are setting imageView.image twice.
If you use nil there might be no measurable performance impact at all. But you might come to the idea to actually set a placeHolder image. So the cell is queued and prepared for reuse, you set the placeholder image; later the cell is dequeued and the placeholder image is replaced by a real image before the placeholder image was even visible.
I would remove the prepareForReuse method completely. You don't need it if you set the image in tableView:cellForRowAtIndexPath:
from the documentation:
If a UITableViewCell object is reusable—that is, it has a reuse identifier—this method is invoked just before the object is returned from the UITableView method dequeueReusableCellWithIdentifier:. For performance reasons, you should only reset attributes of the cell that are not related to content, for example, alpha, editing, and selection state. The table view's delegate in tableView:cellForRowAtIndexPath: should always reset all content when reusing a cell. If the cell object does not have an associated reuse identifier, this method is not called. If you override this method, you must be sure to invoke the superclass implementation.
Since you have a placeholder image behind this image view, There is nothing wrong if you set the top imageview's image to nil.
To enhance the image generating process, you can use a NSCache object like this,
1)
#property(nonatomic, strong) NSCache *imageCache;
2) Call this methods inside tableView cellForRowAtIndexPath: method, the image generating process can be moved into this,
-(UIImage *) imageForIndexPathRow:(int) row{
id image = [self.imageCache objectForKey:[NSNumber numberWithInt:row]];
if(!image){//if the image is not in the cache,
UIImage *newImage; //create image here
[self.imageCache setObject:newImage forKey:[NSNumber numberWithInt:row]];
return newImage;
}
return (UIImage *) image;
}
I have the following code to my UIViewController class implementation:
- (void)viewDidLoad {
CustomUITableView *customUITableView = [[CustomUITableView alloc] initWithFrame:self.view.frame];
[self.view addSubview:customUITableView];
[super viewDidLoad];
}
When I run the app, the table shows up just fine. There are two problems I have with this approach:
I lose the use of StoryBoard since the table is dynamically added to the view. I would like to arrange my view in StoryBoard and not have to manually tweak its position in code.
I also want the ability to swap between CustomUITableViews at runtime (if possible). For example, if I create a "CustomUITableView_Number2", can I simply decide which custom class I want to use for the table at runtime?
I know I can accomplish this in StoryBoard by assigning a custom class to the UITableView, but I want to see how far I can go using only code. My goal is to arrange the layout using StoryBoard, but assign the custom class I want at runtime.
Does anyone know if this is possible? Sample code would really help. Thank-you.
You could load a regular UITableView from interface builder and then initialize a CustomUITableView with the parameters you are interested in from the UITableView.
- (void)viewDidLoad {
//Your tableView was already loaded
CustomUITableView *customUITableView = [[CustomUITableView alloc] initWithFrame:tableView.frame style:tableView.style];
customUITableView.backgroundColor = tableView.backgroundColor;
//...
//... And any other properties you are interested in keeping
//...
[self.view addSubview:customUITableView];
[super viewDidLoad];
}
This approach will work if you make sure to specify each property you think you would modify in interface builder. Just make sure to remove the UITableView that was loaded via the nib after you copy the properties over.
sure you can replace the table.
something as simple as
Class MyTableClass = NSClassFromString(#"WhateverClass");
id newTableView = [[MyTableClass alloc] init];
[newTableView performSelector:#selector(someSel:)];
make sure you set your datasource and delegate and whatever properties from the original nib instantiated view first, then
you could then set your local tableview property to this new object and as long as the ivar has a proper cast you could call methods on it without resorting to runtime calls like above.
You can also dig into the obj-c runtime if you wanted to do it in a different way.
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html
I have a utility app for drawing.
In the xib there is a button that when pressed should update the UIImage variable.
- (IBAction)changeImage {
UIImage *drawImageVar = [UIImage imageNamed:#"line.png"];
That line is in the MainViewController.m file.
The code that uses the drawImageVar is in the MainView.m as part of the
- (void) drawRect:((CGRect)rect {
I don't get any errors when it runs in the simulator, but it doesn't do anything.
BUT...
I have a yellow warning in the MainViewController.m file that says ..
Unused variable 'drawImageVar
I assumed that it's not being seen globally by and tried other suggestions for making it global(singleton, putting variable in the AppDelegate), but non of that seems to work.
Any advice??
Mr.Michael H. The yellow warning says "Unused variable drawImageVar", it means you didn't use the vairable "drawImageVar" anywhere in your code after the initialization/declaration. So, you can use the variable inside of the method by local declaration, if you want to use this variable along the class you should create an object for UIImage in header (.h file). Hope you got any idea. Thanks.
YOUr drawImageVar is a local variable. You have to make it global or use the global variable instead
add to your header file:
UIImage *drawImageVar;
#property (nonatomic, retain) UIImage *drawImageVar;
add to your implementation file:
#synthesize drawImageVar;
- (IBAction)changeImage {
drawImageVar = [UIImage imageNamed:#"line.png"];
[drawImageVar setNeedsDisplay];
I assume you are using drawImageVar in DrawRect.
I have several instances of a UIControl class Foo being instantiated, one instance corresponding to each cell in a UITableView. The Class has:
BOOL selected;
UIImageView *imageView;
UIImage *imageOne;
UIImage *imageTwo;
I've assigned each instance a tag:
foo.tag = indexPath.row;
I would now like to reference the UIImageView.image for a (or several) specific instance(s) by its tag to switch it to the other image.
In my search I've seen things like classes being assigned tags using initWithTag (I assume they're assigning tags)...
SomeClass *someClass = [[SomeClass alloc]initWithTag:1 ...
[someArray addObject: [[SomeClass alloc]initWithTag:2 ...
[someArray addObject: [[SomeClass alloc]initWithTag:3 ...
...but I haven't seen how they are later referenced by that tag.
I have seen a reference to getChildByTag which had promise, but I can't find it in the documentation or examples (maybe not iphone).
Does anyone know how reference the imageView.image within an instance using the instance's tag? (the imageView doesn't have a tag)
Thanks
Let me be a bit more specific. As each instance of Foo is set up in the UITableViewCells I use addTarget
[self addTarget: self action: #selector(switchImage:) forControlEvents: UIControlEventTouchDown];
Then I have this method to switch the images:
- (void) switchImage:(id)sender
{
selected = !selected;
imageView.image = (selected ? imageOne : imageTwo);
// self.tag in here is the indexPath.row from the foo.tag initially assigned
// NSLog(#"switchImage:%#",sender);
}
This works perfectly. I click on the image and the image switches. But in the entire tableView, I only want 1 imageOne all the rest to be imageTwo so I want a way to first turn off all images and then turn on the one. I therefore felt that I could to loop thru all of the instances of Foo using the tag to either somehow directly change the imageView.image or run switchImage in each of the instances to turn off each cell's image.
Lastly, when I look at sender via NSLog, I see that each Foo has a different address so I was wondering if something like allTargets (Foo is a UIControl) would allow me to get to all of the switchImage methods.
I'm pretty deep into this rabbit hole but I'll certainly start over if necessary.
I agree that there are other ways to deal with this, but you could add all your Foo objects to an array or set and iterate through them until you find the one with the tag you want, then access the imageview in the usual way.
If I'm getting you right, you try to access Foo objects (globally) by the tag you assigned to them before. There's no way to do that and it's not what the tag property of UIViews was designed for.
You have to use another way to access your Foo objects. There are uncountable ways of setting up a way to access objects. Since you would usually access them from their data source or view controller, I’d add a dictionary with there. You could also use UITableView's visibleCells property to only change images on cells that are actually displayed.
I feel a bit lost, but how about [UIView viewWithTag:]?
UIView instances have a tag property. You can access them from their superview using:
UIView *view = [superview viewWithTag:n];
I have a subclass of UITableViewCell which contains several elements - UIImageViews, Labels, etc.
Since this cell is intended to be reusable, I want to be able to change it's appearance a bit depending on what data it is currently displaying.
So as an example - I have this view in my custom UITableViewCell:
UIImageView* delimeterView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"cellDelimiter.png"]];
Which I want to be able to hide sometimes like this:
- (void) setRecord:(id)record__ {
if (record__.type == NO_DELIMETER_VIEW)
delimeterView.hidden = YES;
else
delimeterView.hidden = NO;
[self setNeedsLayout];
}
But the problem is that delimeterView will always be displayed on the cell, just like if it was drawn once in the init method and then drawing context was never changed or cleared. I've tried setting clearsContextBeforeDrawing property to YES for both cell and its contentView, I've also tried setting opaque for cell and its contentView to NO since I've read there might be some problems with that aswell in case you're using transparent views.
Nothing helps.
It looks like UITableViewCell never clears its graphic context and just paints over old elements.
Any tips on what am I doing wrong?
I know I can probably fix this by doing custom drawing but I'd rather not.
First, are you sure that delimeterView in setRecord: is actually pointing to your delimeterView? In the code example you give, you assign it to a local. Do you later assign this to an ivar? (You should always use accessors to access ivars: self.delimeterView).
Next, calling -setNeedsLayout just schedules a call to -layoutIfNeeded, which walks the hierarchy calling -layoutSubviews. The default implementation of -layoutSubviews does nothing. You probably meant to call -setNeedsDisplay here, or you need to implement -layoutSubviews to do what you want.