From 2b82be3d1260af8f60b21e32cde48b8c32041b30 Mon Sep 17 00:00:00 2001 From: Bartek Chlebek Date: Sun, 6 Aug 2017 14:50:34 +0200 Subject: [PATCH 1/2] Some missing CGRect APIs implemented --- Foundation/NSGeometry.swift | 158 +++++++ TestFoundation/TestNSGeometry.swift | 673 ++++++++++++++++++++++++++++ 2 files changed, 831 insertions(+) diff --git a/Foundation/NSGeometry.swift b/Foundation/NSGeometry.swift index 80c8d25b97..62eefd96ea 100644 --- a/Foundation/NSGeometry.swift +++ b/Foundation/NSGeometry.swift @@ -237,6 +237,164 @@ extension CGRect { y: -CGFloat.greatestFiniteMagnitude / 2, width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) + + public var width: CGFloat { return abs(self.size.width) } + public var height: CGFloat { return abs(self.size.height) } + + public var minX: CGFloat { return self.origin.x + min(self.size.width, 0) } + public var midX: CGFloat { return (self.minX + self.maxX) * 0.5 } + public var maxX: CGFloat { return self.origin.x + max(self.size.width, 0) } + + public var minY: CGFloat { return self.origin.y + min(self.size.height, 0) } + public var midY: CGFloat { return (self.minY + self.maxY) * 0.5 } + public var maxY: CGFloat { return self.origin.y + max(self.size.height, 0) } + + public var isEmpty: Bool { return self.isNull || self.size.width == 0 || self.size.height == 0 } + public var isInfinite: Bool { return self == .infinite } + public var isNull: Bool { return self.origin.x == .infinity || self.origin.y == .infinity } + + public func contains(_ point: CGPoint) -> Bool { + if self.isNull || self.isEmpty { return false } + + return (self.minX.. Bool { + return self.union(rect2) == self + } + + public var standardized: CGRect { + if self.isNull { return .null } + + return CGRect(x: self.minX, + y: self.minY, + width: self.width, + height: self.height) + } + + public var integral: CGRect { + if self.isNull { return self } + + let standardized = self.standardized + let x = standardized.origin.x.rounded(.down) + let y = standardized.origin.y.rounded(.down) + let width = (standardized.origin.x + standardized.size.width).rounded(.up) - x + let height = (standardized.origin.y + standardized.size.height).rounded(.up) - y + return CGRect(x: x, y: y, width: width, height: height) + } + + public func insetBy(dx: CGFloat, dy: CGFloat) -> CGRect { + if self.isNull { return self } + + var rect = self.standardized + + rect.origin.x += dx + rect.origin.y += dy + rect.size.width -= 2 * dx + rect.size.height -= 2 * dy + + if rect.size.width < 0 || rect.size.height < 0 { + return .null + } + + return rect + } + + public func union(_ r2: CGRect) -> CGRect { + if self.isNull { + return r2 + } + else if r2.isNull { + return self + } + + let rect1 = self.standardized + let rect2 = r2.standardized + + let minX = min(rect1.minX, rect2.minX) + let minY = min(rect1.minY, rect2.minY) + let maxX = max(rect1.maxX, rect2.maxX) + let maxY = max(rect1.maxY, rect2.maxY) + + return CGRect(x: minX, + y: minY, + width: maxX - minX, + height: maxY - minY) + } + + public func intersection(_ r2: CGRect) -> CGRect { + if self.isNull || r2.isNull { return .null } + + let rect1 = self.standardized + let rect2 = r2.standardized + + let rect1SpanH = rect1.minX...rect1.maxX + let rect1SpanV = rect1.minY...rect1.maxY + + let rect2SpanH = rect2.minX...rect2.maxX + let rect2SpanV = rect2.minY...rect2.maxY + + if !rect1SpanH.overlaps(rect2SpanH) || !rect1SpanV.overlaps(rect2SpanV) { + return .null + } + + let overlapH = rect1SpanH.clamped(to: rect2SpanH) + let overlapV = rect1SpanV.clamped(to: rect2SpanV) + + return CGRect(x: overlapH.lowerBound, + y: overlapV.lowerBound, + width: overlapH.upperBound - overlapH.lowerBound, + height: overlapV.upperBound - overlapV.lowerBound) + } + + public func intersects(_ r2: CGRect) -> Bool { + return !self.intersection(r2).isNull + } + + public func offsetBy(dx: CGFloat, dy: CGFloat) -> CGRect { + if self.isNull { return self } + + var rect = self.standardized + rect.origin.x += dx + rect.origin.y += dy + return rect + } + + public func divided(atDistance: CGFloat, from fromEdge: CGRectEdge) -> (slice: CGRect, remainder: CGRect) { + if self.isNull { return (.null, .null) } + + let splitLocation: CGFloat + switch fromEdge { + case .minXEdge: splitLocation = min(max(atDistance, 0), self.width) + case .maxXEdge: splitLocation = min(max(self.width - atDistance, 0), self.width) + case .minYEdge: splitLocation = min(max(atDistance, 0), self.height) + case .maxYEdge: splitLocation = min(max(self.height - atDistance, 0), self.height) + } + + let rect = self.standardized + var rect1 = rect + var rect2 = rect + + switch fromEdge { + case .minXEdge: fallthrough + case .maxXEdge: + rect1.size.width = splitLocation + rect2.origin.x = rect1.maxX + rect2.size.width = rect.width - splitLocation + case .minYEdge: fallthrough + case .maxYEdge: + rect1.size.height = splitLocation + rect2.origin.y = rect1.maxY + rect2.size.height = rect.height - splitLocation + } + + switch fromEdge { + case .minXEdge: fallthrough + case .minYEdge: return (rect1, rect2) + case .maxXEdge: fallthrough + case .maxYEdge: return (rect2, rect1) + } + } } extension CGRect: Equatable { diff --git a/TestFoundation/TestNSGeometry.swift b/TestFoundation/TestNSGeometry.swift index 936743146e..bc10e42311 100644 --- a/TestFoundation/TestNSGeometry.swift +++ b/TestFoundation/TestNSGeometry.swift @@ -17,6 +17,28 @@ import SwiftXCTest #endif +private func assertEqual(_ rect: CGRect, + x: CGFloat, + y: CGFloat, + width: CGFloat, + height: CGFloat, + accuracy: CGFloat? = nil, + _ message: @autoclosure () -> String = "", + file: StaticString = #file, + line: UInt = #line) { + + if let accuracy = accuracy { + XCTAssertEqual(rect.origin.x, x, accuracy: accuracy, message, file: file, line: line) + XCTAssertEqual(rect.origin.y, y, accuracy: accuracy, message, file: file, line: line) + XCTAssertEqual(rect.size.width, width, accuracy: accuracy, message, file: file, line: line) + XCTAssertEqual(rect.size.height, height, accuracy: accuracy, message, file: file, line: line) + } else { + XCTAssertEqual(rect.origin.x, x, message, file: file, line: line) + XCTAssertEqual(rect.origin.y, y, message, file: file, line: line) + XCTAssertEqual(rect.size.width, width, message, file: file, line: line) + XCTAssertEqual(rect.size.height, height, message, file: file, line: line) + } +} class TestNSGeometry : XCTestCase { @@ -33,6 +55,20 @@ class TestNSGeometry : XCTestCase { ("test_CGRect_BasicConstruction", test_CGRect_BasicConstruction), ("test_CGRect_ExtendedConstruction", test_CGRect_ExtendedConstruction), ("test_CGRect_SpecialValues", test_CGRect_SpecialValues), + ("test_CGRect_IsNull", test_CGRect_IsNull), + ("test_CGRect_IsInfinite", test_CGRect_IsInfinite), + ("test_CGRect_IsEmpty", test_CGRect_IsEmpty), + ("test_CGRect_CalculatedGeometricProperties", test_CGRect_CalculatedGeometricProperties), + ("test_CGRect_Standardized", test_CGRect_Standardized), + ("test_CGRect_Integral", test_CGRect_Integral), + ("test_CGRect_ContainsPoint", test_CGRect_ContainsPoint), + ("test_CGRect_ContainsRect", test_CGRect_ContainsRect), + ("test_CGRect_Union", test_CGRect_Union), + ("test_CGRect_Intersection", test_CGRect_Intersection), + ("test_CGRect_Intersects", test_CGRect_Intersects), + ("test_CGRect_OffsetBy", test_CGRect_OffsetBy), + ("test_CGRect_Divide", test_CGRect_Divide), + ("test_CGRect_InsetBy", test_CGRect_InsetBy), ("test_NSEdgeInsets_BasicConstruction", test_NSEdgeInsets_BasicConstruction), ("test_NSEdgeInsetsEqual", test_NSEdgeInsetsEqual), ("test_NSMakePoint", test_NSMakePoint), @@ -196,6 +232,643 @@ class TestNSGeometry : XCTestCase { XCTAssertEqual(r4.size.width, CGFloat(3)) XCTAssertEqual(r4.size.height, CGFloat(4)) } + + func test_CGRect_IsNull() { + XCTAssertTrue(CGRect.null.isNull) + XCTAssertTrue(CGRect(x: CGFloat.infinity, y: CGFloat.infinity, width: 0, height: 0).isNull) + XCTAssertTrue(CGRect(x: CGFloat.infinity, y: CGFloat.infinity, width: 10, height: 10).isNull) + XCTAssertTrue(CGRect(x: CGFloat.infinity, y: CGFloat.infinity, width: -10, height: -10).isNull) + XCTAssertTrue(CGRect(x: CGFloat.infinity, y: 0, width: 0, height: 0).isNull) + XCTAssertTrue(CGRect(x: 0, y: CGFloat.infinity, width: 0, height: 0).isNull) + XCTAssertFalse(CGRect(x: 0, y: 0, width: 0, height: 0).isNull) + } + + func test_CGRect_IsInfinite() { + XCTAssertTrue(CGRect.infinite.isInfinite) + + XCTAssertFalse(CGRect(x: 0, + y: CGRect.infinite.origin.y, + width: CGRect.infinite.size.width, + height: CGRect.infinite.size.height).isInfinite) + + XCTAssertFalse(CGRect(x: CGRect.infinite.origin.x, + y: 0, + width: CGRect.infinite.size.width, + height: CGRect.infinite.size.height).isInfinite) + + XCTAssertFalse(CGRect(x: CGRect.infinite.origin.x, + y: CGRect.infinite.origin.y, + width: 0, + height: CGRect.infinite.size.height).isInfinite) + + XCTAssertFalse(CGRect(x: CGRect.infinite.origin.x, + y: CGRect.infinite.origin.y, + width: CGRect.infinite.size.width, + height: 0).isInfinite) + + XCTAssertFalse(CGRect(x: CGFloat.infinity, + y: CGFloat.infinity, + width: CGFloat.infinity, + height: CGFloat.infinity).isInfinite) + + XCTAssertFalse(CGRect.null.isInfinite) + } + + func test_CGRect_IsEmpty() { + XCTAssertTrue(CGRect.zero.isEmpty) + XCTAssertTrue(CGRect.null.isEmpty) + XCTAssertTrue(CGRect(x: 10, y: 20, width: 30, height: 0).isEmpty) + XCTAssertTrue(CGRect(x: 10, y: 20, width: 0, height: 30).isEmpty) + XCTAssertTrue(CGRect(x: 10, y: 20, width: -30, height: 0).isEmpty) + XCTAssertTrue(CGRect(x: 10, y: 20, width: 0, height: -30).isEmpty) + + var r1 = CGRect.null + r1.origin.x = 0 + XCTAssertTrue(r1.isEmpty) + + var r2 = CGRect.null + r2.origin.y = 0 + XCTAssertTrue(r2.isEmpty) + + var r3 = CGRect.null + r3.size.width = 20 + XCTAssertTrue(r3.isEmpty) + + var r4 = CGRect.null + r4.size.height = 20 + XCTAssertTrue(r4.isEmpty) + + var r5 = CGRect.null + r5.size.width = 20 + r5.size.height = 20 + XCTAssertTrue(r5.isEmpty) + + XCTAssertFalse(CGRect.infinite.isEmpty) + XCTAssertFalse(CGRect.infinite.isEmpty) + } + + func test_CGRect_CalculatedGeometricProperties() { + let ε = CGFloat(0.00001) + + let r1 = CGRect(x: 1.2, y: 3.4, width: 5.6, height: 7.8) + XCTAssertEqual(r1.width, 5.6, accuracy: ε) + XCTAssertEqual(r1.height, 7.8, accuracy: ε) + + XCTAssertEqual(r1.minX, 1.2, accuracy: ε) + XCTAssertEqual(r1.midX, 4, accuracy: ε) + XCTAssertEqual(r1.maxX, 6.8, accuracy: ε) + + XCTAssertEqual(r1.minY, 3.4, accuracy: ε) + XCTAssertEqual(r1.midY, 7.3, accuracy: ε) + XCTAssertEqual(r1.maxY, 11.2, accuracy: ε) + + let r2 = CGRect(x: -1.2, y: -3.4, width: 5.6, height: 7.8) + XCTAssertEqual(r2.width, 5.6, accuracy: ε) + XCTAssertEqual(r2.height, 7.8, accuracy: ε) + + XCTAssertEqual(r2.minX, -1.2, accuracy: ε) + XCTAssertEqual(r2.midX, 1.6, accuracy: ε) + XCTAssertEqual(r2.maxX, 4.4, accuracy: ε) + + XCTAssertEqual(r2.minY, -3.4, accuracy: ε) + XCTAssertEqual(r2.midY, 0.5, accuracy: ε) + XCTAssertEqual(r2.maxY, 4.4, accuracy: ε) + + let r3 = CGRect(x: 1.2, y: 3.4, width: -5.6, height: -7.8) + XCTAssertEqual(r3.width, 5.6, accuracy: ε) + XCTAssertEqual(r3.height, 7.8, accuracy: ε) + + XCTAssertEqual(r3.minX, -4.4, accuracy: ε) + XCTAssertEqual(r3.midX, -1.6, accuracy: ε) + XCTAssertEqual(r3.maxX, 1.2, accuracy: ε) + + XCTAssertEqual(r3.minY, -4.4, accuracy: ε) + XCTAssertEqual(r3.midY, -0.5, accuracy: ε) + XCTAssertEqual(r3.maxY, 3.4, accuracy: ε) + + let r4 = CGRect(x: -1.2, y: -3.4, width: -5.6, height: -7.8) + XCTAssertEqual(r4.width, 5.6, accuracy: ε) + XCTAssertEqual(r4.height, 7.8, accuracy: ε) + + XCTAssertEqual(r4.minX, -6.8, accuracy: ε) + XCTAssertEqual(r4.midX, -4.0, accuracy: ε) + XCTAssertEqual(r4.maxX, -1.2, accuracy: ε) + + XCTAssertEqual(r4.minY, -11.2, accuracy: ε) + XCTAssertEqual(r4.midY, -7.3, accuracy: ε) + XCTAssertEqual(r4.maxY, -3.4, accuracy: ε) + } + + func test_CGRect_Standardized() { + let ε = CGFloat(0.00001) + let nullX = CGRect.null.origin.x + let nullY = CGRect.null.origin.y + let nullWidth = CGRect.null.size.width + let nullHeight = CGRect.null.size.height + + let r1 = CGRect(x: 1.9, y: 1.9, width: 10.1, height: 10.2).standardized + assertEqual(r1, x: 1.9, y: 1.9, width: 10.1, height: 10.2, accuracy: ε) + + let r2 = CGRect(x: 1.9, y: 1.9, width: -10.1, height: -10.2).standardized + assertEqual(r2, x: -8.2, y: -8.3, width: 10.1, height: 10.2, accuracy: ε) + + let r3 = CGRect(x: -1.9, y: -1.9, width: 10.1, height: 10.2).standardized + assertEqual(r3, x: -1.9, y: -1.9, width: 10.1, height: 10.2, accuracy: ε) + + let r4 = CGRect(x: -1.9, y: -1.9, width: -10.1, height: -10.2).standardized + assertEqual(r4, x: -12, y: -12.1, width: 10.1, height: 10.2, accuracy: ε) + + let r5 = CGRect.null.standardized + assertEqual(r5, x: nullX, y: nullY, width: nullWidth, height: nullHeight) + + var r6 = CGRect.null + r6.size = CGSize(width: 10, height: 20) + r6 = r6.standardized + assertEqual(r6, x: nullX, y: nullY, width: nullWidth, height: nullHeight) + + var r7 = CGRect.null + r7.size = CGSize(width: -10, height: -20) + r7 = r7.standardized + assertEqual(r7, x: nullX, y: nullY, width: nullWidth, height: nullHeight) + + var r8 = CGRect.null + r8.origin.x = 20 + r8 = r8.standardized + assertEqual(r8, x: nullX, y: nullY, width: nullWidth, height: nullHeight) + + var r9 = CGRect.null + r9.origin.y = 20 + r9 = r9.standardized + assertEqual(r9, x: nullX, y: nullY, width: nullWidth, height: nullHeight) + + var r10 = CGRect.null + r10.origin = CGPoint(x: 10, y: 20) + r10 = r10.standardized + assertEqual(r10, x: 10, y: 20, width: 0, height: 0) + } + + func test_CGRect_Integral() { + let ε = CGFloat(0.00001) + + let r1 = CGRect(x: 1.9, y: 1.9, width: 10.1, height: 10.2).integral + XCTAssertEqual(r1.origin.x, 1, accuracy: ε) + XCTAssertEqual(r1.origin.y, 1, accuracy: ε) + XCTAssertEqual(r1.size.width, 11, accuracy: ε) + XCTAssertEqual(r1.size.height, 12, accuracy: ε) + + let r2 = CGRect(x: 1.9, y: 1.9, width: -10.1, height: -10.2).integral + XCTAssertEqual(r2.origin.x, -9, accuracy: ε) + XCTAssertEqual(r2.origin.y, -9, accuracy: ε) + XCTAssertEqual(r2.size.width, 11, accuracy: ε) + XCTAssertEqual(r2.size.height, 11, accuracy: ε) + + let r3 = CGRect(x: -1.9, y: -1.9, width: 10.1, height: 10.2).integral + XCTAssertEqual(r3.origin.x, -2, accuracy: ε) + XCTAssertEqual(r3.origin.y, -2, accuracy: ε) + XCTAssertEqual(r3.size.width, 11, accuracy: ε) + XCTAssertEqual(r3.size.height, 11, accuracy: ε) + + let r4 = CGRect(x: -1.9, y: -1.9, width: -10.1, height: -10.2).integral + XCTAssertEqual(r4.origin.x, -12, accuracy: ε) + XCTAssertEqual(r4.origin.y, -13, accuracy: ε) + XCTAssertEqual(r4.size.width, 11, accuracy: ε) + XCTAssertEqual(r4.size.height, 12, accuracy: ε) + + let r5 = CGRect.null.integral + XCTAssertEqual(r5.origin.x, CGRect.null.origin.x) + XCTAssertEqual(r5.origin.y, CGRect.null.origin.y) + XCTAssertEqual(r5.size.width, CGRect.null.size.width) + XCTAssertEqual(r5.size.height, CGRect.null.size.height) + + var r6 = CGRect.null + r6.origin.x = 10 + r6.size = CGSize(width: -20, height: -30) + r6 = r6.integral + XCTAssertEqual(r6.origin.x, 10) + XCTAssertEqual(r6.origin.y, CGRect.null.origin.y) + XCTAssertEqual(r6.size.width, -20) + XCTAssertEqual(r6.size.height, -30) + + var r7 = CGRect.null + r7.origin.y = 10 + r7.size = CGSize(width: -20, height: -30) + r7 = r7.integral + XCTAssertEqual(r7.origin.x, CGRect.null.origin.y) + XCTAssertEqual(r7.origin.y, 10) + XCTAssertEqual(r7.size.width, -20) + XCTAssertEqual(r7.size.height, -30) + + var r8 = CGRect.null + r8.origin = CGPoint(x: 10, y: 20) + r8.size = CGSize(width: -30, height: -40) + r8 = r8.integral + XCTAssertEqual(r8.origin.x, -20) + XCTAssertEqual(r8.origin.y, -20) + XCTAssertEqual(r8.size.width, 30) + XCTAssertEqual(r8.size.height, 40) + } + + func test_CGRect_ContainsPoint() { + XCTAssertFalse(CGRect.null.contains(CGPoint())) + XCTAssertFalse(CGRect.zero.contains(CGPoint())) + + let r1 = CGRect(x: 5, y: 5, width: 10, height: 10) + XCTAssertFalse(r1.contains(CGPoint(x: 1, y: 2))) + XCTAssertFalse(r1.contains(CGPoint(x: 7, y: 2))) + XCTAssertFalse(r1.contains(CGPoint(x: 2, y: 7))) + XCTAssertFalse(r1.contains(CGPoint(x: -7, y: -7))) + XCTAssertFalse(r1.contains(CGPoint(x: 15, y: 15))) + XCTAssertTrue(r1.contains(CGPoint(x: 7, y: 7))) + XCTAssertTrue(r1.contains(CGPoint(x: 10, y: 10))) + XCTAssertTrue(r1.contains(CGPoint(x: 5, y: 5))) + + let r2 = CGRect(x: -5, y: -5, width: -10, height: -10) + XCTAssertFalse(r2.contains(CGPoint(x: -1, y: -2))) + XCTAssertFalse(r2.contains(CGPoint(x: -7, y: -2))) + XCTAssertFalse(r2.contains(CGPoint(x: -2, y: -7))) + XCTAssertFalse(r2.contains(CGPoint(x: 7, y: 7))) + XCTAssertFalse(r2.contains(CGPoint(x: -5, y: -5))) + XCTAssertTrue(r2.contains(CGPoint(x: -7, y: -7))) + XCTAssertTrue(r2.contains(CGPoint(x: -10, y: -10))) + XCTAssertTrue(r2.contains(CGPoint(x: -15, y: -15))) + + XCTAssertTrue(CGRect.infinite.contains(CGPoint())) + } + + func test_CGRect_ContainsRect() { + XCTAssertFalse(CGRect.zero.contains(.infinite)) + XCTAssertTrue(CGRect.zero.contains(.null)) + XCTAssertTrue(CGRect.zero.contains(CGRect.zero)) + XCTAssertFalse(CGRect.zero.contains(CGRect(x: -1.2, y: -3.4, width: 5.6, height: 7.8))) + + XCTAssertFalse(CGRect.null.contains(.infinite)) + XCTAssertTrue(CGRect.null.contains(.null)) + XCTAssertFalse(CGRect.null.contains(CGRect.zero)) + XCTAssertFalse(CGRect.null.contains(CGRect(x: -1.2, y: -3.4, width: 5.6, height: 7.8))) + + XCTAssertTrue(CGRect.infinite.contains(.infinite)) + XCTAssertTrue(CGRect.infinite.contains(.null)) + XCTAssertTrue(CGRect.infinite.contains(CGRect.zero)) + XCTAssertTrue(CGRect.infinite.contains(CGRect(x: -1.2, y: -3.4, width: 5.6, height: 7.8))) + + let r1 = CGRect(x: 10, y: 20, width: 30, height: 40) + XCTAssertTrue(r1.contains(r1)) + + let r2 = CGRect(x: -10, y: -20, width: -30, height: -40) + XCTAssertTrue(r2.contains(r2)) + + let r3 = CGRect(x: -10, y: -20, width: 30, height: 40) + let r4 = CGRect(x: 20, y: 20, width: -30, height: -40) + XCTAssertTrue(r3.contains(r4)) + + let r5 = CGRect(x: -10, y: -10, width: 20, height: 20) + let r6 = CGRect(x: -5, y: -5, width: 10, height: 10) + XCTAssertTrue(r5.contains(r6)) + XCTAssertFalse(r6.contains(r5)) + } + + func test_CGRect_Union() { + let r1 = CGRect.null + let r2 = CGRect.null + let u1 = r1.union(r2) + XCTAssertEqual(u1.origin.x, CGRect.null.origin.x) + XCTAssertEqual(u1.origin.y, CGRect.null.origin.y) + XCTAssertEqual(u1.size.width, CGRect.null.size.width) + XCTAssertEqual(u1.size.height, CGRect.null.size.height) + + let r3 = CGRect.null + var r4 = CGRect.null + r4.size = CGSize(width: 10, height: 20) + let u2 = r3.union(r4) + XCTAssertEqual(u2.origin.x, r4.origin.x) + XCTAssertEqual(u2.origin.y, r4.origin.y) + XCTAssertEqual(u2.size.width, r4.size.width) + XCTAssertEqual(u2.size.height, r4.size.height) + + let u3 = r4.union(r3) + XCTAssertEqual(u3.origin.x, r3.origin.x) + XCTAssertEqual(u3.origin.y, r3.origin.y) + XCTAssertEqual(u3.size.width, r3.size.width) + XCTAssertEqual(u3.size.height, r3.size.height) + + let r5 = CGRect(x: -1.2, y: -3.4, width: -5.6, height: -7.8) + let r6 = CGRect(x: 1.2, y: 3.4, width: 5.6, height: 7.8) + let u4 = r5.union(r6) + XCTAssertEqual(u4.origin.x, -6.8) + XCTAssertEqual(u4.origin.y, -11.2) + XCTAssertEqual(u4.size.width, 13.6) + XCTAssertEqual(u4.size.height, 22.4) + + let r7 = CGRect(x: 1, y: 2, width: 3, height: 4) + let r8 = CGRect.infinite + let u5 = r7.union(r8) + XCTAssertEqual(u5.origin.x, r8.origin.x) + XCTAssertEqual(u5.origin.y, r8.origin.y) + XCTAssertEqual(u5.size.width, r8.size.width) + XCTAssertEqual(u5.size.height, r8.size.height) + } + + func test_CGRect_Intersection() { + let r1 = CGRect(x: 10, y: 10, width: 50, height: 60) + let r2 = CGRect(x: 25, y: 25, width: 60, height: 70) + let i1 = r1.intersection(r2) + XCTAssertEqual(i1.origin.x, 25) + XCTAssertEqual(i1.origin.y, 25) + XCTAssertEqual(i1.size.width, 35) + XCTAssertEqual(i1.size.height, 45) + + let r3 = CGRect(x: 85, y: 95, width: -60, height: -70) + let i2 = r1.intersection(r3) + XCTAssertEqual(i2.origin.x, 25) + XCTAssertEqual(i2.origin.y, 25) + XCTAssertEqual(i2.size.width, 35) + XCTAssertEqual(i2.size.height, 45) + + let r4 = CGRect(x: -10, y: -10, width: -30, height: -30) + let i3 = r1.intersection(r4) + XCTAssertEqual(i3.origin.x, CGRect.null.origin.x) + XCTAssertEqual(i3.origin.y, CGRect.null.origin.y) + XCTAssertEqual(i3.size.width, CGRect.null.size.width) + XCTAssertEqual(i3.size.height, CGRect.null.size.height) + + let r5 = CGRect.null + let i4 = r1.intersection(r5) + XCTAssertEqual(i4.origin.x, CGRect.null.origin.x) + XCTAssertEqual(i4.origin.y, CGRect.null.origin.y) + XCTAssertEqual(i4.size.width, CGRect.null.size.width) + XCTAssertEqual(i4.size.height, CGRect.null.size.height) + + let i5 = r5.intersection(r1) + XCTAssertEqual(i5.origin.x, CGRect.null.origin.x) + XCTAssertEqual(i5.origin.y, CGRect.null.origin.y) + XCTAssertEqual(i5.size.width, CGRect.null.size.width) + XCTAssertEqual(i5.size.height, CGRect.null.size.height) + + var r6 = CGRect.null + r6.size = CGSize(width: 10, height: 20) + r6.origin.x = 30 + let i6 = r5.intersection(r6) + XCTAssertEqual(i6.origin.x, CGRect.null.origin.x) + XCTAssertEqual(i6.origin.y, CGRect.null.origin.y) + XCTAssertEqual(i6.size.width, CGRect.null.size.width) + XCTAssertEqual(i6.size.height, CGRect.null.size.height) + + let i7 = r1.intersection(.infinite) + XCTAssertEqual(i7.origin.x, r1.origin.x) + XCTAssertEqual(i7.origin.y, r1.origin.y) + XCTAssertEqual(i7.size.width, r1.size.width) + XCTAssertEqual(i7.size.height, r1.size.height) + + let i8 = CGRect.infinite.intersection(.infinite) + XCTAssertEqual(i8.origin.x, CGRect.infinite.origin.x) + XCTAssertEqual(i8.origin.y, CGRect.infinite.origin.y) + XCTAssertEqual(i8.size.width, CGRect.infinite.size.width) + XCTAssertEqual(i8.size.height, CGRect.infinite.size.height) + + let r7 = CGRect(x: -10, y: -10, width: 20, height: 20) + let i9 = r7.intersection(.zero) + XCTAssertEqual(i9.origin.x, 0) + XCTAssertEqual(i9.origin.y, 0) + XCTAssertEqual(i9.size.width, 0) + XCTAssertEqual(i9.size.height, 0) + } + + func test_CGRect_Intersects() { + let r1 = CGRect(x: 10, y: 10, width: 50, height: 60) + let r2 = CGRect(x: 25, y: 25, width: 60, height: 70) + XCTAssertTrue(r1.intersects(r2)) + + let r3 = CGRect(x: 85, y: 95, width: -60, height: -70) + XCTAssertTrue(r1.intersects(r3)) + + let r4 = CGRect(x: -10, y: -10, width: -30, height: -30) + XCTAssertFalse(r1.intersects(r4)) + + let r5 = CGRect.null + XCTAssertFalse(r1.intersects(r5)) + XCTAssertFalse(r5.intersects(r1)) + + var r6 = CGRect.null + r6.size = CGSize(width: 10, height: 20) + r6.origin.x = 30 + XCTAssertFalse(r5.intersects(r6)) + + XCTAssertTrue(r1.intersects(.infinite)) + + XCTAssertTrue(CGRect.infinite.intersects(.infinite)) + + let r7 = CGRect(x: -10, y: -10, width: 20, height: 20) + XCTAssertTrue(r7.intersects(.zero)) + } + + func test_CGRect_OffsetBy() { + var r1 = CGRect.null + r1.size = CGSize(width: 10, height: 20) + r1.origin.x = 30 + let o1 = r1.offsetBy(dx: 40, dy: 50) + XCTAssertEqual(o1.origin.x, r1.origin.x) + XCTAssertEqual(o1.origin.y, r1.origin.y) + XCTAssertEqual(o1.size.width, r1.size.width) + XCTAssertEqual(o1.size.height, r1.size.height) + + var r2 = CGRect.null + r2.size = CGSize(width: 10, height: 20) + r2.origin.y = 30 + let o2 = r2.offsetBy(dx: 40, dy: 50) + XCTAssertEqual(o2.origin.x, r2.origin.x) + XCTAssertEqual(o2.origin.y, r2.origin.y) + XCTAssertEqual(o2.size.width, r2.size.width) + XCTAssertEqual(o2.size.height, r2.size.height) + + let o3 = CGRect(x: 1.2, y: 3.4, width: 5.6, height: 7.8).offsetBy(dx: 10.5, dy: 20.5) + XCTAssertEqual(o3.origin.x, 11.7) + XCTAssertEqual(o3.origin.y, 23.9) + XCTAssertEqual(o3.size.width, 5.6) + XCTAssertEqual(o3.size.height, 7.8) + + let o4 = CGRect(x: -1.2, y: -3.4, width: -5.6, height: -7.8).offsetBy(dx: -10.5, dy: -20.5) + XCTAssertEqual(o4.origin.x, -17.3) + XCTAssertEqual(o4.origin.y, -31.7) + XCTAssertEqual(o4.size.width, 5.6) + XCTAssertEqual(o4.size.height, 7.8) + } + + func test_CGRect_Divide() { + let r1 = CGRect(x: 10, y: 20, width: 30, height: 40) + let d1 = r1.divided(atDistance: 10, from: .minXEdge) + XCTAssertEqual(d1.slice.origin.x, 10) + XCTAssertEqual(d1.slice.origin.y, 20) + XCTAssertEqual(d1.slice.size.width, 10) + XCTAssertEqual(d1.slice.size.height, 40) + XCTAssertEqual(d1.remainder.origin.x, 20) + XCTAssertEqual(d1.remainder.origin.y, 20) + XCTAssertEqual(d1.remainder.size.width, 20) + XCTAssertEqual(d1.remainder.size.height, 40) + + let d2 = r1.divided(atDistance: 10, from: .maxXEdge) + XCTAssertEqual(d2.slice.origin.x, 30) + XCTAssertEqual(d2.slice.origin.y, 20) + XCTAssertEqual(d2.slice.size.width, 10) + XCTAssertEqual(d2.slice.size.height, 40) + XCTAssertEqual(d2.remainder.origin.x, 10) + XCTAssertEqual(d2.remainder.origin.y, 20) + XCTAssertEqual(d2.remainder.size.width, 20) + XCTAssertEqual(d2.remainder.size.height, 40) + + let d3 = r1.divided(atDistance: 10, from: .minYEdge) + XCTAssertEqual(d3.slice.origin.x, 10) + XCTAssertEqual(d3.slice.origin.y, 20) + XCTAssertEqual(d3.slice.size.width, 30) + XCTAssertEqual(d3.slice.size.height, 10) + XCTAssertEqual(d3.remainder.origin.x, 10) + XCTAssertEqual(d3.remainder.origin.y, 30) + XCTAssertEqual(d3.remainder.size.width, 30) + XCTAssertEqual(d3.remainder.size.height, 30) + + let d4 = r1.divided(atDistance: 10, from: .maxYEdge) + XCTAssertEqual(d4.slice.origin.x, 10) + XCTAssertEqual(d4.slice.origin.y, 50) + XCTAssertEqual(d4.slice.size.width, 30) + XCTAssertEqual(d4.slice.size.height, 10) + XCTAssertEqual(d4.remainder.origin.x, 10) + XCTAssertEqual(d4.remainder.origin.y, 20) + XCTAssertEqual(d4.remainder.size.width, 30) + XCTAssertEqual(d4.remainder.size.height, 30) + + let d5 = r1.divided(atDistance: 31, from: .minXEdge) + XCTAssertEqual(d5.slice.origin.x, 10) + XCTAssertEqual(d5.slice.origin.y, 20) + XCTAssertEqual(d5.slice.size.width, 30) + XCTAssertEqual(d5.slice.size.height, 40) + XCTAssertEqual(d5.remainder.origin.x, 40) + XCTAssertEqual(d5.remainder.origin.y, 20) + XCTAssertEqual(d5.remainder.size.width, 0) + XCTAssertEqual(d5.remainder.size.height, 40) + + let d6 = r1.divided(atDistance: 31, from: .maxXEdge) + XCTAssertEqual(d6.slice.origin.x, 10) + XCTAssertEqual(d6.slice.origin.y, 20) + XCTAssertEqual(d6.slice.size.width, 30) + XCTAssertEqual(d6.slice.size.height, 40) + XCTAssertEqual(d6.remainder.origin.x, 10) + XCTAssertEqual(d6.remainder.origin.y, 20) + XCTAssertEqual(d6.remainder.size.width, 0) + XCTAssertEqual(d6.remainder.size.height, 40) + + let d7 = r1.divided(atDistance: 41, from: .minYEdge) + XCTAssertEqual(d7.slice.origin.x, 10) + XCTAssertEqual(d7.slice.origin.y, 20) + XCTAssertEqual(d7.slice.size.width, 30) + XCTAssertEqual(d7.slice.size.height, 40) + XCTAssertEqual(d7.remainder.origin.x, 10) + XCTAssertEqual(d7.remainder.origin.y, 60) + XCTAssertEqual(d7.remainder.size.width, 30) + XCTAssertEqual(d7.remainder.size.height, 0) + + let d8 = r1.divided(atDistance: 41, from: .maxYEdge) + XCTAssertEqual(d8.slice.origin.x, 10) + XCTAssertEqual(d8.slice.origin.y, 20) + XCTAssertEqual(d8.slice.size.width, 30) + XCTAssertEqual(d8.slice.size.height, 40) + XCTAssertEqual(d8.remainder.origin.x, 10) + XCTAssertEqual(d8.remainder.origin.y, 20) + XCTAssertEqual(d8.remainder.size.width, 30) + XCTAssertEqual(d8.remainder.size.height, 0) + + let d9 = CGRect(x: -10, y: -20, width: -30, height: -40).divided(atDistance: 10, from: .minXEdge) + XCTAssertEqual(d9.slice.origin.x, -40) + XCTAssertEqual(d9.slice.origin.y, -60) + XCTAssertEqual(d9.slice.size.width, 10) + XCTAssertEqual(d9.slice.size.height, 40) + XCTAssertEqual(d9.remainder.origin.x, -30) + XCTAssertEqual(d9.remainder.origin.y, -60) + XCTAssertEqual(d9.remainder.size.width, 20) + XCTAssertEqual(d9.remainder.size.height, 40) + + var r2 = CGRect.null + r2.size = CGSize(width: 10, height: 20) + r2.origin.x = 30 + let d10 = r2.divided(atDistance: 10, from: .minXEdge) + XCTAssertEqual(d10.slice.origin.x, CGRect.null.origin.x) + XCTAssertEqual(d10.slice.origin.y, CGRect.null.origin.y) + XCTAssertEqual(d10.slice.size.width, CGRect.null.size.width) + XCTAssertEqual(d10.slice.size.height, CGRect.null.size.height) + XCTAssertEqual(d10.remainder.origin.x, CGRect.null.origin.x) + XCTAssertEqual(d10.remainder.origin.y, CGRect.null.origin.y) + XCTAssertEqual(d10.remainder.size.width, CGRect.null.size.width) + XCTAssertEqual(d10.remainder.size.height, CGRect.null.size.height) + + var r3 = CGRect.null + r3.size = CGSize(width: 10, height: 20) + r3.origin.y = 30 + let d11 = r3.divided(atDistance: 10, from: .minXEdge) + XCTAssertEqual(d11.slice.origin.x, CGRect.null.origin.x) + XCTAssertEqual(d11.slice.origin.y, CGRect.null.origin.y) + XCTAssertEqual(d11.slice.size.width, CGRect.null.size.width) + XCTAssertEqual(d11.slice.size.height, CGRect.null.size.height) + XCTAssertEqual(d11.remainder.origin.x, CGRect.null.origin.x) + XCTAssertEqual(d11.remainder.origin.y, CGRect.null.origin.y) + XCTAssertEqual(d11.remainder.size.width, CGRect.null.size.width) + XCTAssertEqual(d11.remainder.size.height, CGRect.null.size.height) + } + + func test_CGRect_InsetBy() { + let ε = CGFloat(0.00001) + let nullX = CGRect.null.origin.x + let nullY = CGRect.null.origin.y + let nullWidth = CGRect.null.size.width + let nullHeight = CGRect.null.size.height + + let r1 = CGRect(x: 1.2, y: 3.4, width: 5.6, height: 7.8) + assertEqual(r1.insetBy(dx: 2.8, dy: 0), x: 4, y: 3.4, width: 0, height: 7.8, accuracy: ε) + assertEqual(r1.insetBy(dx: 0, dy: 3.9), x: 1.2, y: 7.3, width: 5.6, height: 0, accuracy: ε) + assertEqual(r1.insetBy(dx: 10, dy: 10), x: nullX, y: nullY, width: nullWidth, height: nullHeight) + assertEqual(r1.insetBy(dx: 10, dy: -10), x: nullX, y: nullY, width: nullWidth, height: nullHeight) + assertEqual(r1.insetBy(dx: -10, dy: 10), x: nullX, y: nullY, width: nullWidth, height: nullHeight) + assertEqual(r1.insetBy(dx: -10, dy: -10), x: -8.8, y: -6.6, width: 25.6, height: 27.8, accuracy: ε) + + let r2 = CGRect(x: 1.2, y: 3.4, width: -5.6, height: -7.8) + assertEqual(r2.insetBy(dx: 2.8, dy: 0), x: -1.6, y: -4.4, width: 0, height: 7.8, accuracy: ε) + assertEqual(r2.insetBy(dx: 0, dy: 3.9), x: -4.4, y: -0.5, width: 5.6, height: 0, accuracy: ε) + assertEqual(r2.insetBy(dx: 10, dy: 10), x: nullX, y: nullY, width: nullWidth, height: nullHeight) + assertEqual(r2.insetBy(dx: 10, dy: -10), x: nullX, y: nullY, width: nullWidth, height: nullHeight) + assertEqual(r2.insetBy(dx: -10, dy: 10), x: nullX, y: nullY, width: nullWidth, height: nullHeight) + assertEqual(r2.insetBy(dx: -10, dy: -10), x: -14.4, y: -14.4, width: 25.6, height: 27.8, accuracy: ε) + + let r3 = CGRect(x: -1.2, y: -3.4, width: 5.6, height: 7.8) + assertEqual(r3.insetBy(dx: 2.8, dy: 0), x: 1.6, y: -3.4, width: 0, height: 7.8, accuracy: ε) + assertEqual(r3.insetBy(dx: 0, dy: 3.9), x: -1.2, y: 0.5, width: 5.6, height: 0, accuracy: ε) + assertEqual(r3.insetBy(dx: 10, dy: 10), x: nullX, y: nullY, width: nullWidth, height: nullHeight) + assertEqual(r3.insetBy(dx: 10, dy: -10), x: nullX, y: nullY, width: nullWidth, height: nullHeight) + assertEqual(r3.insetBy(dx: -10, dy: 10), x: nullX, y: nullY, width: nullWidth, height: nullHeight) + assertEqual(r3.insetBy(dx: -10, dy: -10), x: -11.2, y: -13.4, width: 25.6, height: 27.8, accuracy: ε) + + let r4 = CGRect(x: -1.2, y: -3.4, width: -5.6, height: -7.8) + assertEqual(r4.insetBy(dx: 2.8, dy: 0), x: -4, y: -11.2, width: 0, height: 7.8, accuracy: ε) + assertEqual(r4.insetBy(dx: 0, dy: 3.9), x: -6.8, y: -7.3, width: 5.6, height: 0, accuracy: ε) + assertEqual(r4.insetBy(dx: 10, dy: 10), x: nullX, y: nullY, width: nullWidth, height: nullHeight) + assertEqual(r4.insetBy(dx: 10, dy: -10), x: nullX, y: nullY, width: nullWidth, height: nullHeight) + assertEqual(r4.insetBy(dx: -10, dy: 10), x: nullX, y: nullY, width: nullWidth, height: nullHeight) + assertEqual(r4.insetBy(dx: -10, dy: -10), x: -16.8, y: -21.2, width: 25.6, height: 27.8, accuracy: ε) + + var r5 = CGRect.null + r5.size = CGSize(width: 10, height: 20) + r5.origin.x = 30 + let i1 = r5.insetBy(dx: 50, dy: 60) + XCTAssertEqual(i1.origin.x, 30) + XCTAssertEqual(i1.origin.y, r5.origin.y) + XCTAssertEqual(i1.size.width, 10) + XCTAssertEqual(i1.size.height, 20) + + var r6 = CGRect.null + r6.size = CGSize(width: 10, height: 20) + r6.origin.y = 30 + let i2 = r6.insetBy(dx: 50, dy: 60) + XCTAssertEqual(i2.origin.x, r6.origin.x) + XCTAssertEqual(i2.origin.y, 30) + XCTAssertEqual(i2.size.width, 10) + XCTAssertEqual(i2.size.height, 20) + } func test_CGRect_SpecialValues() { let r1 = CGRect.null From e081f2c16a0e1087fd424630950f840ce374e511 Mon Sep 17 00:00:00 2001 From: Bartek Chlebek Date: Sun, 6 Aug 2017 14:51:43 +0200 Subject: [PATCH 2/2] Fixed CGRect : Equatable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix for 2 bugs: 1. ```swift CGRect(x: 0, y: 0, width: -5, height: -5) == CGRect(x: -5, y: -5, width: 5, height: 5) // used to return false // should standardize before comparison first ``` 2. ```swift let a = CGRect(x: CGFloat.infinity, y: 1, width: 2, height: 3) let b = CGRect(x: 1, y: CGFloat.infinity, width: 2, height: 3) a == b // used to return false // should return true if both `a` and `b` have any origin's value set to +∞ ``` --- Foundation/NSGeometry.swift | 6 +++++- TestFoundation/TestNSGeometry.swift | 32 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/Foundation/NSGeometry.swift b/Foundation/NSGeometry.swift index 62eefd96ea..3420df6c8e 100644 --- a/Foundation/NSGeometry.swift +++ b/Foundation/NSGeometry.swift @@ -399,7 +399,11 @@ extension CGRect { extension CGRect: Equatable { public static func ==(lhs: CGRect, rhs: CGRect) -> Bool { - return lhs.origin == rhs.origin && lhs.size == rhs.size + if lhs.isNull && rhs.isNull { return true } + + let r1 = lhs.standardized + let r2 = rhs.standardized + return r1.origin == r2.origin && r1.size == r2.size } } diff --git a/TestFoundation/TestNSGeometry.swift b/TestFoundation/TestNSGeometry.swift index bc10e42311..3286caca42 100644 --- a/TestFoundation/TestNSGeometry.swift +++ b/TestFoundation/TestNSGeometry.swift @@ -58,6 +58,7 @@ class TestNSGeometry : XCTestCase { ("test_CGRect_IsNull", test_CGRect_IsNull), ("test_CGRect_IsInfinite", test_CGRect_IsInfinite), ("test_CGRect_IsEmpty", test_CGRect_IsEmpty), + ("test_CGRect_Equatable", test_CGRect_Equatable), ("test_CGRect_CalculatedGeometricProperties", test_CGRect_CalculatedGeometricProperties), ("test_CGRect_Standardized", test_CGRect_Standardized), ("test_CGRect_Integral", test_CGRect_Integral), @@ -307,6 +308,37 @@ class TestNSGeometry : XCTestCase { XCTAssertFalse(CGRect.infinite.isEmpty) } + func test_CGRect_Equatable() { + XCTAssertEqual(CGRect(x: 10, y: 20, width: 30, height: 40), CGRect(x: 10, y: 20, width: 30, height: 40)) + XCTAssertEqual(CGRect(x: -10, y: -20, width: -30, height: -40), CGRect(x: -10, y: -20, width: -30, height: -40)) + XCTAssertEqual(CGRect(x: -10, y: -20, width: 30, height: 40), CGRect(x: 20, y: 20, width: -30, height: -40)) + + XCTAssertNotEqual(CGRect(x: 10, y: 20, width: 30, height: 40), CGRect(x: 10, y: 20, width: 30, height: -40)) + XCTAssertNotEqual(CGRect(x: 10, y: 20, width: 30, height: 40), CGRect(x: 10, y: 20, width: -30, height: 40)) + XCTAssertNotEqual(CGRect(x: 10, y: 20, width: 30, height: 40), CGRect(x: 10, y: -20, width: 30, height: 40)) + XCTAssertNotEqual(CGRect(x: 10, y: 20, width: 30, height: 40), CGRect(x: -10, y: 20, width: 30, height: 40)) + + XCTAssertEqual(CGRect.infinite, CGRect.infinite) + XCTAssertEqual(CGRect.null, CGRect.null) + XCTAssertNotEqual(CGRect.infinite, CGRect.null) + + var r1 = CGRect.null + r1.size = CGSize(width: 20, height: 20) + XCTAssertEqual(r1, CGRect.null) + + var r2 = CGRect.null + r2.origin.x = 20 + XCTAssertEqual(r2, CGRect.null) + + var r3 = CGRect.null + r3.origin.y = 20 + XCTAssertEqual(r3, CGRect.null) + + var r4 = CGRect.null + r4.origin = CGPoint(x: 10, y: 20) + XCTAssertNotEqual(r4, CGRect.null) + } + func test_CGRect_CalculatedGeometricProperties() { let ε = CGFloat(0.00001)