How to set the CAlayer bounds from my view controller for multiple subview - iphone

I am new to iOS programming. Please help me to sort out the following issue
I have one set of view controller(.h, .m and .XIB)
I have one set of view(.h&.m)
3.The view class is responsible for drawing gauge using
-->drawRect
-->CALayer and sublayers
In this view I have initialize method, and this method only i set the bounds for my layers and sublayers
here i am pasting some sort of code..
- (void)initialize {
CGFloat span = fmin(self.frame.size.width, self.frame.size.height);
frameLayerObject_ = [[OuterFrameLayer alloc] init];
frameLayer = [[CALayer layer] retain];
frameLayer.bounds = CGRectMake(250, 50, 710, 320);
frameLayer.position = self.frame.origin;
frameLayer.needsDisplayOnBoundsChange = YES;
frameLayer.delegate = self.frameLayerObject;
[self.layer addSublayer:frameLayer];
[frameLayer setNeedsDisplay];
self.minValue = 0.0;
self.maxValue = 100.0;
self.tickStartAngle = M_PI;
self.tickArcLength = M_PI
self.lineWidth = span / 200.0;
self.tickLength = span / 12.0;
self.minorTickLength = span / 16.0;
self.tickInset = self.lineWidth;
textLabel_ = [[UILabel alloc] initWithFrame:CGRectMake(0, 3.0 * self.frame.size.height / 10.0, self.frame.size.width, self.frame.size.height / 10.0)];
self.textLabel.textColor = [UIColor whiteColor];
self.textLabel.backgroundColor = [UIColor clearColor];
self.textLabel.font = [UIFont systemFontOfSize:span / 17.77];
[self addSubview:self.textLabel];
needleLayerObject_ = [[gaugeNeedle alloc] init];
self.needleLayerObject.tintColor = [UIColor whiteColor];
needleLayer = [[CALayer layer] retain];
needleLayer.bounds = self.bounds;
needleLayer.position =CGPointMake(((self.bounds.size.width / 2.0)), ((self.bounds.size.height / 2.0)));
needleLayer.needsDisplayOnBoundsChange = YES;
needleLayer.delegate = self.needleLayerObject;
[self.layer addSublayer:needleLayer];
[needleLayer setNeedsDisplay];
}
In my view controller, I have created 5 views in my xib and .h and i am assigning the same view class for all the 5 views.
Now the problem is, all 5 views differ in their position and bounds. But my drawRect view class has only one set of bounds for all views. So if I hardcode the bounds and position in initialize method with respect to one view, the remaining 4 views are getting affected. Please let me know if u know where i am going wrong.
Thanks in advance..

each of your five views should be executing the draw code relative to their own coordinate space, and there would be no problem. Use self.bounds as the rectangle in your drawing code and the five should all draw identically in their own space. Don't use self.frame, this is in the parent view's coordinate space, and introduces the problem you are experiencing.
You may also want to experiment with the clipsToBounds property, when this is set to YES a UIView can't draw beyond its own borders.

Related

How to remove the saw tooth of UIView when using Quartz?

