Draw NSTableView Alternating Rows Like iTunes 11 - objective-c

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)
}

Related

Drawing board/grid with Cocoa

I'm writing a small boardgame for Mac OS X using Cocoa. I the actual grid is drawn as follows:
- (void)drawRect:(NSRect)rect
{
for (int x=0; x < GRIDSIZE; x++) {
for (int y=0; y < GRIDSIZE; y++) {
float ix = x*cellWidth;
float iy = y*cellHeight;
NSColor *color = (x % 2 == y % 2) ? boardColors[0] : boardColors[1];
[color set];
NSRect r = NSMakeRect(ix, iy, cellWidth, cellHeight);
NSBezierPath *path = [NSBezierPath bezierPath];
[path appendBezierPathWithRect:r];
[path fill];
[path stroke];
}
}
}
This works great, except that I see some errors in colors between the tiles. I guess this is due to some antialiasing or similar. See screenshots below (hopefully you can also see the same problems... its some black lines where the tiles overlap):
Therefore I have these questions:
Is there any way I can remove these graphical artefacts while still maintaining a resizable/scalable board?
Should I rather use some other graphical library like Core Graphics or OpenGL?
Update:
const int GRIDSIZE = 16;
cellWidth = (frame.size.width / GRIDSIZE);
cellHeight = (frame.size.height / GRIDSIZE);
If you want crisp rectangles you need to align coordinates so that they match the underlying pixels. NSView has a method for this purpose: - (NSRect)backingAlignedRect:(NSRect)aRect options:(NSAlignmentOptions)options. Here's a complete example for drawing the grid:
const NSInteger GRIDSIZE = 16;
- (void)drawRect:(NSRect)dirtyRect {
for (NSUInteger x = 0; x < GRIDSIZE; x++) {
for (NSUInteger y = 0; y < GRIDSIZE; y++) {
NSColor *color = (x % 2 == y % 2) ? [NSColor greenColor] : [NSColor redColor];
[color set];
[NSBezierPath fillRect:[self rectOfCellAtColumn:x row:y]];
}
}
}
- (NSRect)rectOfCellAtColumn:(NSUInteger)column row:(NSUInteger)row {
NSRect frame = [self frame];
CGFloat cellWidth = frame.size.width / GRIDSIZE;
CGFloat cellHeight = frame.size.height / GRIDSIZE;
CGFloat x = column * cellWidth;
CGFloat y = row * cellHeight;
NSRect rect = NSMakeRect(x, y, cellWidth, cellHeight);
NSAlignmentOptions alignOpts = NSAlignMinXNearest | NSAlignMinYNearest |
NSAlignMaxXNearest | NSAlignMaxYNearest ;
return [self backingAlignedRect:rect options:alignOpts];
}
Note that you don't need stroke to draw a game board. To draw pixel aligned strokes you need to remember that coordinates in Cocoa actually point to lower left corners of pixels. To crisp lines you need to offset coordinates by half a pixel from integral coordinates so that coordinates point to centers of pixels. For example to draw a crisp border for a grid cell you can do this:
NSRect rect = NSInsetRect([self rectOfCellAtColumn:column row:row], 0.5, 0.5);
[NSBezierPath strokeRect:rect];
First, make sure your stroke color is not black or gray. (You're setting color but is that stroke or fill color? I can never remember.)
Second, what happens if you simply fill with green, then draw red squares over it, or vice-versa?
There are other ways to do what you want, too. You can use the CICheckerboardGenerator to make your background instead.
Alternately, you could also use a CGBitmapContext that you filled by hand.
First of all, if you don't actually want your rectangles to have a border, you shouldn't call [path stroke].
Second, creating a bezier path for filling a rectangle is overkill. You can do the same with NSRectFill(r). This function is probably more efficient and I suspect less prone to introduce rounding errors to your floats – I assume you realize that your floats must not have a fractional part if you want pixel-precise rectangles. I believe that if the width and height of your view is a multiple of GRIDSIZE and you use NSRectFill, the artifacts should go away.
Third, there's the obvious question as to how you want your board drawn if the view's width and height are not a multiple of GRIDSIZE. This is of course not an issue if the size of your view is fixed and a multiple of that constant. If it is not, however, you first have to clarify how you want the possible remainder of the width or height handled. Should there be a border? Should the last cell in the row or column take up the remainder? Or should it rather be distributed equally among the cells of the rows or columns? You might have to accept cells of varying width and/or height. What the best solution for your problem is, depends on your exact requirements.
You might also want to look into other ways of drawing a checkerboard, e.g. using CICheckerboardGenerator or creating a pattern color with an image ([NSColor colorWithPatternImage:yourImage]) and then filling the whole view with it.
There's also the possibility of (temporarily) turning off anti-aliasing. To do that, add the following line to the beginning of your drawing method:
[[NSGraphicsContext currentContext] setShouldAntialias:NO];
My last observation is about your general approach. If your game is going to have more complicated graphics and animations, e.g. animated movement of pieces, you might be better off using OpenGL.
As of iOS 6, you can generate a checkerboard pattern using CICheckerboardGenerator.
You'll want to guard against the force unwraps in here, but here's the basic implementation:
var checkerboardImage: UIImage? {
let filter = CIFilter(name: "CICheckerboardGenerator")!
let width = NSNumber(value: Float(viewSize.width/16))
let center = CIVector(cgPoint: .zero)
let darkColor = CIColor.red
let lightColor = CIColor.green
let sharpness = NSNumber(value: 1.0)
filter.setDefaults()
filter.setValue(width, forKey: "inputWidth")
filter.setValue(center, forKey: "inputCenter")
filter.setValue(darkColor, forKey: "inputColor0")
filter.setValue(lightColor, forKey: "inputColor1")
filter.setValue(sharpness, forKey: "inputSharpness")
let context = CIContext(options: nil)
let cgImage = context.createCGImage(filter.outputImage!, from: viewSize)
let uiImage = UIImage(cgImage: cgImage!, scale: UIScreen.main.scale, orientation: UIImage.Orientation.up)
return uiImage
}
Apple Developer Docs
Your squares overlap. ix + CELLWIDTH is the same coordinate as ix in the next iteration of the loop.
You can fix this by setting the stroke color explicitly to transparent, or by not calling stroke.
[color set];
[[NSColor clearColor] setStroke];
or
[path fill];
// not [path stroke];

Not expanding NSScroller

The NSScroller automatically expands it's width when the user hovers over it.
However, the document view has pretty little space, and this is why the scroller should not expand.
How can I disable this behaviour?
This maybe a little too late, but something like this might help?
1) Create custom scroller for your vertical scrollbar.
2) Override -drawKnob to force draw knob the default size even when it is to be drawn 'expanded'.
-(void)drawKnob
{
NSRect knobSlot = [self rectForPart:NSScrollerKnob];
if(sFlags.isHoriz)
{
knobSlot.size.height = 9;
knobSlot.origin.y = 6;
}
else
{
knobSlot.size.width = 9;
knobSlot.origin.x = 6;
}
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:knobSlot xRadius:5 yRadius:5];
[[NSColor scrollBarColor] set];
[path fill];
}
3) Depending on if you still want the knob slot or not, override -drawKnobSlotInRect:
-(void)drawKnobSlotInRect:(NSRect)slotRect highlight:(BOOL)flag
{
NSRect newRect = slotRect;
if(sFlags.isHoriz)
newRect.origin.y = 4;
else
newRect.origin.x = 4;
[super drawKnobSlotInRect:newRect highlight:flag];
}

