what does bounds really mean in CALayer? - xcode

I was confused about the property bounds of CALayer,
- (void)viewDidLoad {
[super viewDidLoad];
CALayer *sublayer = [CALayer layer];
sublayer.backgroundColor = [UIColor blueColor].CGColor;
sublayer.frame = CGRectMake(18, 18, 154, 154);
[self.view.layer addSublayer:sublayer];
CALayer *sublayer2 = [CALayer layer];
sublayer2.backgroundColor = [UIColor redColor].CGColor;
sublayer2.frame = CGRectMake(20, 20, 150, 150);
sublayer2.bounds = CGRectMake(0, 0, 50, 50);
sublayer2.zPosition = 10;
[self.view.layer addSublayer:sublayer2];
}
sublayer2 draw a small 50X50 rectangle in the center of the rectangle of sublayer1,
but it will draw a 150X150 rectangle if this line is commented out:
sublayer2.bounds = CGRectMake(0, 0, 50, 50);

after read sch's "guide", I think the behavior is due to the following reason:
1 as metioned in the guide
The bounds rectangle is expressed in the view’s own local coordinate
system. The default origin of this rectangle is (0, 0) and its size
matches the size of the frame rectangle.
//and this is what bounds really mean!
...
...
When you set the size of the bounds property, the size value in the
frame property changes to match the new size of the bounds rectangle.
... ...
so when execute
sublayer2.bounds = CGRectMake(0, 0, 50, 50);
the frame's size will change to 50X50 automatically,
here CGRectMake(0, 0,..) "0,0" could be any value,because it will not take any effect.
2 because we didn't change anchorPoint ,by default anchorPoint is (0.5,0.5), and its corresponding position is (95,95), so finally it will draw a 5X5 rectangle whose center is (95,95)
please correct me if I am wrong

Related

rotate a sublayer in xcode

It's been a while, as I was hospitalized for 3 months after a motorcycle accident.
So I just got to renew my apple programming subscription :-)
I have another question that has been on my mind for quite some time.
In my iPad application I draw a triangle in the center of an iPad like this:
- (void)initTriangle
{
CGRect screenBound = [[UIScreen mainScreen] bounds];
CGSize screenSize = screenBound.size;
CGFloat screenWidth = screenSize.width;
CGFloat screenHeight = screenSize.height;
// draw triangle (TRIANGLE)
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path,NULL, 0.5*screenWidth, 0.5*screenHeight-25);
CGPathAddLineToPoint(path, NULL, 0.5*screenWidth-25, 0.5*screenHeight+25);
CGPathAddLineToPoint(path, NULL, 0.5*screenWidth+25, 0.5*screenHeight+25);
CGPathAddLineToPoint(path, NULL, 0.5*screenWidth, 0.5*screenHeight-25);
CAShapeLayer *triangle = [CAShapeLayer layer];
[triangle setPath:path];
[triangle setFillColor:[[UIColor blackColor] CGColor]];
[[[self view] layer] addSublayer:triangle];
CGPathRelease(path);
}
And I call this from my viewDidLoad like this:
[self initTriangle];
Now I'm trying to rotate this triangle with the rotation of my iPad around Z-Axis while laying flat on the table. I have a function that gives me the yaw readings in float and I'm calling my
-(void)updateTriangleWithYaw:(float)yaw
method, but I don't know what to exactly put in there to make it rotate.
here is what my method looks like so far:
-(void)updateTriangleWithYaw:(float)yaw
{
CGRect screenBound = [[UIScreen mainScreen] bounds];
CGSize screenSize = screenBound.size;
CGFloat screenWidth = screenSize.width;
CGFloat screenHeight = screenSize.height;
NSLog(#"YAW: %f", yaw);
Z += 2 * yaw;
Z *= 0.8;
CGFloat newR = R + 10 * yaw;
self.triangle.frame = CGRectMake(0.5*screenWidth, 0.5*screenHeight, newR, newR);
}
Any help will be greatly appreciated!
Thanks and be safe guys!!
You should set the layer's affineTransform. You can apply a rotation transform like:
[self.triangle setAffineTransform:CGAffineTransformMakeRotation(yaw)];
This method, setAffineTransform is a convenience to set the transform property of the layer, which is a more general type of transform CATransform3D. You can also set the transform of the layer directly, and if you want to do that you can make a rotation about the z-axis like:
self.triangle.transform = CATransform3DMakeRotation(yaw, 0, 0, 1);
In this case the first argument is the angle (in radians) and the last three arguments specify the axis of rotation.
Note that you should not assign or depend on the value of the frame property of a layer whose transform is not the identity (CGAffineTransformIdentity). When you use the transform property you should set the size and position of your layer by assigning the layer's center and bounds properties, and similarly you should read the center and bounds when you want to find out information about the layer's position and size.

create a UIBezierPath of 3 pixels width on retina #2x display

I'm trying to create a line of 3 pixels width on a retina #2x display. The simple idea would be to create a 1.5 width line :
UIGraphicsBeginImageContextWithOptions(CGSizeMake(20, 20), NO, 0.0f);
CGContextRef aRef = UIGraphicsGetCurrentContext();
CGContextSetAllowsAntialiasing(aRef, NO);
CGContextSetShouldAntialias(aRef, NO);
UIBezierPath* bezierPath = UIBezierPath.bezierPath;
[bezierPath moveToPoint: CGPointMake(10, 0)];
[bezierPath addLineToPoint: CGPointMake(10, 10)];
bezierPath.lineWidth = 1.5;
[bezierPath stroke];
UIImage * myImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
But at the end, I end up with a 4 pixels line width on screen.
The thing is, I'm using the iPad 3 (so retina #2x), and when I use a UIBarButtonItem with the predefined system button UIBarButtonSystemItemAdd, the two paths of the cross are 3 pixels width on my screen.
I suspect it's b/c your path is 1.5 points, but is started at (0,0). since the path is drawn half on one side of the path, and half on the other, it means that .75 pts are draw above/below the path.
that'd mean your path reaches from (in px):
left: (-1.5, 0) => (-1.5, 10)
center: (0,0) => (0,10)
right: (1.5,0) => (1.5,10)
that means each side will render using 2 pixels.
instead, you probably want to create the line from (.5, 0) => (.5, 10), which would align the path width to the pixels on screen:
left: (-1, 0) => (-1, 10)
center: (.5, 0) => (.5, 10)
right: (2, 0) => (2, 10)
It worked with :
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRect: CGRectMake(13, 21, 18, 1.5)];