I want to add a head button to a view with some white space around it. My code is just like this:
// head button
UIButton * btnHead = [UIButton buttonWithType:UIButtonTypeCustom];
btnHead.frame = CGRectMake(0, 0, self.frame.size.width - property.userhead_cell_space / 2, self.frame.size.width - property.userhead_cell_space / 2);
btnHead.clipsToBounds = YES;
btnHead.layer.cornerRadius = btnHead.bounds.size.width / 2;
btnHead.layer.borderColor = [UIColor whiteColor].CGColor;
btnHead.layer.borderWidth = (isPad?4.0f:2.0f);
btnHead.layer.contentsScale = [[UIScreen mainScreen] scale];
btnHead.layer.backgroundColor = [UIColor whiteColor].CGColor;
[btnHead addTarget:self action:#selector(clickHead:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:btnHead];
But alway it has some saw tooth around it . It may only one pixel. But it looks very terrible . Just like this:
Does anybody has some tip to remove the black saw tooth ?
Way late to the party but from past experience this is a bug with corner radius and borders. I've tried using it to create circular borders around views and have noticed similar bleeding.
One workaround is to create a view for the border and then a slightly inset view for the image.
UIView* border = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
border.backgroundColor = UIColor.whiteColor; // border color
border.layer.cornerRadius = border.bounds.size.width / 2;
border.layer.masksToBounds = YES;
// inset by the desired border width
UIImageView* image = [[UIImageView alloc] initWithFrame:CGRectInset(border.bounds, 2, 2)];
image.image = someImage;
image.layer.cornerRadius = image.bounds.size.width / 2;
image.layer.masksToBounds = YES;
[border addSubview:image];
[self addSubview:border];
If you want to avoid multiple views, you could add an additional background layer to the image view's layer for the border. That would need to have it's bounds negatively inset so that it drew around the image.

Fitting View Frame around Scaled CALayer

In the following method, I'm simply enlarging a bunch of CAShapeLayers which are attached to my views layer property by using CATransform3DMakeScale. I'm able to enlarge the shape layers correctly, but am having a tough time getting the view frame to enlarge and fit around the graphic produced by the shape layers like it should. Everything is the same size, ie the view frame is the same size as all of the shape layers.
Anyone have any suggestions on how to fix this? I've tried altering the views transform property as well as the views layer transform property. Not sure what I'm missing here.
- (void) setSize:(CGSize)size
{
CATransform3D transform = CATransform3DMakeScale(size.width / (self.graphic.initialSize.width), size.height / (self.graphic.initialSize.height), 1);
self.layer.sublayerTransform = transform;
self.frame = CGRectMake(0, 0, size.width, size.height);
self.graphic.size = CGSizeMake(size.width, size.height);
}
Also for reference, here is how I am setting up the views layer:
- (id)initWithGraphic:(Graphic*)graphic
{
self = [super initWithFrame:CGRectZero];
if (self)
{
self.backgroundColor = [UIColor clearColor];
_graphic = [graphic retain];
self.frame = CGRectMake(0,0, _graphic.initialSize.width, _graphic.initialSize.height);
self.layer.shouldRasterize = YES;
for (int i = 0; i < [_graphic.shapeLayers count]; i++)
{
[self.layer addSublayer:[_graphic.shapeLayers objectAtIndex:i]];
}
}
return self;
}
and here is how I'm setting up one of the shape layers:
CAShapeLayer *outlineShapeLayer = [[CAShapeLayer alloc] init];
outlineShapeLayer.anchorPoint = CGPointMake(0, 0);
outlineShapeLayer.fillColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:1].CGColor;
outlineShapeLayer.bounds = shapeLayerFrame;
outlineShapeLayer.mutablePath = mutablePath;
I had to make it so my views layer also had it's anchorPoint and position set up to match my CAShapeLayers and then it worked:
self.layer.anchorPoint = CGPointMake(0, 0);
self.layer.position = CGPointMake(0, 0);

Make view transparent without seeing the text from bottom view

