Skip to content

Instantly share code, notes, and snippets.

@natecook1000
Last active June 6, 2022 01:00
Show Gist options
  • Star 72 You must be signed in to star a gist
  • Fork 17 You must be signed in to fork a gist
  • Save natecook1000/4269059121ec247fbb90 to your computer and use it in GitHub Desktop.
Save natecook1000/4269059121ec247fbb90 to your computer and use it in GitHub Desktop.
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
Copy link

JakeLin commented Oct 16, 2015

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

@wangdu1005
Copy link

Both of you are awesome, thanks for this demo.

@nahuelDeveloper
Copy link

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

@guti7
Copy link

guti7 commented Aug 2, 2020

Just read the NSHipster entry! Thanks!

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