Change border corlor of NSTableView

Can I change the color of NSTableView's border. The gray line at the pointer.
Thanks.
You need to SubClass your NSScrollView . NSScrollView doesn't usually do any drawing, and probably has a weird interaction with its child views in that way. I'd suggest putting something like
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// We're going to be modifying the state for this,
// so allow it to be restored later
[NSGraphicsContext saveGraphicsState];
// Choose the correct color; isFirstResponder is a custom
// ivar set in becomeFirstResponder and resignFirstResponder
[[NSColor redColor]set];
// Create two rects, one slightly outset from the bounds,
// one slightly inset
NSRect bounds = [self bounds];
NSRect innerRect = NSInsetRect(bounds, 2, 2);
NSRect outerRect = NSMakeRect(bounds.origin.x - 2,
bounds.origin.y - 2,
bounds.size.width + 4,
bounds.size.height + 4);
// Create a bezier path using those two rects; this will
// become the clipping path of the context
NSBezierPath * clipPath = [NSBezierPath bezierPathWithRect:outerRect];
[clipPath appendBezierPath:[NSBezierPath bezierPathWithRect:innerRect]];
// Change the current clipping path of the context to
// the enclosed area of clipPath; "enclosed" defined by
// winding rule. Drawing will be restricted to this area.
// N.B. that the winding rule makes the order that the
// rects were added to the path important.
[clipPath setWindingRule:NSEvenOddWindingRule];
[clipPath setClip];
// Fill the rect; drawing is clipped and the inner rect
// is not drawn in
[[NSBezierPath bezierPathWithRect:outerRect] fill];
[NSGraphicsContext restoreGraphicsState];
}
Easy way to set border to scrollview
let scrollView = NSScrollView()
scrollView.contentView.wantsLayer = true
scrollView.contentView.layer?.borderColor = NSColor.black
scrollView.contentView.layer?.cornerRadius = 6

Why does a CATransform3DMakeRotation not take (0,0,1) as rotating around the Z-axis?

