NSImageView rounded corners + stroke - objective-c

I have subclasses NSImageView and i want to draw a border around with rounded corners. It works but i need to clip off the image corners as well.
Please see my screenshot:
I have created this code to draw the border/corners.
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
NSColor *strokeColor;
if(self.isSelected)
strokeColor = [NSColor colorFromHexRGB:#"f9eca2"];
else
strokeColor = [NSColor colorFromHexRGB:#"000000"];
[strokeColor set];
[[NSBezierPath bezierPathWithRoundedRect:NSInsetRect(dirtyRect, 1, 1) xRadius:5 yRadius:5] stroke];
}
What should i do to make the image clip ?
EDIT:
Well i fixed it, but i feel its an ugly way to do it. Anything smarter?
NEW CODE:
- (void)drawRect:(NSRect)dirtyRect
{
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(dirtyRect, 2, 2) xRadius:5 yRadius:5];
[path setLineWidth:4.0];
[path addClip];
[self.image drawAtPoint: NSZeroPoint
fromRect:dirtyRect
operation:NSCompositeSourceOver
fraction: 1.0];
[super drawRect:dirtyRect];
NSColor *strokeColor;
if(self.isSelected)
{
strokeColor = [NSColor colorFromHexRGB:#"f9eca2"];
}
else
strokeColor = [NSColor colorFromHexRGB:#"000000"];
[strokeColor set];
[NSBezierPath setDefaultLineWidth:4.0];
[[NSBezierPath bezierPathWithRoundedRect:NSInsetRect(dirtyRect, 2, 2) xRadius:5 yRadius:5] stroke];
}

Set the corner radius of your NSImageViews layer also to 5 px and set its maskToBounds property to YES.

updated answer for XCode 8.3 and Swift 3.1
import Cocoa
class SomeViewController: NSViewController {
#IBOutlet weak var artwork: NSImageView!
override func viewDidLoad() {
super.viewDidLoad()
artwork.wantsLayer = true // Use a layer as backing store for this view
artwork.canDrawSubviewsIntoLayer = true // Important, flatten all subviews into layer
artwork.layer?.cornerRadius = 4.0
artwork.layer?.masksToBounds = true // Mask layer
}
}

Related

UILabel inside UIView produces rounded corner with "square corner"

I have an UIView which I created and set background color to white. This view contains UILabel, which is a class called BubbleView. (Sorry I cannot add a picture because you need reputation 10+ :(
PROBLEM:
1. The following code produces a gray Label with rounded corner with gray-border square corner tips. This is because the UIView produces the square corner tips. The UILabel is rounded. Please note that I already set the background of UIView to white.
2. My text string of the UILabel is hiding behind UIView, so it is not displayed.
I'd love to show you pictures, but I am new and I cannot add pictures until I get to 10+ reputations.
http://i.stack.imgur.com/CdRjy.png
http://i.stack.imgur.com/zCdCV.png
Here is my code for setting the text and the view:
BubbleView:
- (void)drawRect:(CGRect)rect
{
const CGFloat boxWidth = self.bubbleWidth;
const CGFloat boxHeight = self.bubbleHeight;
NSLog(#"text, width, height: %#, %f, %f", self.text, self.bubbleWidth, self.bubbleHeight);
CGRect boxRect = CGRectMake(
roundf(self.bounds.size.width - boxWidth) / 2.0f,
roundf(self.bounds.size.height - boxHeight) / 2.0f,
boxWidth,
boxHeight);
UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:boxRect cornerRadius:14.0f];
[[UIColor colorWithWhite:0.3f alpha:0.8f] setFill];
[roundedRect fill];
NSDictionary *attributes = #{
NSFontAttributeName : [UIFont systemFontOfSize:16.0f],
NSForegroundColorAttributeName : [UIColor whiteColor]
};
CGPoint textPoint = CGPointMake(
self.center.x+boxWidth/2,
self.center.y+boxHeight/2);
NSLog(#"text point origin: %f, %f", textPoint.x, textPoint.y);
[self.text drawAtPoint:textPoint withAttributes:attributes];
}
Main View Controller:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor];
[self setText];
}
-(void) viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self setText];
}
- (void) setText
{
NSString *textR = #"I need this text to show up on autolayout so that i could continue working";
UIFont* font = [UIFont fontWithName:#"HelveticaNeue" size:14.0f];
CGSize constraint = CGSizeMake(250,9999);
CGRect textRect = [textR boundingRectWithSize:constraint
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:font}
context:nil];
BubbleView *hostView = [[BubbleView alloc] initWithFrame:CGRectMake(20.0f, 160.0f, textRect.size.width+20, textRect.size.height+20)];
hostView.bubbleWidth = textRect.size.width+20;
hostView.bubbleHeight = textRect.size.height+20;
hostView.text = textR;
hostView.layer.cornerRadius = 10.0f;
self.view.layer.masksToBounds = TRUE;
[hostView drawRect:textRect];
hostView.backgroundColor = [UIColor whiteColor];
self.detailsView = hostView;
//self.detailsView.backgroundColor = [UIColor whiteColor];
NSLog(#"size: %f, %f", textRect.size.width, textRect.size.height);
NSLog(#"origin: %f, %f - size: %f, %f, backgroundColor: #%#", self.detailsView.frame.origin.x, self.detailsView.frame.origin.y, self.detailsView.frame.size.width, self.detailsView.frame.size.height, self.detailsView.backgroundColor);
[self.view addSubview:self.detailsView];
self.hostSays.text = textR;
self.hostSays.textColor = [UIColor blueColor];
[self.view layoutSubviews];
}
SOLUTION (ONLY 1 PART):
OK so I managed to solve half of my problems. I had to add the following code in my BubbleView class (inside - (id)initWithFrame:(CGRect)frame). This got rid of the square angles! (I think Wain was the one who suggested this but I might've misunderstood him)...
[self setOpaque:NO];
[self setBackgroundColor:[UIColor clearColor]];
So...still have the other part 2 of problem to solve and I'm hoping someone has run into this issue before!
Set the background colour of the view and add the label as a subview. Set the frame to get your required padding. Do not implement drawRect.
Now, the view will draw the background colour and the label automatically and the label will draw the text (with its background colour and border settings).
I know when I create custom buttons I need to setMasksToBounds
+ (void) addBorderToButtons:(UIButton *) btn
{
// Round button corners
CALayer *btnLayer = [btn layer];
[btnLayer setMasksToBounds:YES];
[btnLayer setCornerRadius:15.0f];
// Apply a 1 pixel, black border around Buy Button
[btnLayer setBorderWidth:1.5f];
[btnLayer setBorderColor:[[UIColor blackColor] CGColor]];
}
Setting this changes
To this
If you want to save your coding approach you strongly need to add [super drawRect:rect] in your drawRect: method
- (void)drawRect:(CGRect)rect{
[super drawRect:rect];
YOUR CODE
}
In this case you will see your text in UILabel.
Also you should not call drawRect: directly. It will be called automatically in runtime:
BubbleView *hostView =
[[BubbleView alloc] initWithFrame:CGRectMake(20.0f,
160.0f,
textRect.size.width+20,
textRect.size.height+20)];
hostView.bubbleWidth = textRect.size.width+20;
hostView.bubbleHeight = textRect.size.height+20;
hostView.text = textR;
// hostView.layer.cornerRadius = 10.0f;
// self.view.layer.masksToBounds = TRUE;
// [hostView drawRect:textRect];
// hostView.backgroundColor = [UIColor whiteColor];
self.detailsView = hostView;

Objective-C tiled background image in NSview

I have 2 NSViews arranged side by side that have backgrounds made from tiled images.
When I add constraints to the views so that they resize with the main window the tiled background image no longer displays and the background is just black.
What am I missing here?
#import "imageWellGraphics.h"
#implementation imageWellGraphics
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
CGAffineTransform affineTransform = CGContextGetCTM(context);
NSImage* image = [NSImage imageNamed: #"tile.png"];
NSColor* imagePattern = [NSColor colorWithPatternImage: image];
NSRect frame = NSInsetRect(self.bounds, 1, 1);
NSBezierPath* rectanglePath = [NSBezierPath bezierPathWithRect:NSMakeRect(NSMinX(frame), NSMinY(frame), NSWidth(frame), NSHeight(frame))];
[NSGraphicsContext saveGraphicsState];
CGContextSetPatternPhase(context, NSMakeSize(affineTransform.tx, affineTransform.ty));
[imagePattern setFill];
[rectanglePath fill];
[NSGraphicsContext restoreGraphicsState];
}
#end
Include this line inside drawrect method and check:-
[super drawRect:dirtyRect];
Well, it turns out the problem was me not properly understanding/using Images.xcassets for my background image.

NSImage colorWithPatternImage: how to tile images from the top instead?

The NSImage colorWithPatternImage method tiles images from the bottom. Can can I tiles them from the top instead ?
+ (NSColor *)colorWithPatternImage:(NSImage *)image Parameters image
The image to use as the pattern for the color object. The image is
tiled starting at the bottom of the window. The image is not scaled.
This is my code in my subclass of IKImageBrowserView, which is the view inside NSScrollView in my app.
- (void)drawRect:(NSRect)rect
{
NSImage * backgroundImage = [NSImage imageNamed:#"deliciousLibrary.png"];
[backgroundImage setSize:NSMakeSize(459 * bgImageSize, 91 * bgImageSize)];
[[NSColor colorWithPatternImage:backgroundImage] set];
NSRectFill(rect);
[super drawRect:rect];
}
thanks
UPDATE:
I've found on the internet this solution. But in my case it doesn't work because the image is not scrolling anymore in my NSScrollView.
- (void)drawRect:(NSRect)dirtyRect {
NSGraphicsContext* theContext = [NSGraphicsContext currentContext];
[theContext saveGraphicsState];
[[NSGraphicsContext currentContext] setPatternPhase:NSMakePoint(0,[self frame].size.height)];
[self.customBackgroundColour set];
NSRectFill([self bounds]);
[theContext restoreGraphicsState];
}
This is the solution to my problem. Amazingly simple.
float yOffset = NSMaxY([self convertRect:[self frame] toView:nil]);
[[NSGraphicsContext currentContext] setPatternPhase:NSMakePoint(0,yOffset)];
Here's the accepted answer in Swift 5:
let yOffset = NSMaxY(self.convert(self.frame, to: nil))
NSGraphicsContext.current?.patternPhase = NSMakePoint(0, yOffset)
...and a simple example implementation:
class MyPatternedView: NSView {
override func draw(_ dirtyRect: NSRect) {
// Make the pattern tile repeat from the top of the view.
let yOffset = NSMaxY(self.convert(self.frame, to: nil))
NSGraphicsContext.current?.patternPhase = NSMakePoint(0, yOffset)
if let image = NSImage(named: "Bricks") {
let color = NSColor.init(patternImage: image)
color.setFill()
dirtyRect.fill()
}
super.draw(dirtyRect)
}
}

Custom NSTextView border issues

I want to make NSTextView dot border, my drawRect: code is below
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
CGFloat lineDash[2];
lineDash[0] = 1.0;
lineDash[1] = 1.0;
NSBezierPath *path = [NSBezierPath bezierPathWithRect:self.bounds];
[path setLineDash:lineDash count:2 phase:0.0];
[path stroke];
}
I alse want to give some margin between text and border, my code is below
[textView setTextContainerInset:NSMakeSize(0, 10.0)];
[textView setString:#"This is a testThis is a testThis is a testThis is a test"];
But the result is that the top border is missing, who knows how to fix this?
You need to subclass NSScrollView instead of NSTextView. And then you will have a good performance. It can be done like this:
NSScrollView subclass:
-(void)tile {
id contentView = [self contentView];
[super tile];
[contentView setFrame:NSInsetRect([contentView frame], 1.0, 1.0)];
}
-(void)drawRect:(NSRect)dirtyRect {
CGFloat lineDash[2];
lineDash[0] = 1.5;
lineDash[1] = 1.5;
NSBezierPath *path = [NSBezierPath bezierPathWithRect:self.bounds];
[path setLineDash:lineDash count:2 phase:0.0];
[path setLineWidth:2];
[path stroke];
}
Result:

NSTextField with rounded corners?

I'm trying to draw rounded corners around an NSTextField.
I've subclassed NSTextField, tried the code below, but without any result...
Any ideas?
- (void)drawRect:(NSRect)dirtyRect
{
// black outline
NSRect blackOutlineFrame = NSMakeRect(0.0, 0.0, [self bounds].size.width, [self bounds].size.height-1.0);
NSGradient *gradient = nil;
if ([NSApp isActive]) {
gradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedWhite:0.24 alpha:1.0] endingColor:[NSColor colorWithCalibratedWhite:0.374 alpha:1.0]];
}
else {
gradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedWhite:0.55 alpha:1.0] endingColor:[NSColor colorWithCalibratedWhite:0.558 alpha:1.0]];
}
[gradient drawInBezierPath:[NSBezierPath bezierPathWithRoundedRect:blackOutlineFrame xRadius:5 yRadius:5] angle:90];
}
It is better to subclass NSTextFieldCell to draw rounded corners to preserve NSTextField functionality, e.g:
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
NSBezierPath *betterBounds = [NSBezierPath bezierPathWithRoundedRect:cellFrame xRadius:CORNER_RADIUS yRadius:CORNER_RADIUS];
[betterBounds addClip];
[super drawWithFrame:cellFrame inView:controlView];
if (self.isBezeled) {
[betterBounds setLineWidth:2];
[[NSColor colorWithCalibratedRed:0.510 green:0.643 blue:0.804 alpha:1] setStroke];
[betterBounds stroke];
}
}
Yields a nice rounded text field that works perfectly (if you had set it to draw a rectangle bezel in the first place, at least):
You are doing almost everything correct. You just need to change the textField's cell and radius which match. Take a look at this:
-(void)awakeFromNib {
[[self cell] setBezelStyle: NSTextFieldRoundedBezel];
}
- (void)drawRect:(NSRect)dirtyRect
{
NSRect blackOutlineFrame = NSMakeRect(0.0, 0.0, [self bounds].size.width, [self bounds].size.height-1.0);
NSGradient *gradient = nil;
if ([NSApp isActive]) {
gradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedWhite:0.24 alpha:1.0] endingColor:[NSColor colorWithCalibratedWhite:0.374 alpha:1.0]];
}
else {
gradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedWhite:0.55 alpha:1.0] endingColor:[NSColor colorWithCalibratedWhite:0.558 alpha:1.0]];
}
[gradient drawInBezierPath:[NSBezierPath bezierPathWithRoundedRect:blackOutlineFrame xRadius:10 yRadius:10] angle:90];
}
This is working for me nicely.
Swift 3 version of #Verious codes, with properties editable in Interface Builder:
class RoundedTextFieldCell: NSTextFieldCell {
#IBInspectable var borderColor: NSColor = .clear
#IBInspectable var cornerRadius: CGFloat = 3
override func draw(withFrame cellFrame: NSRect, in controlView: NSView) {
let bounds = NSBezierPath(roundedRect: cellFrame, xRadius: cornerRadius, yRadius: cornerRadius)
bounds.addClip()
super.draw(withFrame: cellFrame, in: controlView)
if borderColor != .clear {
bounds.lineWidth = 2
borderColor.setStroke()
bounds.stroke()
}
}
}
I did like this and it is working like charm.
yourLableName.wantsLayer = YES;
yourLableName.layer.cornerRadius = yourLableName.frame.size.width/2;
The easiest one is to do with interface builder, unless you want the corners rounded by some units:
Simply select the border as rounded one :