In Paintcode 2, I have a circle inside a frame inside a canvas.
The constraints on the circle are set like this:
To get the circle to size up and not become an ellipse, I have to
Know the expected aspect ratio
Code it in Objective-C myself
Is there any way around this type of code?
-(void)drawRect:(CGRect)rect {
if (rect.size.width > rect.size.height) {
rect.origin.x = (rect.size.width - rect.size.height) * .5f;
rect.size.width = rect.size.height;
} else {
rect.origin.y = (rect.size.height - rect.size.width) * .5f;
rect.size.height = rect.size.width;
}
NSLog(#"Frame=%#", NSStringFromCGRect(rect));
[CircleDraw drawCircleWithFrame:rect];
}
Create a canvas, 150x120
Create an ellipse, 10, 10, 100, 100
Create a new variable called frame, type rectangle: 10, 10, 150, 100
Create a new expression called smallestSide: min(frame.height, frame.width)
Make an expression called position (below)
Drag smallestSide to both height and width of the ellipse
Drag the position to the position of the ellipse
position (Expression)
makePoint(
frame.x+(frame.width-smallestSide)*0.5,
frame.y+(frame.height-smallestSide)*0.5
)
output
- (void)drawCanvas1WithFrame: (CGRect)frame
{
//// Variable Declarations
CGFloat smallestSide = MIN(frame.size.height, frame.size.width);
CGPoint position = CGPointMake(frame.origin.x + (frame.size.width - smallestSide) * 0.5, frame.origin.y + (frame.size.height - smallestSide) * 0.5);
//// Oval Drawing
UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(position.x, position.y, smallestSide, smallestSide)];
[UIColor.grayColor setFill];
[ovalPath fill];
}
NOTE: I had the help of Matt Dunik at PaintCode to figure this out, but the solution is actually very straightforward.
Related
I'm new to iOS development and I'm struggling with porting some code from iOS6 involving the use of MKOverlay.
When the overlay radius or coordinate change, the renderer should update the display accordingly in real time.
This part works, but if I drag the overlay too much, it reaches some boundary and the rendering gets cut off. I can't find any documentation or help on this behavior.
In the CircleOverlayRenderer class:
- (id)initWithOverlay:(id<MKOverlay>)overlay
{
self = [super initWithOverlay:overlay];
if (self) {
CircleZone *bOverlay = (CircleZone *)overlay;
[RACObserve(bOverlay, coordinate) subscribeNext:^(id x) {
[self setNeedsDisplay];
}];
[RACObserve(bOverlay, radius) subscribeNext:^(id x) {
[self setNeedsDisplay];
}];
}
return self;
}
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context
{
CGRect rect = [self rectForMapRect:[self.overlay boundingMapRect]];
CGContextSaveGState(context);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextSetFillColorSpace(context, colorSpace);
CGColorSpaceRelease(colorSpace);
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGContextSetFillColor(context, color);
CGContextSetAllowsAntialiasing(context, YES);
// outline
{
CGContextSetAlpha(context, 0.8);
CGContextFillEllipseInRect(context, rect);
}
// red
{
CGContextSetAlpha(context, 0.5);
CGRect ellipseRect = CGRectInset(rect, 0.01 * rect.size.width / 2, 0.01 * rect.size.height / 2);
CGContextFillEllipseInRect(context, ellipseRect);
}
CGContextRestoreGState(cox);
}
In the CircleOverlay class:
- (MKMapRect)boundingMapRect
{
MKMapPoint center = MKMapPointForCoordinate(self.coordinate);
double mapPointsPerMeter = MKMapPointsPerMeterAtLatitude(self.coordinate.latitude);
double mapPointsRadius = _radius * mapPointsPerMeter;
return MKMapRectMake(center.x - mapPointsRadius, center.y - mapPointsRadius,
mapPointsRadius * 2.0, mapPointsRadius * 2.0);
}
Here are some screen shots of the problem I'm seeing:
Problem when dragging overlay too much:
Problem when changing the radius:
The problem does go away if I keep zooming the map out. After the map tiles refresh, the overlay no longer gets cut off...
If anyone had a similar problem, please help me, it's driving me crazy!
Looking at the radius example, it makes me suspect the boundingMapRect, given how its cropping. Looking at the boundingMapRect implementation, the reliance upon MKMapPointsPerMeterAtLatitude (esp when you're looking at a large region) is worrying. That function is useful if you are, for example, trying to figure out where a coordinate 10 meters from some other coordinate, but when looking at really large spans, it doesn't always work out well.
I might, instead, suggest something that gets the MKCoordinateRegion of where the circle is, and then convert that to MKMapRect. A simplistic implementation might look like:
- (MKMapRect)boundingMapRect {
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(self.coordinate, _radius * 2, _radius * 2);
CLLocationCoordinate2D upperLeftCoordinate = CLLocationCoordinate2DMake(region.center.latitude - region.span.latitudeDelta / 2, region.center.longitude - region.span.longitudeDelta / 2);
CLLocationCoordinate2D lowerRightCoordinate = CLLocationCoordinate2DMake(region.center.latitude + region.span.latitudeDelta / 2, region.center.longitude + region.span.longitudeDelta / 2);
MKMapPoint upperLeft = MKMapPointForCoordinate(upperLeftCoordinate);
MKMapPoint lowerRight = MKMapPointForCoordinate(lowerRightCoordinate);
return MKMapRectMake(MIN(upperLeft.x, lowerRight.x),
MIN(upperLeft.y, lowerRight.y),
ABS(upperLeft.x - lowerRight.x),
ABS(upperLeft.y - lowerRight.y));
}
You'll have to tweak with this to make sure it gracefully handles crossing of the 180th meridian and when the circle encompasses the north pole, but it illustrates the basic idea: Get MKCoordinateRegion for the circle and then convert that to MKMapRect.
I want to programatically crop a shape over my UIImageView. I know about creating a path with QuartzCore, but I don't understand context. Give me an example by subclassing UIImageView.
So how can I make an image go from this:
To this:
I also need the mask to be transparent
The easiest approach is to
create a UIBezierPath for the hexagon;
create a CAShapeLayer from that path; and
add that CAShapeLayer as a mask to the image view's layer.
Thus, it might look like:
CAShapeLayer *mask = [CAShapeLayer layer];
mask.path = [[self polygonPathWithRect:self.imageView.bounds lineWidth:0.0 sides:6] CGPath];
mask.strokeColor = [UIColor clearColor].CGColor;
mask.fillColor = [UIColor whiteColor].CGColor;
self.imageView.layer.mask = mask;
where
/** Create UIBezierPath for regular polygon inside a CGRect
*
* #param square The CGRect of the square in which the path should be created.
* #param lineWidth The width of the stroke around the polygon. The polygon will be inset such that the stroke stays within the above square.
* #param sides How many sides to the polygon (e.g. 6=hexagon; 8=octagon, etc.).
*
* #return UIBezierPath of the resulting polygon path.
*/
- (UIBezierPath *)polygonPathWithRect:(CGRect)square
lineWidth:(CGFloat)lineWidth
sides:(NSInteger)sides
{
UIBezierPath *path = [UIBezierPath bezierPath];
CGFloat theta = 2.0 * M_PI / sides; // how much to turn at every corner
CGFloat squareWidth = MIN(square.size.width, square.size.height); // width of the square
// calculate the length of the sides of the polygon
CGFloat length = squareWidth - lineWidth;
if (sides % 4 != 0) { // if not dealing with polygon which will be square with all sides ...
length = length * cosf(theta / 2.0); // ... offset it inside a circle inside the square
}
CGFloat sideLength = length * tanf(theta / 2.0);
// start drawing at `point` in lower right corner
CGPoint point = CGPointMake(squareWidth / 2.0 + sideLength / 2.0, squareWidth - (squareWidth - length) / 2.0);
CGFloat angle = M_PI;
[path moveToPoint:point];
// draw the sides and rounded corners of the polygon
for (NSInteger side = 0; side < sides; side++) {
point = CGPointMake(point.x + sideLength * cosf(angle), point.y + sideLength * sinf(angle));
[path addLineToPoint:point];
angle += theta;
}
[path closePath];
return path;
}
I posted another answer that illustrates the idea with rounded corners, too.
If you want to implement the addition of this mask as part of a UIImageView subclass, I'll leave that to you. But hopefully this illustrates the basic idea.
In my program I'm drawing some shapes inside an NSView using NSBezierPath. Everything works and looks great except when I resize the window.
Example
Initial drawing
Window resize
Does anyone know what I should use to prevent this square from being anchored to the initial position, but make it readjust relative the the initial scale.
Any help is appreciated!
If you are doing your drawing in drawRect: then the answer is NO. You will need to rebuild and reposition your path each time. What you can do is something along the following lines:
- (void) drawRect:(NSRect)dirtyRect
{
// Assuming that _relPos with x and y having values bewteen 0 and 1.
// To keep the square in the middle of the view you would set _relPos to
// CGPointMake(0.5, 0.5).
CGRect bounds = self.bounds;
CGRect rect;
rect.size.width = 100;
rect.size.height = 100;
rect.origin.x = bounds.origin.x + bounds.size.width * _relPos.x - rect.size.width /2;
rect.origin.y = bounds.origin.y + bounds.size.height * _relPos.y - rect.size.height/2;
NSBezierPath *path = [NSBezierPath bezierPathWithRect:rect];
[[NSColor redColor] set];
path.lineWidth = 2;
[path stroke];
}
I want to resize the standard IKImageBrowserCell highlight dimension. I found a similar question on stackoverflow here (at the link you can see the standard IKImageBrowserCell selection highlight).
I' ve found some apple sample code that customize completely the IKImageBrowserCell and also the highlight dimension. these are the two overidden methods that set the frames
- (NSRect) imageFrame
{
// //get default imageFrame and aspect ratio
NSRect imageFrame = [super imageFrame];
if(imageFrame.size.height == 0 || imageFrame.size.width == 0) return NSZeroRect;
float aspectRatio = imageFrame.size.width / imageFrame.size.height;
// compute the rectangle included in container with a margin of at least 10 pixel at the bottom, 5 pixel at the top and keep a correct aspect ratio
NSRect container = [self imageContainerFrame];
container = NSInsetRect(container, 8, 8);
if(container.size.height <= 0) return NSZeroRect;
float containerAspectRatio = container.size.width / container.size.height;
if(containerAspectRatio > aspectRatio){
imageFrame.size.height = container.size.height;
imageFrame.origin.y = container.origin.y;
imageFrame.size.width = imageFrame.size.height * aspectRatio;
imageFrame.origin.x = container.origin.x + (container.size.width - imageFrame.size.width)*0.5;
}
else{
imageFrame.size.width = container.size.width;
imageFrame.origin.x = container.origin.x;
imageFrame.size.height = imageFrame.size.width / aspectRatio;
imageFrame.origin.y = container.origin.y + container.size.height - imageFrame.size.height;
}
//round it
imageFrame.origin.x = floorf(imageFrame.origin.x);
imageFrame.origin.y = floorf(imageFrame.origin.y);
imageFrame.size.width = ceilf(imageFrame.size.width);
imageFrame.size.height = ceilf(imageFrame.size.height);
return imageFrame;
}
//---------------------------------------------------------------------------------
// imageContainerFrame
//
// override the default image container frame
//---------------------------------------------------------------------------------
- (NSRect) imageContainerFrame
{
NSRect container = [super frame];
//make the image container 15 pixels up
container.origin.y += 15;
container.size.height -= 15;
return container;
}
And this is the code that' s generates the selection highlight
/* selection layer */
if(type == IKImageBrowserCellSelectionLayer){
//create a selection layer
CALayer *selectionLayer = [CALayer layer];
selectionLayer.frame = CGRectMake(0, 0, frame.size.height, frame.size.height);
float fillComponents[4] = {1.0, 0, 0.5, 0.3};
float strokeComponents[4] = {1.0, 0.0, 0.5, 1};
//set a background color
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
color = CGColorCreate(colorSpace, fillComponents);
[selectionLayer setBackgroundColor:color];
CFRelease(color);
//set a border color
color = CGColorCreate(colorSpace, strokeComponents);
[selectionLayer setBorderColor:color];
CFRelease(color);
[selectionLayer setBorderWidth:2.0];
[selectionLayer setCornerRadius:5];
return selectionLayer;
}
this is the result of that code:
as you can see the frame of the selection is changed but I can' t modify it in the way I wan' t. I simply want to have the highlight as the same size as the image thumbnail. I' ve tried to modify the selection code (selectionLayer.frame = CGRectMake(0, 0, frame.size.height, frame.size.height);) but nothing happens. Anyone can help me? Thanks!
To custom your selection frame layout, you must override -[IKImageBrowserCell selectionFrame]
Here are the methods that you may want to override if you want to custom the layout of IKImageBrowserCell.
- (NSRect) imageContainerFrame;
- (NSRect) imageFrame;
- (NSRect) selectionFrame;
- (NSRect) titleFrame;
- (NSRect) subtitleFrame;
- (NSImageAlignment) imageAlignment;
Here are the methods that you may want to override if you want to custom the appearance of IKImageBrowserCell
- (CGFloat) opacity;
- (CALayer *) layerForType:(NSString *) type;
I wrote this class that draws a animated progress with a circle (it draws a circular sector based on a float progress)
#implementation MXMProgressView
#synthesize progress;
- (id)initWithDefaultSize {
int circleOffset = 45.0f;
self = [super initWithFrame:CGRectMake(0.0f,
0.0f,
135.0f + circleOffset,
135.0f + circleOffset)];
self.backgroundColor = [UIColor clearColor];
return self;
}
- (void)drawRect:(CGRect)rect {
CGRect allRect = self.bounds;
CGRect circleRect = CGRectMake(allRect.origin.x + 2, allRect.origin.y + 2, allRect.size.width - 4,
allRect.size.height - 4);
CGContextRef context = UIGraphicsGetCurrentContext();
// background image
//UIImage *image = [UIImage imageNamed:#"loader_disc_hover.png"];
//[image drawInRect:circleRect];
// Orange: E27006
CGContextSetRGBFillColor(context,
((CGFloat)0xE2/(CGFloat)0xFF),
((CGFloat)0x70/(CGFloat)0xFF),
((CGFloat)0x06/(CGFloat)0xFF),
0.01f); // fill
//CGContextSetLineWidth(context, 2.0);
CGContextFillEllipseInRect(context, circleRect);
//CGContextStrokeEllipseInRect(context, circleRect);
// Draw progress
float x = (allRect.size.width / 2);
float y = (allRect.size.height / 2);
// Orange: E27006
CGContextSetRGBFillColor(context,
((CGFloat)0xE2/(CGFloat)0xFF),
((CGFloat)0x70/(CGFloat)0xFF),
((CGFloat)0x06/(CGFloat)0xFF),
1.0f); // progress
CGContextMoveToPoint(context, x, y);
CGContextAddArc(context, x, y, (allRect.size.width - 4) / 2, -M_PI_2, (self.progress * 2 * M_PI) - M_PI_2, 0);
CGContextClosePath(context);
CGContextFillPath(context);
}
#end
Now what I want to do I to draw a ring shape with the same progress animation, instead of filling the full circle, so a circular sector again not starting from the center of the circle.
I tried with CGContextAddEllipseInRect and the CGContextEOFillPath(context);
with no success.
I think you'll need to construct a more complex path, something like:
// Move to start point of outer arc (which might not be required)
CGContextMoveToPoint(context, x+outerRadius*cos(startAngle), y+outerRadius*sin(startAngle));
// Add outer arc to path (counterclockwise)
CGContextAddArc(context, x, y, outerRadius, startAngle, endAngle, 0);
// move *inward* to start point of inner arc
CGContextMoveToPoint(context, x+innerRadius*cos(endAngle), y+innerRadius*sin(endAngle));
// Add inner arc to path (clockwise)
CGContextAddArc(context, x, y, innerRadius, endAngle, StartAngle, 1);
// Close the path from end of inner arc to start of outer arc
CGContextClosePath(context);
Note: I haven't tried the above code myself
Cheap and nasty solution:
Draw a solid circle that is smaller than the original circle by the thickness of the ring you want to draw.
Draw this circle on top of the original circle, all that you will see animating is the ring.