If I do a transform to an NSView in Cocoa's app:
self.view.layer.transform = CATransform3DMakeRotation(30 * M_PI / 180, 0, 0, 1);
I see the square not rotated around the Z-axis, but rotated as if that vector is pointing downward and outward. I need to make it
self.view.layer.transform = CATransform3DMakeRotation(30 * M_PI / 180, 1, 1, 1);
to make it rotate around the Z-axis, as shown in the picture on this question.
However, if I set an NSTimer and then update the transform in the update method, then using
-(void) update:(id) info {
self.view.layer.transform =
CATransform3DMakeRotation(self.degree * M_PI / 180, 0, 0, 1);
self.degree += 10;
}
(this time, using (0, 0, 1)) will work: it rotates around the Z-axis. I wonder why (1, 1, 1) is needed inside of applicationDidFinishLaunching , but (0, 0, 1) can be used in the update method?
It turns out the view needs to be added to window.contentView first:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self.view = [[NSView alloc] initWithFrame:NSMakeRect(100, 100, 200, 200)];
[self.window.contentView addSubview:self.view];
self.view.wantsLayer = YES;
self.view.layer = [CALayer layer];
self.view.layer.backgroundColor = [[NSColor yellowColor] CGColor];
self.view.layer.anchorPoint = CGPointMake(0.5, 0.5);
self.view.layer.transform = CATransform3DMakeRotation(30 * M_PI / 180, 0, 0, 1);
}
also, the line self.view.layer = [CALayer layer]; needs to be after the line self.view.wantsLayer = YES;. Why these order, I am not sure, but it works as it is supposed to. I can't find the docs that mentions the requirement for this order yet, but the code above works. I will update the answer when I find more info about why the order is needed.

Creating a UIImage from a rotated UIImageView

I have a UIImageView with an image in it. I have rotated the image prior to display by setting the transform property of the UIImageView to CGAffineTransformMakeRotation(angle) where angle is the angle in radians.
I want to be able to create another UIImage that corresponds to the rotated version that I can see in my view.
I am almost there, by rotating the image context I get a rotated image:
- (UIImage *) rotatedImageFromImageView: (UIImageView *) imageView
{
UIImage *rotatedImage;
// Get image width, height of the bounding rectangle
CGRect boundingRect = [self getBoundingRectAfterRotation: imageView.bounds byAngle:angle];
// Create a graphics context the size of the bounding rectangle
UIGraphicsBeginImageContext(boundingRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
// Rotate and translate the context
CGAffineTransform ourTransform = CGAffineTransformIdentity;
ourTransform = CGAffineTransformConcat(ourTransform, CGAffineTransformMakeRotation(angle));
CGContextConcatCTM(context, ourTransform);
// Draw the image into the context
CGContextDrawImage(context, CGRectMake(0, 0, imageView.image.size.width, imageView.image.size.height), imageView.image.CGImage);
// Get an image from the context
rotatedImage = [UIImage imageWithCGImage: CGBitmapContextCreateImage(context)];
// Clean up
UIGraphicsEndImageContext();
return rotatedImage;
}
However the image is not rotated about its centre. I have tried all kinds of transforms concatenated with my rotate to get it to rotate around the centre but to no avail. Am I missing a trick? Is this even possible since I am rotating the context not the image?
Getting desperate to make this work now, so any help would be appreciated.
Dave
EDIT: I've been asked several times for my boundingRect code, so here it is:
- (CGRect) getBoundingRectAfterRotation: (CGRect) rectangle byAngle: (CGFloat) angleOfRotation {
// Calculate the width and height of the bounding rectangle using basic trig
CGFloat newWidth = rectangle.size.width * fabs(cosf(angleOfRotation)) + rectangle.size.height * fabs(sinf(angleOfRotation));
CGFloat newHeight = rectangle.size.height * fabs(cosf(angleOfRotation)) + rectangle.size.width * fabs(sinf(angleOfRotation));
// Calculate the position of the origin
CGFloat newX = rectangle.origin.x + ((rectangle.size.width - newWidth) / 2);
CGFloat newY = rectangle.origin.y + ((rectangle.size.height - newHeight) / 2);
// Return the rectangle
return CGRectMake(newX, newY, newWidth, newHeight);
}
OK - at last I seem to have done it. Any comments on the correctness would be useful... needed a translate, a rotate, a scale and an offset from the drawing rect position to make it work. Code is here:
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, boundingRect.size.width/2, boundingRect.size.height/2);
transform = CGAffineTransformRotate(transform, angle);
transform = CGAffineTransformScale(transform, 1.0, -1.0);
CGContextConcatCTM(context, transform);
// Draw the image into the context
CGContextDrawImage(context, CGRectMake(-imageView.image.size.width/2, -imageView.image.size.height/2, imageView.image.size.width, imageView.image.size.height), imageView.image.CGImage);
// Get an image from the context
rotatedImage = [UIImage imageWithCGImage: CGBitmapContextCreateImage(context)];

Resources