Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
An IBInspectable Calculator Construction Set
// CalculatorView.swift
// as seen in http://nshipster.com/ibinspectable-ibdesignable/
//
// (c) 2015 Nate Cook, licensed under the MIT license
/// The alignment for drawing an String inside a bounding rectangle.
enum NCStringAlignment {
case LeftTop
case CenterTop
case RightTop
case LeftCenter
case Center
case RightCenter
case LeftBottom
case CenterBottom
case RightBottom
}
extension String {
/// Draw the `String` inside the bounding rectangle with a given alignment.
func drawAtPointInRect(rect: CGRect, withAttributes attributes: [String: AnyObject]?, andAlignment alignment: NCStringAlignment) {
let size = self.sizeWithAttributes(attributes)
let x, y: CGFloat
switch alignment {
case .LeftTop, .LeftCenter, .LeftBottom:
x = CGRectGetMinX(rect)
case .CenterTop, .Center, .CenterBottom:
x = CGRectGetMidX(rect) - size.width / 2
case .RightTop, .RightCenter, .RightBottom:
x = CGRectGetMaxX(rect) - size.width
}
switch alignment {
case .LeftTop, .CenterTop, .RightTop:
y = CGRectGetMinY(rect)
case .LeftCenter, .Center, .RightCenter:
y = CGRectGetMidY(rect) - size.height / 2
case .LeftBottom, .CenterBottom, .RightBottom:
y = CGRectGetMaxY(rect) - size.height
}
self.drawAtPoint(CGPoint(x: x, y: y), withAttributes: attributes)
}
}
extension CGRect {
/// Convert a rect to a pixel-aligned version, rounding position and size
func pixelAligned() -> CGRect {
return CGRect(x: round(origin.x), y: round(origin.y),
width: round(size.width), height: round(size.height))
}
}
@IBDesignable
class CalculatorView: UIView {
let (columns, rows) = (4, 6) as (CGFloat, CGFloat)
// basic buttons: all 1x1 unit
let buttonRows = [ ["C", "=", "/", "*"], ["7", "8", "9", "-"], ["4", "5", "6", "+"], ["1", "2", "3"] ]
// special cases: non-square or out of position
let specialButtons = [("=", CGPoint(x: 3, y: 4), CGSize(width: 1, height: 2)),
("0", CGPoint(x: 0, y: 5), CGSize(width: 2, height: 1)),
(".", CGPoint(x: 2, y: 5), CGSize(width: 1, height: 1))]
// MARK: - IBInspectable
@IBInspectable var faceColor: UIColor = UIColor(red: 211/255, green: 211/255, blue: 211/255, alpha: 1)
@IBInspectable var textColor: UIColor = UIColor.blackColor()
@IBInspectable var textSize: CGFloat = 12
@IBInspectable var buttonColor: UIColor = UIColor.whiteColor()
@IBInspectable var edgeColor: UIColor = UIColor.blackColor()
@IBInspectable var edgeWidth: CGFloat = 1
@IBInspectable var shadowColor: UIColor = UIColor.blackColor()
@IBInspectable var shadowOffset: CGFloat = 2
@IBInspectable var cornerRadius: CGFloat = 7
@IBInspectable var paddingX: CGFloat = 7
@IBInspectable var paddingY: CGFloat = 8
@IBInspectable var spacingX: CGFloat = 3
@IBInspectable var spacingY: CGFloat = 5
@IBInspectable var titleHeight: CGFloat = 20
@IBInspectable var titleColor: UIColor = UIColor.whiteColor()
@IBInspectable var titleBarColor: UIColor = UIColor.blackColor()
// MARK: - Drawing
override func drawRect(rect: CGRect) {
// initial drawing setup
// determine unit height/width from view size, padding and button spacing
let unitHeight = (bounds.height - 2 * paddingY - (rows - 1) * spacingY - titleHeight) / rows
let unitWidth = (bounds.width - 2 * paddingX - (columns - 1) * spacingX) / columns
// set up text attributes
let textFont = UIFont.systemFontOfSize(textSize)
let textAttributes = [NSFontAttributeName: textFont, NSForegroundColorAttributeName: textColor]
// get the graphics context
let context = UIGraphicsGetCurrentContext()
// helper functions
// maps a point and size (in units) to an actual rect in the view's coordinates for drawing
func rectForPosition(position: CGPoint, andSize size: CGSize) -> CGRect {
let x = paddingX + unitWidth * position.x + spacingX * position.x
let y = titleHeight + paddingY + unitHeight * position.y + spacingY * position.y
let width = size.width * unitWidth + (size.width - 1) * spacingX
let height = size.height * unitHeight + (size.height - 1) * spacingY
return CGRect(x: x, y: y, width: width, height: height).pixelAligned()
}
// draw a button in the specified rect
func drawButtonInRect(rect: CGRect, withText text: String) {
shadowColor.setFill()
UIRectFill(rect.offsetBy(dx: shadowOffset, dy: shadowOffset))
buttonColor.setFill()
edgeColor.setStroke()
CGContextSetLineWidth(context, edgeWidth)
UIRectFill(rect)
UIRectFrame(rect)
var textRect = rect
if textRect.width != unitWidth {
textRect.size.width = unitWidth
}
if textRect.height != unitHeight {
textRect.origin.y = textRect.maxY - unitHeight
textRect.size.height = unitHeight
}
text.drawAtPointInRect(textRect, withAttributes: textAttributes, andAlignment: .Center)
}
// draw the display
func drawDisplayInRect(rect: CGRect, withText text: String) {
buttonColor.setFill()
edgeColor.setStroke()
CGContextSetLineWidth(context, edgeWidth)
UIRectFill(rect)
UIRectFrame(rect)
text.drawAtPointInRect(rect.offsetBy(dx: unitWidth / -3, dy: 0), withAttributes: textAttributes, andAlignment: .RightCenter)
}
// calculator "window" - paint the color of the title bar
let window = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
titleBarColor.setFill()
window.fill()
// center the title in the title bar
let title = "Calculator"
let (titleRect, _) = bounds.divide(titleHeight, fromEdge: CGRectEdge.MinYEdge)
let titleAttributes = [NSFontAttributeName: UIFont.boldSystemFontOfSize(textSize + 2), NSForegroundColorAttributeName: titleColor]
title.drawAtPointInRect(titleRect, withAttributes: titleAttributes, andAlignment: .Center)
// calculator "face" - covers most of the window
let face = UIBezierPath(roundedRect: bounds.insetBy(dx: 0, dy: titleHeight / 2).offsetBy(dx: 0, dy: titleHeight / 2), cornerRadius: cornerRadius)
faceColor.setFill()
face.fill()
// draw the display
let displayRect = rectForPosition(CGPoint(x: 0, y: 0), andSize: CGSize(width: 4, height: 1))
drawDisplayInRect(displayRect, withText: "3")
// draw basic buttons
for (rowNumber, row) in buttonRows.enumerate() {
for (columnNumber, text) in row.enumerate() {
drawButtonInRect(rectForPosition(CGPoint(x: columnNumber, y: rowNumber + 1), andSize: CGSize(width: 1, height: 1)), withText: text)
}
}
// draw special buttons
for buttonInfo in specialButtons {
drawButtonInRect(rectForPosition(buttonInfo.1, andSize: buttonInfo.2), withText: buttonInfo.0)
}
}
}

JakeLin commented Oct 16, 2015

I have updated the app to use Xcode 7 and Swift 2. https://github.com/JakeLin/IBCalculator

jiro9611 commented Nov 8, 2016

Both of you are awesome, thanks for this demo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment