setRadiusCorner just at the bottom of the view - view

i have a view with rounded corners.
I want to make it rounded just at the two bottom corners of the view.
How can i do that?
This is my code that makes the rounded corners for the view:
CALayer *myLayer = moreInfoView.layer;
[myLayer setCornerRadius:20.0];
[view.layer setMasksToBounds:YES];
Is it possible?
Thanks!

Round only the UIRectCornerBottomLeft and UIRectCornerBottomRight corners:
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:yourView.bounds
byRoundingCorners:(UIRectCornerBottomLeft | UIRectCornerBottomRight)
cornerRadii:CGSizeMake(5.0, 5.0)];
CAShapeLayer *layer = [CAShapeLayer layer];
layer.frame = yourView.bounds;
layer.path = path.CGPath;
yourView.layer.mask = layer;

Don't think you can do that with the CALayer with ease - you could subclass NSView and make a simple redraw routine. May be easiest to use http://www.cocoadev.com/index.pl?RoundedRectangles as a starting point (after removing the two bezier Paths you do not want to have rounded) and then do a simple:
- (void)drawRect:(CGRect)rect
{
self.layer.masksToBounds = NO;
self.layer.shadowColor = [[UIColor xxxx] CGColor];
self.layer.shadowOffset = CGSizeMake(0,2);
self.layer.shadowRadius = ...
self.layer.shadowOpacity = ...
UIBezierPath *path = [UIBezierPath bezierPathWithPartiallyRoundedRect:rect
byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(20, 20)];
[[UIColor blackColor] setFill or Stroke];
[path stroke or fill];
}
In above link - the second simplified example makes your live easier - as you can simply make two of them 'path lineToPoint: ..'.

Related

What's the preferable way to draw a collection of lines in an NSView?

Let's say there's a collection of lines in some instance variable in my NSView instance. This collection stays the same over time. How would you draw the collection?
I currently draw to an NSImage when the collection is set up, and override -drawRect: to display the image's contents. Is there a better way?
Tested out two options so far :
Draw to an NSImage when the collection is set up, and override -drawRect: to display the image's contents.
Draw the list of lines in -drawRect: itself.
Benchmarks show the image drawing to be much faster. If I resize my window and it has, say; 10000 line segments, then the resize is incredibly slow if I use the second option. Not so with the first.
What's the preferable (eg. most efficient) strategy?
Example code :
- (void)drawSegments:(NSSize)size segments:(segment *)segments count:(int)nb_segments {
NSBitmapImageRep *lineRepresentation = [self bitmapForPixels:linePixels];
[NSGraphicsContext setCurrentContext:
[NSGraphicsContext graphicsContextWithBitmapImageRep:lineRepresentation]];
memset(*linePixels, 0, pixels*4);
NSBezierPath *path = [NSBezierPath bezierPath];
for (int i=0 ; i<nb_segments ; i++) {
[path moveToPoint:NSMakePoint(segments[i].x1, segments[i].y1)];
[path lineToPoint:NSMakePoint(segments[i].x2, segments[i].y2)];
}
[[NSColor blackColor] setStroke];
[path stroke];
[lineRepresentation release];
}
- (NSBitmapImageRep *)bitmapForPixels:(int **)pixels {
NSBitmapImageRep *representation = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:((unsigned char **)pixels)
pixelsWide:SCREEN_SIZE.width
pixelsHigh:SCREEN_SIZE.height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:true
isPlanar:false
colorSpaceName:NSDeviceRGBColorSpace
bitmapFormat:NSAlphaFirstBitmapFormat
bytesPerRow:4*SCREEN_SIZE.width
bitsPerPixel:32];
return representation;
}

Creating right aligned NSView

I want to create a NSView container such that any NSControl object added should be right aligned.
I have added a method to MyCustomNSView class as following. Currently I am adding buttons which are getting left aligned.
- (void) _addButton:(NSString *)title withIdentifier:(NSString *)identifier {
NSButton *button = [[NSButton alloc] initWithFrame:NSMakeRect(100 * [_buttonIdentifierList count] + 10 , 5, 70, 20)];
[button setTitle:title];
[button setAction:#selector(actionButtonPressed:)];
[button setTarget:self];
[button setIdentifier:identifier];
[self addSubview:button];
[_buttonIdentifierList addObject:identifier];
}
So what modifications do I have to make to the above method so that it will add the objects from right side.
I was planning to do it mathematically(Generating frame origin that would generate right aligned origin point). I also tried out using NSLayoutConstrains but didnt work out..
How do I do it using autolayouts ?
To do it by manual positioning, you would compute the frame for the button something like this:
NSButton *button = [[NSButton alloc] initWithFrame:NSMakeRect(NSMaxX(self.bounds) - (100 * [_buttonIdentifierList count] + 10) - 70, 5, 70, 20)];
That is, you take your current calculation which is an offset toward the right (from the left edge) and negate it to make it an offset toward the left. You add the value of the right edge of the containing view so it's an offset from the right edge. That has computed the X position of the right edge of the button, so you subtract the button's width to get the origin of the button, which is on its left edge.
To use auto layout (which uses NSLayoutConstraint), you could do this:
NSButton *button = [[NSButton alloc] initWithFrame:NSZeroRect];
[button setTitle:title];
[button setAction:#selector(actionButtonPressed:)];
[button setTarget:self];
[button setIdentifier:identifier];
button.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:button];
__block NSButton* previousButton = nil;
if (_buttonIdentifierList.count)
{
NSString* previousButtonIdentifier = _buttonIdentifierList.lastObject;
[self.subviews enumerateObjectsUsingBlock:^(NSView* subview, NSUInteger idx, BOOL *stop){
if ([subview.identifier isEqualToString:previousButtonIdentifier])
{
previousButton = (NSButton*)subview;
*stop = YES;
}
}];
}
NSDictionary* metrics = #{ #"buttonWidth": #70,
#"buttonHeight": #20,
#"buttonSeparation": #30,
#"horizontalMargin": #10,
#"verticalMargin": #5 };
if (previousButton)
{
NSDictionary* views = NSDictionaryOfVariableBindings(button, previousButton);
NSArray* constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"[button(buttonWidth)]-(buttonSeparation)-[previousButton]" options:NSLayoutFormatAlignAllBaseline metrics:metrics views:views];
[self addConstraints:constraints];
}
else
{
NSDictionary* views = NSDictionaryOfVariableBindings(button);
NSArray* constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"[button(buttonWidth)]-(horizontalMargin)-|" options:0 metrics:metrics views:views];
[self addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[button(buttonHeight)]-(verticalMargin)-|" options:0 metrics:metrics views:views];
[self addConstraints:constraints];
}
[_buttonIdentifierList addObject:identifier];
Finding the previousButton would be simplified if you keep track of the buttons, rather than the identifiers. If you have a button object, it's easy to get its identifier, but the reverse (getting the button object when all you have is the identifier) is not as simple.
If you want to allow the buttons to be their natural width and height, rather than a fixed value, you can just leave out those width/height specifiers (that is, use [button] rather than [button(buttonWidth)]). If you want all of the buttons to have the same width, but let the system pick the width of the naturally widest button, you can use [button(==previousButton)]. Since a button's default compression resistance priority is higher than its content hugging priority, it will pick the smallest width that doesn't compress any of them.
If you want the buttons to be the standard distance away from each other, rather than the fixed value of 30 points, you can use use - instead of -(buttonSeparation)-. Similarly, if you want them to be the standard distance from the superview edge, you can use - instead of -(horizontalMargin)- or -(verticalMargin)-.

CALayer thin white lines

I am animating a CALayer to move and for some reason I keep getting these thin white lines all across the screen... it's not my device I can assure you here is how I set up the CALayer:
dodgelayer=nil;
dodgelayer= [CALayer layer];
dodgelayer.backgroundColor = [UIColor blueColor].CGColor;
dodgelayer.frame = CGRectMake(190, 80, 50, 50);
dodgelayer.borderColor = [UIColor whiteColor].CGColor;
dodgelayer.borderWidth = 2.0;
dodgelayer.cornerRadius = 50.0;
and how I animate it:
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"position"];
values = [NSArray arrayWithObjects:[NSValue valueWithCGPoint: CGPointMake(aLayer.frame.origin.x+25,aLayer.frame.origin.y+25)], [NSValue valueWithCGPoint: CGPointMake(point.x,point.y)], nil];
[anim setValues:values];
[anim setDuration:0.7];
anim.removedOnCompletion=NO;
anim.fillMode = kCAFillModeForwards;
anim.timingFunction=[CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut];
sublayer.shouldRasterize=YES;
[sublayer addAnimation:anim forKey:#"position"];
I honestly have no idea what is making these thing white lines appear, but I can tell you that they appear during animations... im stumped on this one any help would be appreciated
I just bumped into this problem myself.
The cause is the shouldResterize = YES.
Before calling this, you should set
sublayer.rasterizationScale = [[UIScreen mainScreen] scale];
and the lines will go away.

How to draw both stroked and filled text in drawLayer:inContext delegate

this is my drawLayer method in a CALayer's delegate.
it's only responsible for drawing a string with length = 1.
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
CGRect boundingBox = CGContextGetClipBoundingBox(ctx);
NSAttributedString *string = [[NSAttributedString alloc] initWithString:self.letter attributes:[self attrs]];
CGContextSaveGState(ctx);
CGContextSetShadowWithColor(ctx, CGSizeZero, 3.0, CGColorCreateGenericRGB(1.0, 1.0, 0.922, 1.0));
CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef)string);
CGRect rect = CTLineGetImageBounds(line, ctx);
CGFloat xOffset = CGRectGetMidX(rect);
CGFloat yOffset = CGRectGetMidY(rect);
CGPoint pos = CGPointMake(CGRectGetMidX(boundingBox) - xOffset, CGRectGetMidY(boundingBox)- yOffset);
CGContextSetTextPosition(ctx, pos.x, pos.y);
CTLineDraw(line, ctx);
CGContextRestoreGState(ctx);
}
here's the attributes dictionary:
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
[NSFont fontWithName:#"GillSans-Bold" size:72.0], NSFontAttributeName,
[NSColor blackColor], NSForegroundColorAttributeName,
[NSNumber numberWithFloat:1.0], NSStrokeWidthAttributeName,
[NSColor blackColor], NSStrokeColorAttributeName,
style, NSParagraphStyleAttributeName, nil];
as is, the stroke does not draw, but the fill does.
if i comment out the stroke attributes in the dictionary, the fill draws.
i know this can't be right, but i can't find any reference to this problem.
is this a known issue when drawing text with a delegate ?
as the string is one character, i was following the doc example not using any framesetter machinery, but tried that anyway as a fix attempt without success.
in reading this question's answer, i realized that i needed to be using a negative number for the stroke value. i was thinking of the stroke being applied to the outside of the letter drawn by CTLineDraw, rather then inside the text shape.
i'm answering my own question, in case this should help anyone else with this misunderstanding, as i didn't see the referenced doc covering this.

