iOS CALayer memory leak only on device - iphone

The Instruments tool reports memory leak for CALayer. I have a custom class, a subclass of UIViewController; in the viewDidLoad() I instantiate a view with CAGradientLayer and insert this view as a subview of current view, e.g.:
UIControl *view = [[[UIControl alloc] initWithFrame:CGRectMake(10, 10, 460, 220)] autorelease];
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = view.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor whiteColor] CGColor], (id)[[UIColor colorWithRed:0.85 green:0.85 blue:0.85 alpha:1.0] CGColor],nil];
gradient.startPoint = CGPointMake(0, 0);
gradient.endPoint = CGPointMake(1, 1);
[view.layer insertSublayer:gradient atIndex:0];
view.layer.masksToBounds = YES;
[view.layer setCornerRadius:5];
[self.view insertSubview:view atIndex:1];
When I run the code in simulator, all is fine. However when I run it on device (iOS 4.3.3), the Instruments tool reports leaks for CALayer. When I coment out this code, there are no leaks.
What is the issue here?
Edit: I found out it only leaks if I insert the subview into a view which has a scrollview (so my subview with the gradient calayer is inserted between the view and scrollview).

release the
uiview and
layer
because simulator runs in system configuration so no problem will occur
but the device has less memory compared to system

Related

add subLayer to a imageView.layer

well I know that every imageViews have a layer (we can access it with :imageView.layer).I would like to use the layer of my imageView and add a subLayer to it's layer:shapeLayer(that is a CAShapeLayer) My code doesn't work, it doesn't show the shape layer!
- (void)viewDidLoad
{
imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
[self.view addSubview:imageView];
}
- (void)anotherMethod
{
[imageView.layer addSublayer:shapeLayer];
}
How can I solve this please ?
Try this:
[outputImage.layer insertSublayer:shapeLayer atIndex:0];
I solved similar problem by using UIView below UIImageView and setting a layer to UIView. When you add a layer to UIImageView at index 0, your image will not be visible.
I have a UIView called photoView. It has a subview of class UIImageView, that has same bounds and has a clear background color. And I add a gradientLayer to photoView.
- (void)viewDidLoad
{
[super viewDidLoad];
// Adding gradient background to UIImageView
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = self.photoImageView.bounds;
gradientLayer.colors = #[(id)[UIColor customUltraLightGrayColor].CGColor, (id)[UIColor customUltraLightGrayColor].CGColor, (id)[UIColor customDarkGrayColor].CGColor];
gradientLayer.locations = #[#(0.0), #(0.6)];
[self.photoView.layer insertSublayer:gradientLayer atIndex:0];
}
As a result I get this:
This could have several reasons, for example:
Did you #import <QuartzCore/QuartzCore.h> (and / or CoreGraphics/CoreGraphics.h> depending on what you are doing in the rest of your code)?
You could have set a wrong CGRectMake when initializing the CALayer which is not inside your visible frame (Like CGRectMake(5000,5000,...,...)) or one of the size properties is 0?
Try setting the frame for layer:
Follow the three steps
CALayer layerToBeAdded = [[CALayer alloc] init];
[layerToBeAdded setFrame:imgView.frame];
[imgView.layer addSublayer:layerToBeAdded];
Instead of allocating CALayer, you can use [imgView layer]
And to add image to layer do it like this
layerToBeAdded.contents = (id)imgXYZ.CGImage;
Assuming that you are talking about just one UIImageView (imageView == outputImage ???)
//in another method:
{
[imageView.layer addSublayer:shapeLayer]; //Where is outputImage initialized?
}
Here is a solution:
CALayer *pulseLayer_ = [[CALayer layer] retain];
pulseLayer_.backgroundColor = [[UIColor whiteColor] CGColor];
pulseLayer_.bounds = CGRectMake(0., 0., 80., 80.);
pulseLayer_.cornerRadius = 12.;
pulseLayer_.position = self.view.center;
[self.view.layer addSublayer:pulseLayer_];
CALayer *imageLayer = [CALayer layer];
imageLayer.frame = pulseLayer_.bounds;
imageLayer.cornerRadius = 10.0;
imageLayer.contents = (id) [UIImage imageNamed:#"jacklogo.png"].CGImage;
imageLayer.masksToBounds = YES;
[pulseLayer_ addSublayer:imageLayer];
[pulseLayer_ setNeedsDisplay];

iPhone groupTableViewBackgroundColor alternative for iPad

I am building an universal navigation app on iOS 5.0.
Setting the view backgroundcolor as below looks good for iPhone
self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];
But for iPad, i found out that this does not work as expected.
Is there any alternative to get same background on iPad using some color pattern?
If so ,please point me to some code samples or post some.
Recently when working on an XCode project we had set the background colour to groupTableViewBackgroundColor, which on an iPhone produces a pinstriped background, and works well. This background is the same one as used on UITableViews.
When porting this to iPad, the UITableViews now have a solid grey colour, and groupTableViewBackgroundColor essenitially is the same as [UIColor clearColor].
You would think that grabbing the colour that the UITableView uses would work. I am afraid it does not, as it turns out it is a slight gradient, not so much as you would notice, but when you're changing views, you notice.
The way to fix this is to create a CAGradientLayer, and apply it to a UIView before anything else loads.
First off, you need to create a function that emulates the gradient, below is what I used:
-(CAGradientLayer *)groupGradientColour {
CAGradientLayer *layer = [CAGradientLayer layer];
layer.colors = [NSArray arrayWithObjects:
(id)[[UIColor colorWithRed:(0xE2 / 255.0)
green:(0xE5 / 255.0)
blue:(0xE9 / 255.0)
alpha:1.0] CGColor],
(id)[[UIColor colorWithRed:(0xD0 / 255.0)
green:(0xD2 / 255.0)
blue:(0xD7 / 255.0)
alpha:1.0] CGColor],
nil];
layer.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0f],
[NSNumber numberWithFloat:1.0f],
nil];
return layer;
}
Then call this in viewDidLoad:
UIView *background = [[UIView alloc] init];
background.frame = CGRectMake(
0,
0,
[[UIScreen mainScreen] applicationFrame].size.width,
[[UIScreen mainScreen] applicationFrame].size.height);
CAGradientLayer *gradient = [self groupGradientColour];
gradient.frame = background.bounds;
[background.layer insertSublayer:gradient atIndex:0];
[self.view addView:background atIndex:0];
Once you have done this, you will have a gradient the same style as a UITableView.