Change ImageBrowserCell highlight dimension

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;

UIView set only side borders

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
}

How would you give an Outline View alternating row colors?

I know there is a check box for it in IB but that only gives you the colors White and Blue. How would I make it so that it used different colors?
This article about gradient for TableView (cocoa not cocoa-touch) might give you some pointers how to go about it.
I have found this code to it,
// RGB values for stripe color (light blue)
#define STRIPE_RED (237.0 / 255.0)
#define STRIPE_GREEN (243.0 / 255.0)
#define STRIPE_BLUE (254.0 / 255.0)
static NSColor *sStripeColor = nil;
#implementation …
// This is called after the table background is filled in,
// but before the cell contents are drawn.
// We override it so we can do our own light-blue row stripes a la iTunes.
- (void) highlightSelectionInClipRect:(NSRect)rect {
[self drawStripesInRect:rect];
[super highlightSelectionInClipRect:rect];
}
// This routine does the actual blue stripe drawing,
// filling in every other row of the table with a blue background
// so you can follow the rows easier with your eyes.
- (void) drawStripesInRect:(NSRect)clipRect {
NSRect stripeRect;
float fullRowHeight = [self rowHeight] + [self intercellSpacing].height;
float clipBottom = NSMaxY(clipRect);
int firstStripe = clipRect.origin.y / fullRowHeight;
if (firstStripe % 2 == 0)
firstStripe++; // we're only interested in drawing the stripes
// set up first rect
stripeRect.origin.x = clipRect.origin.x;
stripeRect.origin.y = firstStripe * fullRowHeight;
stripeRect.size.width = clipRect.size.width;
stripeRect.size.height = fullRowHeight;
// set the color
if (sStripeColor == nil)
sStripeColor = [[NSColor colorWithCalibratedRed:STRIPE_RED
green:STRIPE_GREEN
blue:STRIPE_BLUE
alpha:1.0] retain];
[sStripeColor set];
// and draw the stripes
while (stripeRect.origin.y < clipBottom) {
NSRectFill(stripeRect);
stripeRect.origin.y += fullRowHeight * 2.0;
}
}
But I do not know how to Sub-class NSOutlineView. Could some one tell me how I could sub-class NSOutline View?