I'm trying to draw an E shaped thingy and as far as I know there are 2 ways to go one of them being path.
I could draw with path as the below documentation suggests. https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/BezierPaths/BezierPaths.html
Or I could create a rectangle with a UIView and carve it with 2 small squares and make an E by substracting those 2 spots.
I'm not sure which way is the way to go considering efficiency and everything. If there's any other better ways, please enlighten me. Thanks!
(I've found plenty about drawing shapes on here but none is even remotely recent and I'd like a recent answer)
One of few possible ways:
Create a subclass of UIView.
Set CAShapeLayer as a backing layer your UIView.
Configure UIBezierPath by adding lines to reflect your E-shape.
Assign UIBezierPath CGPath property to CAShapeLayer's path property.
Add your view to view hierarch.
Other:
Create a subclass of UIView.
Override drawRect method.
Add shapes/lines to drawing context to reflect your E-shape.
Add your view to view hierarch.
IMHO first solution will be bit more efficient, but I usually goes into second one.
If you don't want to use bezier path, you can draw the letter on view:
CGContextRef context = UIGraphicsGetCurrentContext();
// Text Drawing
CGRect textRect = CGRectMake(CGRectGetMinX(frame) + 52, CGRectGetMinY(frame) + 26, 17, 21);
{
NSString* textContent = #"E"; // You can draw any letter , just replace this!
NSMutableParagraphStyle* textStyle = NSMutableParagraphStyle.defaultParagraphStyle.mutableCopy;
textStyle.alignment = NSTextAlignmentCenter;
NSDictionary* textFontAttributes = #{NSFontAttributeName: [UIFont systemFontOfSize: UIFont.labelFontSize], NSForegroundColorAttributeName: UIColor.blackColor, NSParagraphStyleAttributeName: textStyle};
CGFloat textTextHeight = [textContent boundingRectWithSize: CGSizeMake(textRect.size.width, INFINITY) options: NSStringDrawingUsesLineFragmentOrigin attributes: textFontAttributes context: nil].size.height;
CGContextSaveGState(context);
CGContextClipToRect(context, textRect);
[textContent drawInRect: CGRectMake(CGRectGetMinX(textRect), CGRectGetMinY(textRect) + (CGRectGetHeight(textRect) - textTextHeight) / 2, CGRectGetWidth(textRect), textTextHeight) withAttributes: textFontAttributes];
CGContextRestoreGState(context);
}
Subclass the UIVIew and include this code in the drawRect method.
Related
I have one UIView which is drawn with:
- (void)drawRect:(CGRect)rect
{
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, self.widthOfLine, self.heightOfLine)];
[bezierPath fill];
}
and whose frame is centered within the iOS window. I want to place a bunch of smaller (5px by 5px) views within this view randomly, which I am doing fine with arc4random() function and drawing the views. However, I can't seem to figure out how to cut off all views outside the bezierPathWithOvalInRect, it will just display them everywhere in the first UIView's frame. Anyone know what to do for this?
edit:
For clarity, I have defined another view as:
- (void)drawRect:(CGRect)rect
{
UIBezierPath *drawPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(self.xPosition, self.yPosition, self.width, self.width)];
[[UIColor whiteColor] setFill];
[drawPath fill];
}
and I am adding a large number of these to the first view as subviews, but I want them to not be displayed outside the oval bezier path. Is there a way to a) only add them within the oval bezier path as opposed to within the entire frame, or b) a way to clip all that are outside the oval from the view?
In order to clip all subviews to a particular UIBezierPath, you'll want to set the mask property of the view's layer property. If you set the layer.mask to a CAShapeLayer of that path, then all subviews will be cropped to that path.
I use the following code to do just that in my app:
// create your view that'll hold all the subviews that
// need to be clipped to the path
CGRect anyRect = ...;
UIView* clippedView = [[UIView alloc] initWithFrame:anyRect];
clippedView.clipsToBounds = YES;
// now create the shape layer that we'll use to clip
CAShapeLayer* maskingLayer = [CAShapeLayer layer];
[maskingLayer setPath:bezierPath.CGPath];
maskingLayer.frame = backingImageHolder.bounds;
// set the mask property of the layer, and this will
// make sure that all subviews are only visible inside
// this path
clippedView.layer.mask = maskingLayer;
// now any subviews you add won't show outside of that path
UIView* anySubview = ...;
[clippedView addSubview:anySubview];
I am rewriting some of my graphics drawing code from SKShapeNodes to CGContext/CALayers. I am trying to draw a curve with glow in CGContext. This is what I used to have in SpriteKit:
CGPathRef path = …(some path)
SKShapeNode *node = [SKShapeNode node];
node.path = path;
node.glowWidth = 60;
After adding it to the scene with dark-grey background, the result was as follows:
Is it possible to draw line with such glow using CGContext but without using CIFilters? Normally I will be drawing over an non-blank context background, so I prefer not to use CIFilters after the line was drawn.
I have already tried the "shadow" solution, but the results are far from perfect:
UIGraphicsBeginImageContextWithOptions(frame.size, NO, 1);
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat glowWidth = 60.0;
CGContextSetShadowWithColor(context, CGSizeMake(0.0, 0.0), glowWidth, [UIColor whiteColor].CGColor);
CGContextBeginPath(context);
CGContextAddPath(context, path);
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextStrokePath(context);
Result (the shadow is hardly visible):
Please let me know if you have some ideas.
You can create a SKEffectNode which allows you to apply Core Image filters to all of its children. In other words, you can create a SKEffectNode then add a flower sprite as a child and the SKEffectNode would apply the Core Image effect to the flower sprite.
For more detailed information, please see the SKEffectNode Class Reference.
How can I draw this style of text in Cocoa (OS X)? It seems to be used in several Apple apps including Mail (as pictured above) and several places in Xcode sidebars. I've looked around but haven't been able to find any resources suggesting how to reproduce this specific style of text. It looks like an inset shadow and my first guess was to try using an NSShadow with the blur radius set to a negative value but apparently only positive values are allowed. Any ideas?
I have some code that draws an embossed cell (originally written Jonathon Mah, I believe). It might not do exactly what you want but it'll give you a place to start:
#implementation DMEmbossedTextFieldCell
#pragma mark NSCell
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
/* This method copies the three-layer method used by Safari's error page. That's accessible by forcing an
* error (e.g. visiting <foo://>) and opening the web inspector. */
// I tried to use NSBaselineOffsetAttributeName instead of shifting the frame, but that didn't seem to work
const NSRect onePixelUpFrame = NSOffsetRect(cellFrame, 0.0, [NSGraphicsContext currentContext].isFlipped ? -1.0 : 1.0);
const NSRange fullRange = NSMakeRange(0, self.attributedStringValue.length);
NSMutableAttributedString *scratchString = [self.attributedStringValue mutableCopy];
BOOL overDark = (self.backgroundStyle == NSBackgroundStyleDark);
CGFloat (^whenLight)(CGFloat) = ^(CGFloat b) { return overDark ? 1.0 - b : b; };
// Layer 1
[scratchString addAttribute:NSForegroundColorAttributeName value:[NSColor colorWithCalibratedWhite:whenLight(1.0) alpha:1.0] range:fullRange];
[scratchString drawInRect:cellFrame];
// Layer 2
BOOL useGradient = NO; // Safari 5.2 preview has switched to a lighter, solid color look for the detail text. Since we use the same class, use bold-ness to decide
if (self.attributedStringValue.length > 0) {
NSFont *font = [self.attributedStringValue attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL];
if ([[NSFontManager sharedFontManager] traitsOfFont:font] & NSBoldFontMask)
useGradient = YES;
}
NSColor *solidShade = [NSColor colorWithCalibratedHue:200/360.0 saturation:0.03 brightness:whenLight(0.41) alpha:1.0];
[scratchString addAttribute:NSForegroundColorAttributeName value:solidShade range:fullRange];
[scratchString drawInRect:onePixelUpFrame];
// Layer 3 (Safari uses alpha of 0.25)
[scratchString addAttribute:NSForegroundColorAttributeName value:[NSColor colorWithCalibratedWhite:whenLight(1.0) alpha:0.25] range:fullRange];
[scratchString drawInRect:cellFrame];
}
#end
Please don't pick this as the answer I just implemented the above suggestions for fun and put it here because it will probably be useful to someone in the future!
https://github.com/danieljfarrell/InnerShadowTextFieldCell
Following from the advice of indragie and wil-shipley here is a subclass of NSTextFieldCell that draws the text with an inner shadow.
The header file,
// InnerShadowTextFieldCell.h
#import <Cocoa/Cocoa.h>
#interface InnerShadowTextFieldCell : NSTextFieldCell
#property (strong) NSShadow *innerShadow;
#end
Now the implementation file,
// InnerShadowTextFieldCell.m
#import "InnerShadowTextFieldCell.h"
// This class needs the NSString category -bezierWithFont: from,
// https://developer.apple.com/library/mac/samplecode/SpeedometerView/Listings/SpeedyCategories_m.html
#implementation InnerShadowTextFieldCell
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
//// Shadow Declarations
if (_innerShadow == nil) {
/* Inner shadow has not been set, override here with default shadow.
You may or may not want this behaviour. */
_innerShadow = [[NSShadow alloc] init];
[_innerShadow setShadowColor: [NSColor darkGrayColor]];
[_innerShadow setShadowOffset: NSMakeSize(0.1, 0.1)];
/* Trying to find a default shadow radius which will look good for
a label of any size, let's get a rough estimate based on the
hypotenuse of the cell frame. */
[_innerShadow setShadowBlurRadius: 0.0075 * hypot(NSWidth(cellFrame), NSHeight(cellFrame)) ];
}
/* Because we are using the -bezierWithFont: we get a slightly
different path than had we used the superclass to drawn the
text path. This means that the background colour and text
colour looks odd if we use call the superclass's,
-drawInteriorWithFrame:inView: method let's do that
drawing here. Not making the call to super might cause
problems for general use (?) but for a simple label is seems
to work OK */
[self.backgroundColor setFill];
NSRectFill(cellFrame);
NSBezierPath *bezierPath = [self.title bezierWithFont:self.font];
[self.textColor setFill];
[bezierPath fill];
/* The following is inner shadow drawing method is taken from Paint Code */
////// Bezier Inner Shadow
NSShadow *shadow = _innerShadow;
NSRect bezierBorderRect = NSInsetRect([bezierPath bounds], -shadow.shadowBlurRadius, -shadow.shadowBlurRadius);
bezierBorderRect = NSOffsetRect(bezierBorderRect, -shadow.shadowOffset.width, shadow.shadowOffset.height);
bezierBorderRect = NSInsetRect(NSUnionRect(bezierBorderRect, [bezierPath bounds]), -1, -1);
NSBezierPath* bezierNegativePath = [NSBezierPath bezierPathWithRect: bezierBorderRect];
[bezierNegativePath appendBezierPath: bezierPath];
[bezierNegativePath setWindingRule: NSEvenOddWindingRule];
[NSGraphicsContext saveGraphicsState];
{
NSShadow* shadowWithOffset = [shadow copy];
CGFloat xOffset = shadowWithOffset.shadowOffset.width + round(bezierBorderRect.size.width);
CGFloat yOffset = shadowWithOffset.shadowOffset.height;
shadowWithOffset.shadowOffset = NSMakeSize(xOffset + copysign(0.0, xOffset), yOffset + copysign(0.1, yOffset));
[shadowWithOffset set];
[[NSColor grayColor] setFill];
[bezierPath addClip];
NSAffineTransform* transform = [NSAffineTransform transform];
[transform translateXBy: -round(bezierBorderRect.size.width) yBy: 0];
[[transform transformBezierPath: bezierNegativePath] fill];
}
[NSGraphicsContext restoreGraphicsState];
}
#end
This could probably be made more robust but it seems fine for just drawing static labels.
Make sure your change the text color and text background color properties in Interface Builder otherwise you will not be able to see the shadow.
From your screenshot, that looks like text drawn with an inner shadow. Hence, the standard NSShadow method of using a blur radius of 0 won't work because that only draws the shadow under/above the text.
There are two steps to drawing text with an inner shadow.
1. Get the drawing path of the text
To be able to draw a shadow inside the text glyphs, you need to create a bezier path from the string. The Apple sample code SpeedometerView has a category that adds the method -bezierWithFont: to NSString. Run the project to see how this method is used.
2. Fill the path with an inner shadow
Drawing shadows under bezier paths is easy, but drawing a shadow inside one is not trivial. Fortunately, the NSBezierPath+MCAdditions category adds the -[NSBezierPath fillWithInnerShadow:] method to make this easy.
I've finally got my main app release (Tap Play MMO - check it out ;-) ) and I'm now working on expanding it.
To do this I need to have a circle that has four seperate buttons in it, these buttons will essentially be quarters. I've come to the conclusion that the circlular image will need to be constructed of four images, one for each quarter, but due to the necessity of rectangular image shapes I'm going to end up with some overlap, although the overlap will be transparent.
What's the best way of getting this to work? I need something really simple really, I've looked at this
http://iphonedevelopment.blogspot.com/2010/03/irregularly-shaped-uibuttons.html
Before but not yet succeeded in getting it to work. Anyone able to offer some advice?
In case it makes any difference I'll be deploying to a iOS 3.X framework (will be 4.2 down the line when 4.2 comes out for iPad)
Skip the buttons and simply respond to touches in your view that contains the circle.
Create a CGPath for each area that you want to capture touches, when your UIview receives a touch, check for membership inside the paths.
[Edited answer to show skeleton implementation details -- TomH]
Here's how I would approach the problem: (I haven't tested this code and the syntax may not be quite right, but this is the general idea)
1) Using PS or your favorite image creation application, create one png of the quarter circles. Add it to your XCode project.
2) Add a UIView to the UI. Set the UIView's layer's contents to the png.
self.myView = [[UIView alloc] initWithRect:CGRectMake(10.0, 10.0, 100.0, 100,0)];
[myView.layer setContents:(id)[UIImage loadImageNamed:#"my.png"]];
3) Create CGPaths that describe the region in the UIView that you are interested in.
self.quadrantOnePath = CGPathCreateMutable();
CGPathMoveToPoint(self.quadrantOnePath, NULL, 50.0, 50.0);
CGPathAddLineToPoint(self.quadrantOnePath, NULL, 100.0, 50.0);
CGPathAddArc(self.quadrantOnePath, NULL, 50.0, 50.0, 50.0, 0.0, M_PI2, 1);
CGPathCloseSubpath(self.quadrantOnePath);
// create paths for the other 3 circle quadrants too!
4) Add a UIGestureRecognizer and listen/observe for taps in the view
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
[tapRecognizer setNumberOfTapsRequired:2]; // default is 1
5) When tapRecognizer invokes its target selector
- (void)handleGesture:(UIGestureRecognizer *) recognizer {
CGPoint touchPoint = [recognizer locationOfTouch:0 inView:self.myView];
bool processTouch = CGPathContainsPoint(self.quadrantOnePath, NULL, touchPoint, true);
if(processTouch) {
// call your method to process the touch
}
}
Don't forget to release everything when appropriate -- use CGPathRelease to release paths.
Another thought: If the graphic that you are using to represent your circle quadrants is simply a filled color (i.e. no fancy graphics, layer effects, etc.), you could also use the paths you created in the UIView's drawRect method to draw the quadrants too. This would address one of the failings of the approach above: there isn't a tight integration between the graphic and the paths used to check for the touches. That is, if you swap out the graphic for something different, change the size of the graphic, etc., your paths used to check for touches will be out of sync. Potentially a high maintenance piece of code.
I can't see, why overlapping is needed.
Just create 4 buttons and give each one a slice of your image.
edit after comment
see this great project. One example is exactly what you want to do.
It works by incorporating the alpha-value of a pixel in the overwritten
pointInside:withEvent: and a category on UIImage, that adds this method
- (UIColor *)colorAtPixel:(CGPoint)point {
// Cancel if point is outside image coordinates
if (!CGRectContainsPoint(CGRectMake(0.0f, 0.0f, self.size.width, self.size.height), point)) {
return nil;
}
// Create a 1x1 pixel byte array and bitmap context to draw the pixel into.
// Reference: http://stackoverflow.com/questions/1042830/retrieving-a-pixel-alpha-value-for-a-uiimage
NSInteger pointX = trunc(point.x);
NSInteger pointY = trunc(point.y);
CGImageRef cgImage = self.CGImage;
NSUInteger width = self.size.width;
NSUInteger height = self.size.height;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
int bytesPerPixel = 4;
int bytesPerRow = bytesPerPixel * 1;
NSUInteger bitsPerComponent = 8;
unsigned char pixelData[4] = { 0, 0, 0, 0 };
CGContextRef context = CGBitmapContextCreate(pixelData,
1,
1,
bitsPerComponent,
bytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextSetBlendMode(context, kCGBlendModeCopy);
// Draw the pixel we are interested in onto the bitmap context
CGContextTranslateCTM(context, -pointX, pointY-(CGFloat)height);
CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, (CGFloat)width, (CGFloat)height), cgImage);
CGContextRelease(context);
// Convert color values [0..255] to floats [0.0..1.0]
CGFloat red = (CGFloat)pixelData[0] / 255.0f;
CGFloat green = (CGFloat)pixelData[1] / 255.0f;
CGFloat blue = (CGFloat)pixelData[2] / 255.0f;
CGFloat alpha = (CGFloat)pixelData[3] / 255.0f;
return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
}
Here's an awesome project that solves the problem of irregular shaped buttons so easily:
http://christinemorris.com/2011/06/ios-irregular-shaped-buttons/
Can anyone guide me in the correct way to build a colored bubble/circle programmatically?
I can't use images as I need it to be able to be any color depending on user interaction.
My thought was maybe to make a white circle image and then overlay a color on top of it.
However I am not sure if this would work, or how to really go about it.
If someone could point me the right direction I would appreciate it.
There are a couple steps to drawing something in Cocoa.
First you need a path that will be used to define the object that you are going to be drawing. Take a look here Drawing Fundamental Shapes for a guide on creating paths in Cocoa. You will be most interested in sending the "appendBezierPathWithOvalInRect" message to an "NSBezierPath" object, this takes a rectangle that bounds the circle you want to draw.
This code will create a 10x10 circle at coordinates 10,10:
NSRect rect = NSMakeRect(10, 10, 10, 10);
NSBezierPath* circlePath = [NSBezierPath bezierPath];
[circlePath appendBezierPathWithOvalInRect: rect];
Once you have your path you want to set the color for the current drawing context. There are two colors, stroke and fill; stroke is the outline of the path and the fill is the interior color. To set a color you send "set" to an "NSColor" object.
This sets the stroke to black and the fill to red:
[[NSColor blackColor] setStroke];
[[NSColor redColor] setFill];
Now that you have your path and you have your colors set just fill the path and then draw it:
[path stroke];
[path fill];
All of this will need to be done in a graphics context like in drawRect of a view perhaps. All of this together with a graphics context would look like this:
- (void)drawRect:(NSRect)rect
{
// Get the graphics context that we are currently executing under
NSGraphicsContext* gc = [NSGraphicsContext currentContext];
// Save the current graphics context settings
[gc saveGraphicsState];
// Set the color in the current graphics context for future draw operations
[[NSColor blackColor] setStroke];
[[NSColor redColor] setFill];
// Create our circle path
NSRect rect = NSMakeRect(10, 10, 10, 10);
NSBezierPath* circlePath = [NSBezierPath bezierPath];
[circlePath appendBezierPathWithOvalInRect: rect];
// Outline and fill the path
[circlePath stroke];
[circlePath fill];
// Restore the context to what it was before we messed with it
[gc restoreGraphicsState];
}
You may use simple UIView to create perfect circle with only parameter radius:
// Add framework CoreGraphics.framework
#import <QuartzCore/QuartzCore.h>
-(UIView *)circleWithColor:(UIColor *)color radius:(int)radius {
UIView *circle = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 2 * radius, 2 * radius)];
circle.backgroundColor = color;
circle.layer.cornerRadius = radius;
circle.layer.masksToBounds = YES;
return circle;
}
Create an NSView subclass that holds an NSColor as an ivar. In the drawRect method, create an NSBezierPath of the appropriate size, using the view's bounds. Then set the color [myColor set] and fill the path [myPath fill]. There's a lot more you can do, such as set transparency, a border, and so on and so on, but I'll leave that to the docs unless you have a specific question.
To use the NSView subclass, just drag a view object onto your nib, and choose the name of your subclass in custom class in IB's inspector. You'll need to also set an outlet to it in your controller, so you can change the color as needed.
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(c, 40, 0, 255, 0.1);
CGContextSetRGBStrokeColor(c, 0, 40, 255, 0.5);
// Draw a green solid circle
CGContextSetRGBFillColor(c, 0, 255, 0, 1);
CGContextFillEllipseInRect(c, CGRectMake(100, 100, 25, 25));
Download sketch from apple. http://developer.apple.com/library/mac/#samplecode/Sketch
It can do a lot more, but one of the things is draw circles.