Simultaneous animation when entering editing mode of UITableViewCell

when entering the editing mode in a UITableViewCell through setEditing:animated:, there is an (implicit) animation where the cells move right and the delete "buttons" appear at the left.
How do I manage, probably with Core Animation features, to run a second animation simultaneously to the first animation when entering editing mode, i.e. with the the same starting time, duration, timing curve, and so on? (I'm actually trying to change the width of a cell's sublayer synchronously.)
I studied the Core Animation Programming Guide and tried some of the techniques in a custom UITableViewCell class, for example by overriding willTransitionToState:. In principle, everything works well, however, I can only manage to have the animations one after the other, but not simultaneously.
Any advice is greatly appreciated.
Alright, it's much simpler than I initially thought. The behavior I sought can rather easily be achieved by correctly initializing the layers (or views) in the custom UITableViewCell. However, I realized that the order and, e.g., whether you manipulate bounds or frame properties is quite important.
The following code snippet does the job for a cell in a grouped UITableView. In this example, a CAGradientLayer (as an example for a CALayer) is added as a sublayer to a UIView, which itself is added as a subview to self.contentView of the cell. The subview's frame is tweaked to fit into the contentView. The code goes, e.g., into initWithStyle:reuseIdentifier: of a custom UITableViewCell:
CGRect realFrame = self.contentView.frame;
realFrame.size.width += 20;
UIView *gradientView = [[UIView alloc] initWithFrame:realFrame];
CAGradientLayer *gradientLayer = [[CAGradientLayer alloc] init];
gradientLayer.anchorPoint = CGPointMake(0.0, 0.0);
gradientLayer.frame = gradientView.frame;
gradientLayer.position = CGPointMake(0.0, 0.0);
[gradientLayer setColors:[NSArray arrayWithObjects:(id)[[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:1.0] CGColor], (id)[[UIColor clearColor] CGColor], nil]];
gradientLayer.startPoint = CGPointMake(0.5, 0.0);
gradientLayer.endPoint = CGPointMake(0.5, 1.0);
gradientView.layer.masksToBounds = YES;
gradientView.layer.cornerRadius = 8.0;
[gradientView.layer insertSublayer:gradientLayer atIndex:0];
[self.contentView addSubview:gradientView];
gradientView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[gradientView release];
[gradientLayer release];

Mask overlay on UIScrollView