In the iPhone calendar app if you have 2 tiles overlaying ontop of each other the text from the bottom tile gets cut off and cannot be seen through the top transparent tile. How would I be able to keep the tiles transparent but not have the text from the bottom tiles show through to the top tiles?
Here is my code
APCalendarDayTile *tile = (APCalendarDayTile *)view;
CGFloat startPos = [APCalendarCurrentDayView yAxisForTime:[APCalendarCurrentDayView minutesToTime:tile.appointment.startDate]];
CGFloat endPos = [APCalendarCurrentDayView yAxisForTime:[APCalendarCurrentDayView minutesToTime:tile.appointment.endDate]];
tile.frame = CGRectMake(kLeftSideBuffer, startPos, (self.bounds.size.width - kLeftSideBuffer - kRightSideBuffer) , endPos - startPos);
tile.backgroundColor = [UIColor colorWithHexString:tile.appointment.appointmentColor]; <-- This also sets the alpha that makes it transparent.
tile.layer.borderColor = [UIColor colorWithHexString:tile.appointment.appointmentColor alpha:1.0].CGColor;
tile.layer.borderWidth = 1.0f;
Tile code
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
CALayer *layer = [self layer];
[layer setMasksToBounds:YES];
[layer setCornerRadius:kCornerRadius];
}
return self;
}
- (id)init
{
if (self = [super init]) {
self.clipsToBounds = YES;
self.userInteractionEnabled = YES;
self.multipleTouchEnabled = NO;
tileTitle = [[UILabel alloc] init];
tileTitle.textColor = [UIColor blackColor];
tileTitle.backgroundColor = [UIColor clearColor];
tileTitle.font = [UIFont boldSystemFontOfSize:13.0f];
[tileTitle setAutoresizesSubviews:YES];
[tileTitle setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
tileDescription = [[UILabel alloc] init];
tileDescription.textColor = [UIColor blackColor];
tileDescription.backgroundColor = [UIColor clearColor];
tileDescription.font = [UIFont systemFontOfSize:11.0f];
tileDescription.lineBreakMode = UILineBreakModeTailTruncation;
[tileDescription setAutoresizesSubviews:YES];
[tileDescription setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
[self setAutoresizesSubviews:YES];
[self setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
[self addSubview:tileTitle];
[self addSubview:tileDescription];
}
return self;
}
- (void)layoutSubviews
{
CGRect myBounds = self.bounds;
CGSize stringSize = [tileTitle.text sizeWithFont:tileTitle.font];
if (myBounds.size.height <= 22.0) {
tileTitle.frame = CGRectMake(3, 0, myBounds.size.width, stringSize.height);
tileDescription.frame = CGRectMake(stringSize.width + 6, -1, myBounds.size.width, 14);
} else {
tileTitle.frame = CGRectMake(3, 0, myBounds.size.width, stringSize.height);
tileDescription.frame = CGRectMake(5, tileTitle.frame.size.height, myBounds.size.width, 14);
}
}
I would like the text in this photo to not show in the front tiles like the second photo.
#user1272965 is right insofar as each tile needs to know what tiles are above it, then you can get the text effect you're looking for by making the tile views a custom subclass and implementing their drawing manually:
Each tile will need access to tiles that are above it (or their frames at least):
NSInteger tileCount = [tilesAboveMine count];
CGRect framesAboveMine[tileCount];
for (int i=0; i<tileCount; i++) {
framesAboveMine[i] = [tilesAboveMine objectAtIndex:i].frame;
}
In drawRect of your tile view, draw the tile, and then draw the text clipped by the views above it:
// draw aspects of the tile not to be clipped, like the background image.
// the setup for clipping:
CGContextClipToRects(context, framesAboveMine, tileCount);
then draw the text, which will be clipped by the frames above
[myCalendarString drawAtPoint:CGPointMake(10,10)];
What about adding a UIView in between the two boxes? This middle UIView would cover up the letters (and have the same background color as the rear event), which solves your problem: no text coming through, but you'll see the color behind the other label!
You might be able to create the frame for this UIView using the frame attribute of the UILabel as well as the frame attribute of the frontmost calendar appointment.
I think the native calendar app squeezes tiles that occur at the very same start time as you do, but it also squeezes tiles whose times overlap, i.e. if the start time of the later tile is between the start and end time of the earlier tile.
But if you need your UI to draw these tiles overlapping, then there's no way around it: the obscured tile needs to know that it's underneath (starting after and overlapping), and it needs to either hide its labels, or reduce their alpha level.

Fixing CALayer at top and bottom of UIScrollView

I am subclassing UIScrollView and adding some visual elements. At the top, there should be a gradient going from black to clear, and at the bottom there is a mask that fades out towards the bottom. I have those layers added and looking right, but when I scroll, they stay at the coordinates I put them in (with respect to the scroll view), rather than being "fixed" to the bottom and top of the view. This scroll view only scrolls vertically.
Here is the code for SettingsScrollView.m:
#import "SettingsScrollView.h"
#import <QuartzCore/QuartzCore.h>
#define SHADOW_HEIGHT 20.0
#define SHADOW_INVERSE_HEIGHT 10.0
#define SHADOW_RATIO (SHADOW_INVERSE_HEIGHT / SHADOW_HEIGHT)
#implementation SettingsScrollView
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
[self setUpShadow];
return self;
}
- (CAGradientLayer *)shadowAsInverse:(BOOL)inverse
{
CAGradientLayer *newShadow = [[[CAGradientLayer alloc] init] autorelease];
CGRect newShadowFrame = CGRectMake(0, 0, self.frame.size.width, inverse ? SHADOW_INVERSE_HEIGHT : SHADOW_HEIGHT);
newShadow.frame = newShadowFrame;
CGColorRef darkColor =[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:inverse ? (SHADOW_INVERSE_HEIGHT / SHADOW_HEIGHT) * 0.5 : 0.5].CGColor;
CGColorRef lightColor = [self.backgroundColor colorWithAlphaComponent:0.0].CGColor;
newShadow.colors = [NSArray arrayWithObjects: (id)(inverse ? lightColor : darkColor), (id)(inverse ? darkColor : lightColor), nil];
return newShadow;
}
- (CAGradientLayer *)gradientMask
{
CAGradientLayer *mask = [[[CAGradientLayer alloc] init] autorelease];
CGRect maskFrame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
mask.frame = maskFrame;
CGColorRef darkColor =[UIColor clearColor].CGColor;
CGColorRef lightColor =[UIColor blackColor].CGColor;
mask.colors = [NSArray arrayWithObjects: (id)lightColor, (id)lightColor, (id)darkColor, nil];
mask.locations = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0], [NSNumber numberWithFloat:0.9], [NSNumber numberWithFloat:1.0], nil];
return mask;
}
- (void)setUpShadow
{
CAGradientLayer *topShadowLayer = [self shadowAsInverse:NO];
CAGradientLayer *bottomShadowLayer = [self gradientMask];
[self.layer insertSublayer:topShadowLayer atIndex:0];
[self.layer setMask:bottomShadowLayer];
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
CGRect topShadowLayerFrame = topShadowLayer.frame;
topShadowLayerFrame.size.width = self.frame.size.width;
topShadowLayerFrame.origin.y = 0;
topShadowLayer.frame = topShadowLayerFrame;
CGRect bottomShadowLayerFrame = bottomShadowLayer.frame;
bottomShadowLayerFrame.size.width = self.frame.size.width;
bottomShadowLayerFrame.origin.y = self.frame.size.height - bottomShadowLayer.frame.size.height;
bottomShadowLayer.frame = bottomShadowLayerFrame;
[CATransaction commit];
}
#end
I know one solution for the top could just be to add a separate view that contains a gradient, but for the bottom I believe I need to use a mask to have it do what I want it to (fade out into the background at the very bottom). The background is an image so I can't just fade to white or another color, it needs to fade to clear. I've been looking for a method that gets called when the scroll view is moved and use that to change the position of the mask, but I haven't found anything yet. Any suggestions?
You could probably accomplish this by overriding -layoutSubviews on the UIScrollView, which is invoked when its bounds change.
Another technique I've seen on views like this is that instead of doing this layer management from the view, subclass CALayer, use your subclass as the scroll view's layer, and then in your layer's -layoutSublayers, do to the same work when it moves around. I mention this latter technique, because it seems somewhat more natural for the layer to be managing its sublayers vs the view managing its layers sublayers directly.

