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.

Great example! Here's the gist with the code updated for Xcode 9 and Swift 4.
GitHub

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