UIView set only side borders - objective-c

Is there a way to set the sides of the border of a UIView to one color and leave the top and the bottom another?

Nope—CALayer borders don’t support that behavior. The easiest way to accomplish what you want is adding an n-point-wide opaque subview with your desired border color as its background color on each side of your view.
Example:
CGSize mainViewSize = theView.bounds.size;
CGFloat borderWidth = 2;
UIColor *borderColor = [UIColor redColor];
UIView *leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, borderWidth, mainViewSize.height)];
UIView *rightView = [[UIView alloc] initWithFrame:CGRectMake(mainViewSize.width - borderWidth, 0, borderWidth, mainViewSize.height)];
leftView.opaque = YES;
rightView.opaque = YES;
leftView.backgroundColor = borderColor;
rightView.backgroundColor = borderColor;
// for bonus points, set the views' autoresizing mask so they'll stay with the edges:
leftView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleRightMargin;
rightView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin;
[theView addSubview:leftView];
[theView addSubview:rightView];
[leftView release];
[rightView release];
Note that this won’t quite match the behavior of CALayer borders—the left and right border views will always be inside the boundaries of their superview.

The answer with the views that works like borders are very nice, but remember that every view is a UI Object that cost lots of memory.
I whould use uivew's layer to paint a stroke with color on an already existing UIview.
-(CAShapeLayer*)drawLineFromPoint:(CGPoint)fromPoint toPoint:(CGPoint) toPoint withColor:(UIColor *)color andLineWidth:(CGFloat)lineWidth{
CAShapeLayer *lineShape = nil;
CGMutablePathRef linePath = nil;
linePath = CGPathCreateMutable();
lineShape = [CAShapeLayer layer];
lineShape.lineWidth = lineWidth;
lineShape.strokeColor = color.CGColor;
NSUInteger x = fromPoint.x;
NSUInteger y = fromPoint.y;
NSUInteger toX = toPoint.x;
NSUInteger toY = toPoint.y;
CGPathMoveToPoint(linePath, nil, x, y);
CGPathAddLineToPoint(linePath, nil, toX, toY);
lineShape.path = linePath;
CGPathRelease(linePath);
return lineShape;}
and add it to our view.
CAShapeLayer* borderLine=[self drawLineFromPoint:CGPointMake(0, 0) toPoint:CGPointMake(0,_myView.frame.size.height) withColor:[UIColor lightGrayColor] andLineWidth:1.0f];
[_myView.layer addSublayer:borderLine];
So... We take a point and actually painting a line from top to the bottom of our view. The result is that there is a line that looks like a one pixel width border.

Updated for Swift 3.0
I wrote a Swift extension (for a UIButton) that simulates setting a border on any side of a UIView to a given color and width. It's similar to #Noah Witherspoon's approach, but self-contained and autolayout constraint based.
// Swift 3.0
extension UIView {
enum Border {
case left
case right
case top
case bottom
}
func setBorder(border: UIView.Border, weight: CGFloat, color: UIColor ) {
let lineView = UIView()
addSubview(lineView)
lineView.backgroundColor = color
lineView.translatesAutoresizingMaskIntoConstraints = false
switch border {
case .left:
lineView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
lineView.topAnchor.constraint(equalTo: topAnchor).isActive = true
lineView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
lineView.widthAnchor.constraint(equalToConstant: weight).isActive = true
case .right:
lineView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
lineView.topAnchor.constraint(equalTo: topAnchor).isActive = true
lineView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
lineView.widthAnchor.constraint(equalToConstant: weight).isActive = true
case .top:
lineView.topAnchor.constraint(equalTo: topAnchor).isActive = true
lineView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
lineView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
lineView.heightAnchor.constraint(equalToConstant: weight).isActive = true
case .bottom:
lineView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
lineView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
lineView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
lineView.heightAnchor.constraint(equalToConstant: weight).isActive = true
}
}
}

This sounds like one of two answers:
If your view is a static size, then just put a UIView behind it that is 2 pixels wider and 2 pixels shorter than your front view.
If it is non-static sized then you could do the same, resizing the backing view whenever your foreground view is resized, or implement a custom object that implements a UIView, and implement (override) your own drawRect routine.

NAUIViewWithBorders did the trick for me. See also the creator's SO post here. Worth checking out if you need this functionality for more than a couple views.