How to align a subview to the center of a parent NSView

I'm developing a program in which I am programmatically adding an NSImageView to a custom NSView class. While creating the image-view, I am passing the frame of the parent container.
-(NSImageView *)loadNSImage:(NSString *)imageName frame:(NSRect)frame{
imageName = [[NSBundle mainBundle] pathForResource:imageName ofType:#"png"];
NSImage *image = [[NSImage alloc] initWithContentsOfFile:imageName];
NSImageView *imageView = [[NSImageView alloc]initWithFrame:frame];
[imageView setImage:image];
[imageView setImageScaling:NSImageScaleProportionallyUpOrDown];
[imageView setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable | NSViewMaxXMargin | NSViewMaxYMargin | NSViewMinXMargin | NSViewMinYMargin];
[imageView setImageAlignment:NSImageAlignCenter];
[image release];
return imageView;
}
Then I am using the addSubView method to add this into the Custom View. The problem is that the image sticks to the bottom-left of the parent view. How can I place this image in the center of the parent view?
I have tried adding an offset to the frame origin, but that doesn't really work when the window is resized, or if an image with a different size is loaded.
Any help would be appreciated.
I'm not sure if this is the best way to do it, but I just do simple math when I want to center a subview inside its parent.
You need to set all the margins to be auto-resizable if you want it to stay centered.
[subview setFrameOrigin:NSMakePoint(
round((NSWidth([parentView bounds]) - NSWidth([subview frame])) / 2),
round((NSHeight([parentView bounds]) - NSHeight([subview frame])) / 2)
)];
[subview setAutoresizingMask:NSViewMinXMargin | NSViewMaxXMargin | NSViewMinYMargin | NSViewMaxYMargin];
This is just calculating the margins required to get a centered origin.
If you need the centered frame before you invoke initWithFrame: then just use the above logic to compute the frame origin values.
Swift 5 #d11wtq's answer
let x = (parentView.bounds.width - subviewWidth) * 0.5
let y = (parentView.bounds.height - subviewHeight) * 0.5
let f = CGRect(x: x, y: y, width: subviewWidth, height: subviewHeight)
let subview = NSView(frame: f)
subview.autoresizingMask = [.minXMargin, .maxXMargin, .minYMargin, .maxYMargin ]
Objective - C in macOS Catalina , Version 10.15.3
more readable #d11wtq's answer
CGFloat x = (NSWidth(parentView.bounds) - subviewWidth) * 0.5;
CGFloat y = (NSHeight(parentView.bounds) - subviewHeight) * 0.5;
CGRect f = CGRectMake(x, y, subviewWidth, subviewHeight);
subview = [[NSView alloc] initWithFrame: f];
subview.autoresizingMask = NSViewMinXMargin | NSViewMaxXMargin | NSViewMinYMargin | NSViewMaxYMargin;
Try This.
in .h
#property (strong, nonatomic) IBOutlet UIView *CoinsPurchaseView;
in .m
self.CoinsPurchaseView.center = self.view.center;
[self.view addSubview:self.CoinsPurchaseView];

Resources