/*
CGRectExtensions.playground
Created by C. Keith Ray on 2016.08.06.
Copyright © 2016 by C. Keith Ray. All rights reserved.
Permission is granted for anyone to use and modify this
code for any purpose, as long as this copyright message
is retained.
*/
import Cocoa
import CoreGraphics
/* These are the constructors available for CGRect:
CGRect()
CGRect(origin: CGPoint, size: CGSize)
CGRect(x: CGFloat, y: CGFloat, width: CGFloat,
height: CGFloat)
CGRect(x: Double, y: Double, width: Double, height: Double)
CGRect(x: Int, y: Int, width: Int, height: Int)
I'd like to be able to create CGRects given a height
and width, and a center-point. Creating a new constructor
isn't hard, we just add it to an extension.
*/
extension CGRect {
public init(center: CGPoint, size: CGSize) {
// This assumes width and height are positive numbers.
let origin = CGPoint(x: center.x - size.width/2.0,
y: center.y - size.height/2.0)
self.init(origin: origin, size: size)
}
}
let a = CGRect(center: CGPoint(x:25,y:15), size: CGSize(width:20,height:10))
print("a.origin.x = \(a.origin.x)")
print("a.origin.y = \(a.origin.y)")
print("a.size.width = \(a.size.width)")
print("a.size.height = \(a.size.height)")
/* prints:
a.origin.x = 15.0
a.origin.y = 10.0
a.size.width = 20.0
a.size.height = 10.0
*/
/*
We'd also like to get the center of the rectangle easily.
*/
extension CGRect {
public var center: CGPoint {
get { return CGPoint(x: self.midX, y: self.midY) }
}
}
print("a.center = \(a.center)")
// a.center = (25.0, 15.0)
/*
I'd like to be able to get a rectangle that is close to
an existing rectangle, but is a different size.
For example, a taller and thinner rectangle at the same
origin: ("inc" stands for "increment")
let b = a.incHeightBy(5).incWidthBy(-5)
Also, I don't want to modify the original rectangle. Using
existing features we could do this instead:
let b = a // copy a
b.size.height += 5
b.size.width -= 5
but you can see that takes more lines of code.
*/
extension CGRect {
public func incWidthBy(deltaX: CGFloat) -> CGRect {
var result = self.standardized;
result.size.width += deltaX
return result
}
public func incHeightBy(deltaY: CGFloat) -> CGRect {
var result = self.standardized;
result.size.height += deltaY
return result
}
}
/*
We call self.standardized because rectangles can have
negative width and height (which puts the origin at the
opposite corner than usual) but we don't want to deal
with that in our math. The existing CGRect functions
call self.standardized, too.
*/
let b = a.incHeightBy(5).incWidthBy(-5)
print("a = \(a)") // a = (15.0, 10.0, 20.0, 10.0)
print("b = \(b)") // b = (15.0, 10.0, 15.0, 15.0)
/*
I want similar functions to change the origin. A
rectangle with the same width and height, but a
different origin can be gotten by:
let c = a.incXBy(2).incYBy(3)
Again, not changing the original rectangle. An
alternative would be:
let c = a // copy a
a.origin.x += 2
a.origin.y += 3
(Note: "incOriginBy(dx:dy:)" is essentially the
same as the already-existing function
"offsetBy(dx:dy:)")
*/
extension CGRect {
public func incXBy(deltaX: CGFloat) -> CGRect {
var result = self.standardized
result.origin.x += deltaX
return result
}
public func incYBy(deltaY: CGFloat) -> CGRect {
var result = self.standardized
result.origin.y += deltaY
return result
}
}
let c = a.incXBy(2).incYBy(3)
print("a = \(a)") // a = (15.0, 10.0, 20.0, 10.0)
print("c = \(c)") // c = (17.0, 13.0, 20.0, 10.0)
/*
To be complete, we can also increment origin and size
using pairs of floats.
*/
extension CGRect {
public func incOriginBy(dx deltaX: CGFloat,
dy deltaY: CGFloat) -> CGRect {
var result = self.standardized
result.origin.x += deltaX
result.origin.y += deltaY
return result
}
public func incSizeBy(dx deltaX: CGFloat,
dy deltaY: CGFloat) -> CGRect {
var result = self.standardized;
result.size.width += deltaX
result.size.height += deltaY
return result
}
}
let d = CGRect(x: 10, y: 20, width: 30, height: 40)
let e = d.incOriginBy(dx: 5, dy: 10)
let f = d.incSizeBy(dx: 15, dy: 20)
print("d = \(d)") // d = (10.0, 20.0, 30.0, 40.0)
print("e = \(e)") // e = (15.0, 30.0, 30.0, 40.0)
print("f = \(f)") // f = (10.0, 20.0, 45.0, 60.0)
/*
We made center as a getter, but we can also make a
function that returns a new rectangle with a different
center. I'll call that newCenter.
*/
extension CGRect {
public func newCenter(cxy: CGPoint) -> CGRect {
let me = self.standardized
let result = CGRect(center: cxy, size: me.size)
return result
}
}
let g = a.newCenter(CGPoint(x: 50, y: 30))
print("a.center = \(a.center)")
print("a = \(a)")
print("g = \(g)")
print("g.center = \(g.center)")
/* prints
a.center = (25.0, 15.0)
a = (15.0, 10.0, 20.0, 10.0)
g = (40.0, 25.0, 20.0, 10.0)
g.center = (50.0, 30.)
*/
/*
and, last but not least, an overload of newCenter taking
x,y instead of CGPoint.
*/
extension CGRect {
public func newCenter(newX newX: CGFloat, newY: CGFloat)
-> CGRect {
let me = self.standardized
let result = CGRect(center: CGPoint(x: newX, y: newY),
size: me.size)
return result
}
}
let h = a.newCenter(newX: 50, newY: 30)
print("a.center = \(a.center)")
print("a = \(a)")
print("h = \(h)")
print("h.center = \(h.center)")
/* prints
a.center = (25.0, 15.0)
a = (15.0, 10.0, 20.0, 10.0)
h = (40.0, 25.0, 20.0, 10.0)
h.center = (50.0, 30.0)
*/
/*
One CGRect function I don't need to implement is "insetBy",
which reduces the size of a rectangle, but keeps the same
center by altering the origin.
*/
let i = h.insetBy(dx: 3, dy: 3)
print("h = \(h)")
print("i = \(i)")
print("i.center = \(i.center)")
print("h.center = \(h.center)")
/* prints
h = (40.0, 25.0, 20.0, 10.0)
i = (43.0, 28.0, 14.0, 4.0)
i.center = (50.0, 30.0)
h.center = (50.0, 30.0)
*/
//
// CGRectExtensions.swift
//
// Created by C. Keith Ray on 2016.08.06.
// Copyright © 2016 by C. Keith Ray. All rights reserved.
// Permission is granted for anyone to use and modify this
// code for any purpose, as long as this copyright message
// is retained.
//
import Foundation
import CoreGraphics
extension CGRect {
public init(center: CGPoint, size: CGSize) {
let origin = CGPoint(x: center.x - size.width/2.0,
y: center.y - size.height/2.0)
self.init(origin: origin, size: size)
}
public var center: CGPoint {
get { return CGPoint(x: self.midX, y: self.midY) }
}
public func newCenter(cxy: CGPoint) -> CGRect {
let me = self.standardized
let result = CGRect(center: cxy, size: me.size)
return result
}
public func newCenter(newX newX: CGFloat, newY: CGFloat)
-> CGRect {
let me = self.standardized
let result = CGRect(center: CGPoint(x: newX, y: newY),
size: me.size)
return result
}
public func incXBy(deltaX: CGFloat) -> CGRect {
var result = self.standardized
result.origin.x += deltaX
return result
}
public func incYBy(deltaY: CGFloat) -> CGRect {
var result = self.standardized
result.origin.y += deltaY
return result
}
public func incOriginBy(dx deltaX: CGFloat,
dy deltaY: CGFloat) -> CGRect {
var result = self.standardized
result.origin.x += deltaX
result.origin.y += deltaY
return result
}
public func incWidthBy(deltaX: CGFloat) -> CGRect {
var result = self.standardized;
result.size.width += deltaX
return result
}
public func incHeightBy(deltaY: CGFloat) -> CGRect {
var result = self.standardized;
result.size.height += deltaY
return result
}
public func incSizeBy(dx deltaX: CGFloat,
dy deltaY: CGFloat) -> CGRect {
var result = self.standardized;
result.size.width += deltaX
result.size.height += deltaY
return result
}
}
//
// CGRectExtensionsTests.swift
//
// Created by C. Keith Ray on 2016.08.06.
// Permission is granted for anyone to use and modify this
// code for any purpose, as long as this copyright message
// is retained.
import XCTest
class CGRectExtensionsTests: XCTestCase {
override func setUp() {
super.setUp()
}
override func tearDown() {
super.tearDown()
}
func test_init_center() {
let result = CGRect(center: CGPoint(
x: 120.0, y: 100.0), size: CGSize(width:
6.0, height: 4.0))
let expected = CGRect(x: 120 - 3, y: 100 - 2,
width: 6.0, height: 4.0)
XCTAssertEqual(result, expected)
}
func test_center() {
let input = CGRect(x: 10.0, y: 20.0, width:
100.0, height: 200.0)
let result = input.center
let expected = CGPoint(x: 60.0, y: 120.0)
XCTAssertEqual(result, expected)
}
func test_incXBy5() {
let input = CGRect(x: 10.0, y: 20.0, width:
100.0, height: 200.0)
let result = input.incXBy(5.0)
let expected = CGRect(x: 15.0, y: 20.0,
width: 100.0, height: 200.0)
XCTAssertEqual(result, expected)
}
func test_incXByMinus5() {
let input = CGRect(x: 10.0, y: 20.0, width:
100.0, height: 200.0)
let result = input.incXBy(-5.0)
let expected = CGRect(x: 5.0, y: 20.0,
width: 100.0, height: 200.0)
XCTAssertEqual(result, expected)
}
func test_incYBy5() {
let input = CGRect(x: 10.0, y: 20.0, width:
100.0, height: 200.0)
let result = input.incYBy(5.0)
let expected = CGRect(x: 10.0, y: 25.0,
width: 100.0, height: 200.0)
XCTAssertEqual(result, expected)
}
func test_incYByMinus5() {
let input = CGRect(x: 10.0, y: 20.0, width:
100.0, height: 200.0)
let result = input.incYBy(-5.0)
let expected = CGRect(x: 10.0, y: 15.0,
width: 100.0, height: 200.0)
XCTAssertEqual(result, expected)
}
func test_incOriginBy() {
let input = CGRect(x: 10.0, y: 20.0, width:
100.0, height: 200.0)
let result = input.incOriginBy(dx: 5.0, dy: 6.0)
let expected = CGRect(x: 15.0, y: 26.0,
width: 100.0, height: 200.0)
XCTAssertEqual(result, expected)
}
func test_incWidthBy() {
let input = CGRect(x: 10.0, y: 20.0, width:
100.0, height: 200.0)
let result = input.incWidthBy(5.0)
let expected = CGRect(x: 10.0, y: 20.0,
width: 105.0, height: 200.0)
XCTAssertEqual(result, expected)
}
func test_incHeightBy() {
let input = CGRect(x: 10.0, y: 20.0, width:
100.0, height: 200.0)
let result = input.incHeightBy(5.0)
let expected = CGRect(x: 10.0, y: 20.0,
width: 100.0, height: 205.0)
XCTAssertEqual(result, expected)
}
func test_incSizeBy() {
let input = CGRect(x: 10.0, y: 20.0, width:
100.0, height: 200.0)
let result = input.incSizeBy(dx: 5.0, dy: 4.0)
let expected = CGRect(x: 10.0, y: 20.0,
width: 105.0, height: 204.0)
XCTAssertEqual(result, expected)
}
}