Drawing a transparent circle on top of a UIImage - iPhone SDK

I am having a lot of trouble trying to find out how to draw a transparent circle on top of a UIImage within my UIImageView. Google-ing gives me clues, but I still can't find a working example.
Are there any examples that anyone knows of that demonstrate this?
Easiest way is simply to create a semi-transparent square UIView, then set the cornerRadius of its layer to be half of its width/height. Something like:
UIView *squareView = [[UIView alloc] initWithFrame:CGRectMake(0,0,100,100)];
squareView.alpha = 0.5;
squareView.layer.cornerRadius = 50;
...
[squareView release];
This has got to be the simplest solution:
CGFloat r = 150;
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(0,0,1.5*r,1.5*r)];
lbl.text = #"●";
lbl.transform = CGAffineTransformMakeTranslation(0.0f, -r/6);
lbl.textAlignment = UITextAlignmentCenter;
lbl.backgroundColor = [UIColor clearColor];
lbl.textColor = [UIColor redColor];
lbl.font = [UIFont systemFontOfSize:2*r];
lbl.alpha = 0.5;
lbl.center = self.view.center;
[self.view addSubview:lbl];
One way would be to add a CAShapeLayer with a circular path, either directly to the layer of the UIImageView or as the layer of a new UIView that is added to the UIImageView.
If you actually want to modify the image, then create a mutable copy of it by drawing it into a CGBitmapContext then creating a new image from the modified bitmap.
CGPathRef circlePath = CGPathCreateMutable();
CGPathAddEllipseInRect( circlePath , NULL , CGRectMake( 0,0,20,20 ) );
CAShapeLayer *circle = [[CAShapeLayer alloc] init];
circle.path = circlePath;
circle.opacity = 0.5;
[myImageView.layer addSublayer:circle];
CGPathRelease( circlePath );
[circle release];
You can implement a custom sub-class of UIView that draws your image and then the circle in the drawRect method:
#interface CircleImageView : UIView {
UIImage * m_image;
CGRect m_viewRect;
// anything else you need in this view?
}
Implementation of drawRect:
- (void)drawRect:(CGRect)rect {
// first draw the image
[m_image drawInRect:m_viewRect blendMode:kCGBlendModeNormal alpha:1.0];
// then use quartz to draw the circle
CGContextRef context = UIGraphicsGetCurrentContext ()
// stroke and fill black with a 0.5 alpha
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 0.5);
CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 0.5);
// now draw the circle
CGContextFillEllipseInRect (context, m_viewRect);
}
You will need to set up the m_viewRect and m_image member functions on init.