public extension UIView {
// Border type and arbitrary tag values to identify UIView borders as subviews
public enum BorderType: Int {
case left = 20000
case right = 20001
case top = 20002
case bottom = 20003
}
public func addBorder(borderType: BorderType, width: CGFloat, color: UIColor) {
// figure out frame and resizing based on border type
var autoresizingMask: UIViewAutoresizing
var layerFrame: CGRect
switch borderType {
case .left:
layerFrame = CGRect(x: 0, y: 0, width: width, height: self.bounds.height)
autoresizingMask = [ .flexibleHeight, .flexibleRightMargin ]
case .right:
layerFrame = CGRect(x: self.bounds.width - width, y: 0, width: width, height: self.bounds.height)
autoresizingMask = [ .flexibleHeight, .flexibleLeftMargin ]
case .top:
layerFrame = CGRect(x: 0, y: 0, width: self.bounds.width, height: width)
autoresizingMask = [ .flexibleWidth, .flexibleBottomMargin ]
case .bottom:
layerFrame = CGRect(x: 0, y: self.bounds.height - width, width: self.bounds.width, height: width)
autoresizingMask = [ .flexibleWidth, .flexibleTopMargin ]
}
// look for the existing border in subviews
var newView: UIView?
for eachSubview in self.subviews {
if eachSubview.tag == borderType.rawValue {
newView = eachSubview
break
}
}
// set properties on existing view, or create a new one
if newView == nil {
newView = UIView(frame: layerFrame)
newView?.tag = borderType.rawValue
self.addSubview(newView!)
} else {
newView?.frame = layerFrame
}
newView?.backgroundColor = color
newView?.autoresizingMask = autoresizingMask
}

Related

How to shrink a rectangular round cornered UIButton to a circular UIButton?

With the help of this i was able to shrink the UIButton but atlast i want the UIButton to get rounded.Please help me to get the desired animation in sign up button. The code snippet is :
Follow the link : https://www.dropbox.com/s/rh4tdub3zabxp2j/shot.gif?dl=0
self.buttonShrink = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
self.buttonShrink.duration = .2f;
self.buttonShrink.values = #[[NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(.9,1,1)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(.8,1,1)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(.7,1,1)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(.6,1,1)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(.5,1,1)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(.4,1,1)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(.3,1,1)]];
self.buttonShrink.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
self.sampleButton.transform = CGAffineTransformMakeScale(0,0);
self.sampleButton.alpha = 1;
[self.sampleButton.layer addAnimation:self.buttonShrink forKey:#"buttonScale"];
[self.sampleButton setUserInteractionEnabled:NO];
I did some tinkering and got pretty decent results.
EDIT:
I just uploaded a demo project to GitHub called MorphingButton (link) that generates the animation below:
Here's what I did:
I created a normal iOS 8 button in IB (no outline at all) and connected an outlet and an action to it.
I added height and width constraints.
I added code to set the borderColor, borderWidth, and cornerRadius of the button's layer to give it a rounded corner look. This would take some adjustment to make it look like a real rounded rectangle button.
In the IBAction for the button, switch back and forth between making it round and making it rectangular.
To make the button round:
Use a UIView animateWithDuration method call to set the button's
height constraint to it's width constraint (making it square) and invoke layoutWithNeeded()
Use aCABasicAnimation to animate the button's layer's corner radius to 1/2
the button width.
To make the button rectangular:
Use a UIView animateWithDuration method call to set the button's
height constraint to it's starting height constraint
Use aCABasicAnimation to animate the button's layer's corner radius to 10 (which looks pretty good for a rounded rectangle button.)
The IBAction and viewDidLoad code would look like this in Objective-C:
- (void)viewDidLoad
{
oldHeight = buttonHeightConstraint.constant;
buttonIsRound = FALSE;
[super viewDidLoad];
animationDuration = 0.5;
}
- (IBAction)handleButton:(id)sender
{
CGFloat newHeight;
CGFloat newCornerRadius;
NSLog(#"Entering %s", __PRETTY_FUNCTION__);
if (buttonIsRound)
{
//If the button is currently round,
//go back to the old height/corner radius
newHeight = oldHeight;
newCornerRadius = 10;
}
else
{
//It isn't round now,
//so make it's height and width the same
//and set the corner radius to 1/2 the width
newHeight = buttonWidthConstraint.constant;
newCornerRadius = buttonWidthConstraint.constant/2;
}
[UIView animateWithDuration: animationDuration
animations:^
{
buttonHeightConstraint.constant = newHeight;
[button layoutIfNeeded];
}];
CABasicAnimation *cornerAnimation = [[CABasicAnimation alloc] init];
cornerAnimation.keyPath = #"cornerRadius";
cornerAnimation.fromValue = #(button.layer.cornerRadius);
cornerAnimation.toValue = #(newCornerRadius);
cornerAnimation.duration = animationDuration;
[button.layer addAnimation: cornerAnimation forKey: #"woof"];
button.layer.cornerRadius = newCornerRadius;
buttonIsRound = !buttonIsRound;
}
The Swift IBAction code for the button looks like this:
#IBAction func handleButton(sender: AnyObject)
{
if !buttonIsRound
{
UIView.animateWithDuration(animationDuration)
{
self.buttonHeightConstraint.constant = self.buttonWidthConstraint.constant
self.button.layoutIfNeeded()
self.buttonIsRound = true
}
let cornerAnimation = CABasicAnimation(keyPath: "cornerRadius")
cornerAnimation.fromValue = button.layer.cornerRadius
cornerAnimation.toValue = self.buttonWidthConstraint.constant / 2.0
cornerAnimation.duration = animationDuration
button.layer.addAnimation(cornerAnimation, forKey: "woof")
button.layer.cornerRadius = self.buttonWidthConstraint.constant / 2.0
}
else
{
UIView.animateWithDuration(animationDuration)
{
self.buttonHeightConstraint.constant = self.oldHeight
self.button.layoutIfNeeded()
self.buttonIsRound = false
}
let cornerAnimation = CABasicAnimation(keyPath: "cornerRadius")
cornerAnimation.fromValue = self.buttonWidthConstraint.constant / 2.0
cornerAnimation.toValue = 10
cornerAnimation.duration = animationDuration
button.layer.addAnimation(cornerAnimation, forKey: "woof")
button.layer.cornerRadius = 10
}
}
I never used this for shrinking but as you are using button layer so why can not you use it cornerRadius. I'm not sure suggestion is ok or not??

How to add a gradient tint color to a UISlider in XCode 6?

I'm working on a design application that has a section for selecting colors by three sliders for RGB.
As we can see in xcode, where we want to select a color by RGB values, the slider tint color is a gradient color that changes when we change the sliders. I want to use this in my application. but I have no idea about how to do this?
I've found this code in a blog. but didn't work for me.
- (void)setGradientToSlider:(UISlider *)Slider WithColors:(NSArray *)Colors{
UIView * view = (UIView *)[[Slider subviews]objectAtIndex:0];
UIImageView * maxTrackImageView = (UIImageView *)[[view subviews]objectAtIndex:0];
CAGradientLayer * maxTrackGradient = [CAGradientLayer layer];
CGRect rect = maxTrackImageView.frame;
rect.origin.x = view.frame.origin.x;
maxTrackGradient.frame = rect;
maxTrackGradient.colors = Colors;
[maxTrackGradient setStartPoint:CGPointMake(0.0, 0.5)];
[maxTrackGradient setEndPoint:CGPointMake(1.0, 0.5)];
[[maxTrackImageView layer] insertSublayer:maxTrackGradient atIndex:0];
/////////////////////////////////////////////////////
UIImageView * minTrackImageView = (UIImageView *)[[view subviews]objectAtIndex:1];
CAGradientLayer * minTrackGradient = [CAGradientLayer layer];
rect = minTrackImageView.frame;
rect.size.width = maxTrackImageView.frame.size.width;
rect.origin.x = 0;
rect.origin.y = 0;
minTrackGradient.frame = rect;
minTrackGradient.colors = Colors;
[minTrackGradient setStartPoint:CGPointMake(0.0, 0.5)];
[minTrackGradient setEndPoint:CGPointMake(1.0, 0.5)];
[minTrackImageView.layer insertSublayer:minTrackGradient atIndex:0];
}
I would appreciate any helps. Thanks.
While it didnt give me the desired results here is a down and dirty Swift version of the answer above for those that want to try it.
func setSlider(slider:UISlider) {
let tgl = CAGradientLayer()
let frame = CGRectMake(0, 0, slider.frame.size.width, 5)
tgl.frame = frame
tgl.colors = [UIColor.blueColor().CGColor, UIColor.greenColor().CGColor, UIColor.yellowColor().CGColor, UIColor.orangeColor().CGColor, UIColor.redColor().CGColor]
tgl.startPoint = CGPointMake(0.0, 0.5)
tgl.endPoint = CGPointMake(1.0, 0.5)
UIGraphicsBeginImageContextWithOptions(tgl.frame.size, tgl.opaque, 0.0);
tgl.renderInContext(UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
image.resizableImageWithCapInsets(UIEdgeInsetsZero)
slider.setMinimumTrackImage(image, forState: .Normal)
//slider.setMaximumTrackImage(image, forState: .Normal)
}
UPDATE for Swift 4.0
func setSlider(slider:UISlider) {
let tgl = CAGradientLayer()
let frame = CGRect.init(x:0, y:0, width:slider.frame.size.width, height:5)
tgl.frame = frame
tgl.colors = [UIColor.blue.cgColor, UIColor.green.cgColor, UIColor.yellow.cgColor, UIColor.orange.cgColor, UIColor.red.cgColor]
tgl.startPoint = CGPoint.init(x:0.0, y:0.5)
tgl.endPoint = CGPoint.init(x:1.0, y:0.5)
UIGraphicsBeginImageContextWithOptions(tgl.frame.size, tgl.isOpaque, 0.0);
tgl.render(in: UIGraphicsGetCurrentContext()!)
if let image = UIGraphicsGetImageFromCurrentImageContext() {
UIGraphicsEndImageContext()
image.resizableImage(withCapInsets: UIEdgeInsets.zero)
slider.setMinimumTrackImage(image, for: .normal)
}
}
Here is possible solution:
Usage:
//array of CGColor objects, color1 and color2 are UIColor objects
NSArray *colors = [NSArray arrayWithObjects:(id)color1.CGColor, (id)color2.CGColor, nil];
//your UISlider
[slider setGradientBackgroundWithColors:colors];
Implementation:
Create category on UISlider:
- (void)setGradientBackgroundWithColors:(NSArray *)colors
{
CAGradientLayer *trackGradientLayer = [CAGradientLayer layer];
CGRect frame = self.frame;
frame.size.height = 5.0; //set the height of slider
trackGradientLayer.frame = frame;
trackGradientLayer.colors = colors;
//setting gradient as horizontal
trackGradientLayer.startPoint = CGPointMake(0.0, 0.5);
trackGradientLayer.endPoint = CGPointMake(1.0, 0.5);
UIImage *trackImage = [[UIImage imageFromLayer:trackGradientLayer] resizableImageWithCapInsets:UIEdgeInsetsZero];
[self setMinimumTrackImage:trackImage forState:UIControlStateNormal];
[self setMaximumTrackImage:trackImage forState:UIControlStateNormal];
}
Where colors is array of CGColor.
I have also created a category on UIImage which creates image from layer as you need an UIImage for setting gradient on slider.
+ (UIImage *)imageFromLayer:(CALayer *)layer
{
UIGraphicsBeginImageContextWithOptions(layer.frame.size, layer.opaque, 0.0);
[layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return outputImage;
}
For Swift 3 and to prevent the slider from scaling the Min image, apply this when setting the its image. Recalculating the slider's left side is not necessary. Only recalc if you can changing the color of the gradient. The Max image does not seem to scale, but you should probably apply the same setting for consistency. There is a slight difference on the Max image when not applying its insets.
slider.setMinimumTrackImage(image?.resizableImage(withCapInsets:.zero), for: .normal)
For some reason it only works properly when resizableImage(withCapInsets:.zero) is all done at the same time. Running that part separate does not allow the image to work and gets scaled.
Here is the entire routine in Swift 3:
func setSlider(slider:UISlider) {
let tgl = CAGradientLayer()
let frame = CGRect(x: 0.0, y: 0.0, width: slider.bounds.width, height: 5.0 )
tgl.frame = frame
tgl.colors = [ UIColor.yellow.cgColor,UIColor.black.cgColor]
tgl.endPoint = CGPoint(x: 1.0, y: 1.0)
tgl.startPoint = CGPoint(x: 0.0, y: 1.0)
UIGraphicsBeginImageContextWithOptions(tgl.frame.size, false, 0.0)
tgl.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
slider.setMaximumTrackImage(image?.resizableImage(withCapInsets:.zero), for: .normal)
slider.setMinimumTrackImage(image?.resizableImage(withCapInsets:.zero), for: .normal)
}
This is a really effective approach that I've found after a lot of web search. So it's better to share it here as a complete answer. The following code is a Swift Class That you can use to create and use gradients as UIView or UIImage.
import Foundation
import UIKit
class Gradient: UIView{
// Gradient Color Array
private var Colors: [UIColor] = []
// Start And End Points Of Linear Gradient
private var SP: CGPoint = CGPoint.zeroPoint
private var EP: CGPoint = CGPoint.zeroPoint
// Start And End Center Of Radial Gradient
private var SC: CGPoint = CGPoint.zeroPoint
private var EC: CGPoint = CGPoint.zeroPoint
// Start And End Radius Of Radial Gradient
private var SR: CGFloat = 0.0
private var ER: CGFloat = 0.0
// Flag To Specify If The Gradient Is Radial Or Linear
private var flag: Bool = false
// Some Overrided Init Methods
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
// Draw Rect Method To Draw The Graphics On The Context
override func drawRect(rect: CGRect) {
// Get Context
let context = UIGraphicsGetCurrentContext()
// Get Color Space
let colorSpace = CGColorSpaceCreateDeviceRGB()
// Create Arrays To Convert The UIColor to CG Color
var colorComponent: [CGColor] = []
var colorLocations: [CGFloat] = []
var i: CGFloat = 0.0
// Add Colors Into The Color Components And Use An Index Variable For Their Location In The Array [The Location Is From 0.0 To 1.0]
for color in Colors {
colorComponent.append(color.CGColor)
colorLocations.append(i)
i += CGFloat(1.0) / CGFloat(self.Colors.count - 1)
}
// Create The Gradient With The Colors And Locations
let gradient: CGGradientRef = CGGradientCreateWithColors(colorSpace, colorComponent, colorLocations)
// Create The Suitable Gradient Based On Desired Type
if flag {
CGContextDrawRadialGradient(context, gradient, SC, SR, EC, ER, 0)
} else {
CGContextDrawLinearGradient(context, gradient, SP, EP, 0)
}
}
// Get The Input Data For Linear Gradient
func CreateLinearGradient(startPoint: CGPoint, endPoint: CGPoint, colors: UIColor...) {
self.Colors = colors
self.SP = startPoint
self.EP = endPoint
self.flag = false
}
// Get The Input Data For Radial Gradient
func CreateRadialGradient(startCenter: CGPoint, startRadius: CGFloat, endCenter: CGPoint, endRadius: CGFloat, colors: UIColor...) {
self.Colors = colors
self.SC = startCenter
self.EC = endCenter
self.SR = startRadius
self.ER = endRadius
self.flag = true
}
// Function To Convert Gradient To UIImage And Return It
func getImage() -> UIImage {
// Begin Image Context
UIGraphicsBeginImageContext(self.bounds.size)
// Draw The Gradient
self.drawRect(self.frame)
// Get Image From The Current Context
let image = UIGraphicsGetImageFromCurrentImageContext()
// End Image Context
UIGraphicsEndImageContext()
// Return The Result Gradient As UIImage
return image
}
}

SKShapeNode Positioning

I'm poking around SpriteKit and encountered some weirdness
i.e i'm simply adding rectangular SKShapeNodethat should be fullscreen on iPhone
override func didMoveToView(view: SKView) {
let _box = SKShapeNode(rectOfSize: self.frame.size)
_box.strokeColor = SKColor.blueColor()
_box.fillColor = SKColor.blueColor()
_box.position = CGPointMake(200, 200)
_box.name = "box"
self.addChild(_box)
let _player = SKShapeNode(circleOfRadius: 50)
_player.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
_player.fillColor = SKColor.redColor()
_player.strokeColor = SKColor.redColor()
_player.name = "player"
_player.physicsBody = SKPhysicsBody(circleOfRadius: 50)
_player.physicsBody?.dynamic = true
_player.physicsBody?.allowsRotation = false
self.addChild(_player)
let _ground = SKShapeNode(rectOfSize: CGSizeMake(self.frame.width, 20))
_ground.name = "ground"
_ground.position = CGPointMake(0, 0)
_ground.fillColor = SKColor.greenColor()
_ground.strokeColor = SKColor.greenColor()
_ground.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(self.frame.width, 20))
_ground.physicsBody?.dynamic = false
self.addChild(_ground)
}
My views are initialised this way
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
// Configure the view.
let skView = self.view as SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
}
The resulting scene looks like so: screenshot
All Nodes are shifted to the left-bottom.
Basically left-bottom corner of rectangular shapes is out of bounds (i've checked that by positioning blue shape to (200, 200) instead of (0, 0) - and left-bottome edges were still out of screen frame.
Anchor point for scene is set to (0, 0) so the basically it looks like for me, that it sets position for central point of shape.
What is a best way to define position of left-bottom corner of the Node, instead of mid-point?
_box.position = CGPointMake(self.size.width*.5, self.size.height*.5)
This will position the center of your sprite at the center of the screen.
If you create a shape using a bezier path or CGPath, you can specify the position of its "anchor point," where the path's origin specifies the bottom/left corner of the path. For example,
UIBezierPath* bezierPath = [UIBezierPath bezierPathWithRect: CGRectMake(0, 0, 50, 50)];
SKShapeNode *shape = [SKShapeNode node];
shape.path = bezierPath.CGPath;
shape.fillColor = [SKColor blueColor];
Also, if your deployment target is iOS 8, you can create your shape node with
SKShapeNode *shape = [SKShapeNode shapeNodeWithRect:CGRectMake(0, 0, 50, 50)];
shape.fillColor = [SKColor blueColor];

Draw NSTableView Alternating Rows Like iTunes 11

I am aware that there are other questions on SO about changing alternating row colors. That's easy and it's not what I want to do.
I want to draw custom alternating-colored rows in a view-based NSTableView that look like those from iTunes 11 (slight bezel at the top and bottom of the row, as shown in this screenshot):
NOTE:
I know I can subclass NSTableRowView and do my custom drawing there. However, this is NOT an acceptable answer because the custom row will only be used for rows that have data in the table. In other words, if the table has only 5 rows, those 5 rows will use my custom NSTableRowView class but the remaining "rows" in the rest of the table (which are empty) will use the standard alternating colors. In that case, the first 5 rows will show the bezel and the remaining ones won't. Not good.
So, how can I hack NSTableView to draw these styled alternating rows for both filled and empty rows?
That "slight bezel", as you put it, can actually be easily done with a little cheating on our part. Because, if you look closely, the top of every cell is a slightly lighter blue color than the dark alternating row, and the bottom of every cell is a dark grayish color, you can subclass NSTableView, then override - (void)drawRow:(NSInteger)row clipRect:(NSRect)clipRect:
- (void)drawRow:(NSInteger)row clipRect:(NSRect)clipRect
{
//Use the drawing code from http://stackoverflow.com/a/5101923/945847, but change the colors to
//look like iTunes's alternating rows.
NSRect cellBounds = [self rectOfRow:row];
NSColor *color = (row % 2) ? [NSColor colorWithCalibratedWhite:0.975 alpha:1.000] : [NSColor colorWithCalibratedRed:0.932 green:0.946 blue:0.960 alpha:1.000];
[color setFill];
NSRectFill(cellBounds);
/* Slightly dark gray color */
[[NSColor colorWithCalibratedWhite:0.912 alpha:1.000] set];
/* Get the current graphics context */
CGContextRef currentContext = [[NSGraphicsContext currentContext]graphicsPort];
/*Draw a one pixel line of the slightly lighter blue color */
CGContextSetLineWidth(currentContext,1.0f);
/* Start the line at the top of our cell*/
CGContextMoveToPoint(currentContext,0.0f, NSMaxY(cellBounds));
/* End the line at the edge of our tableview, for multi-columns, this will actually be overkill*/
CGContextAddLineToPoint(currentContext,NSMaxX(cellBounds), NSMaxY(cellBounds));
/* Use the context's current color to draw the line */
CGContextStrokePath(currentContext);
/* Slightly lighter blue color */
[[NSColor colorWithCalibratedRed:0.961 green:0.970 blue:0.985 alpha:1.000] set];
CGContextSetLineWidth(currentContext,1.0f);
CGContextMoveToPoint(currentContext,0.0f,1.0f);
CGContextAddLineToPoint(currentContext,NSMaxX(self.bounds), 1.0f);
CGContextStrokePath(currentContext);
[super drawRow:row clipRect:clipRect];
}
Which, when done in a quick little tableview, looks like this:
But what to do about the top and bottom of the tableview? After all, they'll still be either an ugly white, or the default alternating rows color. Well, as Apple revealed (in a talk titled, interestingly enough View Based NSTableView, Basic To Advanced), you can override -(void)drawBackgroundInClipRect:(NSRect)clipRect and do a little math to draw the background of the tableview like extra rows. A quick implementation looks something like this:
-(void)drawBackgroundInClipRect:(NSRect)clipRect
{
// The super class implementation obviously does something more
// than just drawing the striped background, because
// if you leave this out it looks funny
[super drawBackgroundInClipRect:clipRect];
CGFloat yStart = 0;
NSInteger rowIndex = -1;
if (clipRect.origin.y < 0) {
while (yStart > NSMinY(clipRect)) {
CGFloat yRowTop = yStart - self.rowHeight;
NSRect rowFrame = NSMakeRect(0, yRowTop, clipRect.size.width, self.rowHeight);
NSUInteger colorIndex = rowIndex % self.colors.count;
NSColor *color = [self.colors objectAtIndex:colorIndex];
[color set];
NSRectFill(rowFrame);
/* Slightly dark gray color */
[[NSColor colorWithCalibratedWhite:0.912 alpha:1.000] set];
/* Get the current graphics context */
CGContextRef currentContext = [[NSGraphicsContext currentContext]graphicsPort];
/*Draw a one pixel line of the slightly lighter blue color */
CGContextSetLineWidth(currentContext,1.0f);
/* Start the line at the top of our cell*/
CGContextMoveToPoint(currentContext,0.0f, yRowTop + self.rowHeight - 1);
/* End the line at the edge of our tableview, for multi-columns, this will actually be overkill*/
CGContextAddLineToPoint(currentContext,NSMaxX(clipRect), yRowTop + self.rowHeight - 1);
/* Use the context's current color to draw the line */
CGContextStrokePath(currentContext);
/* Slightly lighter blue color */
[[NSColor colorWithCalibratedRed:0.961 green:0.970 blue:0.985 alpha:1.000] set];
CGContextSetLineWidth(currentContext,1.0f);
CGContextMoveToPoint(currentContext,0.0f,yRowTop);
CGContextAddLineToPoint(currentContext,NSMaxX(clipRect), yRowTop);
CGContextStrokePath(currentContext);
yStart -= self.rowHeight;
rowIndex--;
}
}
}
But then, this leaves the bottom of the tableview that same ugly blank white color! So, we have to also override -(void)drawGridInClipRect:(NSRect)clipRect. Yet another quick implementation looks like this:
-(void)drawGridInClipRect:(NSRect)clipRect {
[super drawGridInClipRect:clipRect];
NSUInteger numberOfRows = self.numberOfRows;
CGFloat yStart = 0;
if (numberOfRows > 0) {
yStart = NSMaxY([self rectOfRow:numberOfRows - 1]);
}
NSInteger rowIndex = numberOfRows + 1;
while (yStart < NSMaxY(clipRect)) {
CGFloat yRowTop = yStart - self.rowHeight;
NSRect rowFrame = NSMakeRect(0, yRowTop, clipRect.size.width, self.rowHeight);
NSUInteger colorIndex = rowIndex % self.colors.count;
NSColor *color = [self.colors objectAtIndex:colorIndex];
[color set];
NSRectFill(rowFrame);
/* Slightly dark gray color */
[[NSColor colorWithCalibratedWhite:0.912 alpha:1.000] set];
/* Get the current graphics context */
CGContextRef currentContext = [[NSGraphicsContext currentContext]graphicsPort];
/*Draw a one pixel line of the slightly lighter blue color */
CGContextSetLineWidth(currentContext,1.0f);
/* Start the line at the top of our cell*/
CGContextMoveToPoint(currentContext,0.0f, yRowTop - self.rowHeight);
/* End the line at the edge of our tableview, for multi-columns, this will actually be overkill*/
CGContextAddLineToPoint(currentContext,NSMaxX(clipRect), yRowTop - self.rowHeight);
/* Use the context's current color to draw the line */
CGContextStrokePath(currentContext);
/* Slightly lighter blue color */
[[NSColor colorWithCalibratedRed:0.961 green:0.970 blue:0.985 alpha:1.000] set];
CGContextSetLineWidth(currentContext,1.0f);
CGContextMoveToPoint(currentContext,0.0f,yRowTop);
CGContextAddLineToPoint(currentContext,NSMaxX(self.bounds), yRowTop);
CGContextStrokePath(currentContext);
yStart += self.rowHeight;
rowIndex++;
}
}
When all is said and done, we get nice little fake tableview cell rows on the top and bottom of our clipview that looks a little like this:
The full subclass can be found here.
you can use
- (void)setUsesAlternatingRowBackgroundColors:(BOOL)useAlternatingRowColors
with useAlternatingRowColors YES to specify standard alternating row colors for the background, NO to specify a solid color.
I found that you can draw both the top and bottom part in drawBackgroundInClipRect -- essentially in the missing else clause of #CodaFi's solution.
So here's an approach in Swift, assuming you have access to backgroundColor and alternateBackgroundColor:
override func drawBackground(inClipRect clipRect: NSRect) {
// I didn't find leaving this out changed appearance at all unlike
// CodaFi stated.
super.drawBackground(inClipRect: clipRect)
guard usesAlternatingRowBackgroundColors else { return }
drawTopAlternatingBackground(inClipRect: clipRect)
drawBottomAlternatingBackground(inClipRect: clipRect)
}
fileprivate func drawTopAlternatingBackground(inClipRect clipRect: NSRect) {
guard clipRect.origin.y < 0 else { return }
let backgroundColor = self.backgroundColor
let alternateColor = self.alternateBackgroundColor
let rectHeight = rowHeight + intercellSpacing.height
let minY = NSMinY(clipRect)
var row = 0
while true {
if row % 2 == 0 {
backgroundColor.setFill()
} else {
alternateColor.setFill()
}
let rowRect = NSRect(
x: 0,
y: (rectHeight * CGFloat(row) - rectHeight),
width: NSMaxX(clipRect),
height: rectHeight)
NSRectFill(rowRect)
drawBezel(inRect: rowRect)
if rowRect.origin.y < minY { break }
row -= 1
}
}
fileprivate func drawBottomAlternatingBackground(inClipRect clipRect: NSRect) {
let backgroundColor = self.backgroundColor
let alternateColor = self.alternateBackgroundColor
let rectHeight = rowHeight + intercellSpacing.height
let maxY = NSMaxY(clipRect)
var row = rows(in: clipRect).location
while true {
if row % 2 == 1 {
backgroundColor.setFill()
} else {
alternateColor.setFill()
}
let rowRect = NSRect(
x: 0,
y: (rectHeight * CGFloat(row)),
width: NSMaxX(clipRect),
height: rectHeight)
NSRectFill(rowRect)
drawBezel(inRect: rowRect)
if rowRect.origin.y > maxY { break }
row += 1
}
}
func drawBezel(inRect rect: NSRect) {
let topLine = NSRect(x: 0, y: NSMaxY(rect) - 1, width: NSWidth(rect), height: 1)
NSColor(calibratedWhite: 0.912, alpha: 1).set()
NSRectFill(topLine)
let bottomLine = NSRect(x: 0, y: NSMinY(rect) , width: NSWidth(rect), height: 1)
NSColor(calibratedRed:0.961, green:0.970, blue:0.985, alpha:1).set()
NSRectFill(bottomLine)
}
And in case you don't draw in a NSTableRowView subclass:
override func drawRow(_ row: Int, clipRect: NSRect) {
let rowRect = rect(ofRow: row)
let color = row % 2 == 0 ? self.backgroundColor : self.alternateBackgroundColor
color.setFill()
NSRectFill(rowRect)
drawBezel(inRect: rowRect)
}

UIView with blurred border

Is there a way to blur/fade out the border of a UIView?
i have done very little to none with core graphics so far.
You might try setting the UIView's CALayer with a large shadow radius and transparency. Something like:
#include <QuartzCore/QuartzCore.h>
...
CALayer *layer = myView.layer;
layer.shadowOpacity = .5;
layer.shadowColor = [[UIColor blackColor] CGColor];
layer.shadowOffset = CGSizeMake(0,0);
layer.shadowRadius = 10;
Adding an example with the swift version:
btnSignIn.layer.cornerRadius = btnSignIn.frame.height * 0.5
btnSignIn.layer.shadowColor = UIColor.darkGray.cgColor
btnSignIn.layer.shadowRadius = 4.0
btnSignIn.layer.shadowOpacity = 1.0
btnSignIn.layer.shadowOffset = CGSize(width: 0, height: 0)
Result:
Keep in mind that if you change the shadow settings in the ib editor, it will change the text's shadow, not the view's border.
Also you can make a class and set it in IB Builder:
class UIRoundedWhiteButton: UIButton {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
layer.cornerRadius = frame.height * 0.5
layer.shadowColor = UIColor.darkGray.cgColor
layer.shadowRadius = 4.0
layer.shadowOpacity = 1.0
layer.shadowOffset = CGSize(width: 0, height: 0)
}...