how would you go about overlaying a mask image of sorts on top of a UIScrollView?
For example, I have an image with black on the left and right fading to white in the center. I'd like to use this so that the items in my scroll view gradually fade out to the sides and the center item is completely opaque.
Also, the background of the view the scrollview is placed on is an image (not a solid color) which is dynamic and can change.
Any ideas?
Your question wound up being the best source I could find on the basic problem, so I decided to post my solution here.
If you add the mask directly to the scroll view, you wind up applying the mask to the content itself for some reason -- i. e. the transparency doesn't shift as you scroll. As per the suggestions of other posts, I put the gradient in a view over (or rather, containing) the scroll view. As a result, scrollContainer is a UIViewObject whose sole subview is the scroll view in question.
This mask is configured for left-to-right scrolling. You could change it to top to bottom by manipulating the start and end point properties of the gradient.
The one down side is that this also clips the scroll position bar indicator of the scroll view. Since my scenario doesn't need one, that's acceptable, but your mileage may vary.
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = scrollView.bounds;
gradient.colors = [NSArray arrayWithObjects:
(id)[[UIColor colorWithWhite:0 alpha:0] CGColor],
(id)[[UIColor colorWithWhite:0 alpha:1] CGColor],
(id)[[UIColor colorWithWhite:0 alpha:1] CGColor],
(id)[[UIColor colorWithWhite:0 alpha:0] CGColor], nil];
gradient.locations=[NSArray arrayWithObjects:
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:.1],
[NSNumber numberWithFloat:.9],
[NSNumber numberWithFloat:1], nil];
gradient.startPoint=CGPointMake(0, .5);
gradient.endPoint=CGPointMake(1, .5);
[scrollContainer.layer setMask:gradient];
If you have a solid color background behind the scrollview, you'll get better performance by just overlaying an image on top of the scroll view rather than trying to perform a mask within it.
Try something along these lines:
// I assume you are in a UIViewController and self.view is set to some kind of view
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 320, 420)];
scrollView.backgroundColor = [UIColor redColor];
[self.view addSubview: scrollView];
[scrollView release];
// add img with gradient
UIImageView *imgView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"my-gradient.png"];
imgView.frame = CGRectMake(0, 0, 160, 420);
[self.view addSubview: imgView];
[imgView release];
This would give you a gradient that begins on the left and goes all the way to the center for the entire height, assuming that "my-gradient.png" is an image that actually contains a gradient.
These kind of manipulations are big ressources consumers. The best approach would be (if your case allows it) to put a mask image OVER your UIScrollView. This image's background shoud be transparent (ex. png32) and the image should be an alpha-gradient.
Use insertView:yourView atIndex:0

replaceSublayer example

I have the following code and I can't seem to get it to work.
First I create a view with a layer.
UIView *view = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 100)] autorelease];
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = view.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor blackColor] CGColor], (id)[[UIColor whiteColor] CGColor], nil];
[view.layer insertSublayer:gradient atIndex:0];
Next I need to replace that layer. I have tried the following and it fails because I don't know how to access that layer 0. At this point I am in a different part of the program so I cannot just call gradient. I need to extricate it from view somehow.
[view.layer replaceSublayer:0 with:newgradient];
Apparently the 0 is supposed to be Old layer but I don't know how to access it.
Thanks.
I think you should be able to use this:
[view.layer replaceSublayer:[[view.layer sublayers] objectAtIndex:0]
with:newGradient];
I'm away from my Xcode development environment and can't test this at the moment though.
Keep a pointer to the gradient layer. If you have alot of them do it with an array.
If you still don't want to do that you can always use this to get an array of all the layers..
[self.layer sublayers];
Then you can cycle through and check some parameter to see if they are the same. Like set the layer.name to something when you create it.
Here is the function :
#define MyGradientLayerName #"MyGradient"
void makeViewGradient(UIView *pView,BOOL bRemoveBackground,CGColorRef clr1,CGColorRef clr2)
{
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.name = MyGradientLayerName;
gradient.frame = pView.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)clr1,(id)clr2,nil];
CALayer *pGradientLayer = nil;
NSArray *ar = pView.layer.sublayers;
for (CALayer *pLayer in ar)
{
if ([pLayer.name isEqualToString:MyGradientLayerName])
{
pGradientLayer = pLayer;
break;
}
}
if (!pGradientLayer) [pView.layer insertSublayer:gradient atIndex:0];
else [pView.layer replaceSublayer:pGradientLayer with:gradient];
if (bRemoveBackground) pView.backgroundColor = nil;//free memory !
}