Does CALayer derive its contents? - iphone

After investigating Custom UITableViewCell backgroundView & selectedBackgroundView, I noticed that each layer of backgroundView & selectedBackgroundView (of each cell of a grouped table view) has a CGImageRef as its contents.
Must Apple be creating each CGImageRef and explicitly setting the contents of each layer?

Yes, a UIGroupTableViewCellBackground is its layer's delegate and sets its layer's contents in displayLayer:. I figured this out by creating MyLayer : CALayer with a setContents: method, in which I put a breakpoint, and implementing +[UIGroupTableViewCellBackground layerClass] to return [MyLayer class]. Then, I inspected the backtrace when the breakpoint was hit.

Related

Table View Cell With Image

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;
}

Isn't property layer of UIView be readonly?

The property layer of UIView is described in Apple's doc as following:
layer
The view’s Core Animation layer used for rendering. (read-only)
#property(nonatomic, readonly, retain) CALayer *layer
It is obviously that it is readonly. But in my project, why it could be set as following:
NSLog(#"before: %f",self.myView.laye.frame.size.width);
[self.myView.layer setAffineTransform:CGAffineTransformMakeScale(2, 2)];
NSLog(#"after: %f",self.myView.laye.frame.size.width);
//log shows us that the frame is modified
Really confused in this situation. Anyone can help me out? Thanks in advance!
The layer property is read-only, it means you cannot change the layer for another, however the CALayer object contained in the property is not immutable, you can set its own properties.
You cannot do:
self.myView.layer = newLayer;
// equivalent to [self.myView setLayer:newLayer];
But you can do:
[self.myView.layer setAffineTransform:CGAffineTransformMakeScale(2, 2)];
It's the setLayer: selector that you can't use.
CALayer is not part of UIKit. It’s part of the Quartz Core framework
while UIView class is a part of UIKit. You can read the documentation of both to know the differences
UIView inherits from NSObject and CALayer also inherits from NSObject so at the time you are doing: [self.myView.layer setAffineTransform:CGAffineTransformMakeScale(2, 2)];
You are not assigning the layer , you are actually accessing the CALayer class layer properties directly and therefore you can play with position, size, and transformation of a layer, as you can see in CALayer documentation, it allows all these things

iOS: UIView's origin with respect to UIWindow

A UIView has an origin contained in its frame or bounds properties with respect to its superview. Say I want to do something which grabs the origin of a UIView with respect to the UIWindow instead. Do I have to go each step up the hierarchy in order to make this calculation or is there a simpler, more direct way?
I guess you looking for this method
convertPoint:toView
to find out where a view aView is in the application window, try
[aView convertRect:aView.frame toView:[UIWindow screen]]
This method is in the UIView Class Reference
The best and easiest way to do this is by using convertPoint:toView or convertRect:toView on UIWindow. In order to do this correctly, you need to supply a view whose window matches the window of the view whose coordinates you are trying to convert.
For your question, you need to know a view's origin with respect to its location in the window. Thankfully, you don't need to know the window in order to get this. According to the documentation of UIView's convert:to methods.
The view into whose coordinate system point is to be converted. If view is nil, this method instead converts to window base coordinates. Otherwise, both view and the receiver must belong to the same UIWindow object.
This means that you can just supply nil to achieve what you need!
In Swift:
let pointInWindow = yourView.convert(.zero, to: nil)
In Objective-C:
CGPoint pointInWindow = [yourView convertPoint:CGPointZero toView:nil];

Replacing the content of UIImage(s) loaded from XIB at runtime

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

UITableViewCell doesn't clear context before drawing

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.