Tutorials โฉ Extensions
Custom Types
Rectangular.swift (4)
Vector2D.swift (5)
//
// Rectangular.swift
// -----------------
// 1.0.0: 2021/11/18
// 1.0.1: 2021/11/24 + ๐CGPoint + ๐
ฟ๏ธ Rectangular
// 1.0.2: 2021/11/25
// - fix `inscribedSquare` bug,
// + Self.unitSquare
// + ๐UnitPoint + ๐
ฟ๏ธ Rectangular
//
import SwiftUI
// ๐
ฟ๏ธ Rectangular
public protocol Rectangular {
var origin: CGPoint { get }
var size : CGSize { get }
}
// default behaviors
extension Rectangular {
// MARK: - Bounds -
/// the bounds (x-coordinates) of the range of width
public var boundsX: (CGFloat, CGFloat) {
let x1 = origin.x
let x2 = x1 + size.width
return (x1, x2)
}
/// the bounds (y-coordinates) of the range of height
public var boundsY: (CGFloat, CGFloat) {
let y1 = origin.y
let y2 = y1 + size.height
return (y1, y2)
}
// MARK: - Coordinates -
/// lower bound (x-coordinate) of the width
public var minX: CGFloat {
return min(boundsX.0, boundsX.1)
}
/// upper bound (x-coordinate) of the width
public var maxX: CGFloat {
return max(boundsX.0, boundsX.1)
}
/// midpoint (x-coordinate) of the width
public var midX: CGFloat {
origin.x + size.width / 2
}
/// lower bound (y-coordinate) of the height
public var minY: CGFloat {
return min(boundsY.0, boundsY.1)
}
/// upper bound (y-coordinate) of the height
public var maxY: CGFloat {
return max(boundsY.0, boundsY.1)
}
/// midpoint (y-coordinate) of the height
public var midY: CGFloat {
origin.y + size.height / 2
}
// MARK: - Points -
// rect.top, ...
public var top : CGPoint { CGPoint(x: midX, y: minY) }
public var bottom : CGPoint { CGPoint(x: midX, y: maxY) }
public var left : CGPoint { CGPoint(x: minX, y: midY) }
public var right : CGPoint { CGPoint(x: maxX, y: midY) }
public var center : CGPoint { CGPoint(x: midX, y: midY) }
public var bottomLeft : CGPoint { CGPoint(x: minX, y: maxY) }
public var bottomRight: CGPoint { CGPoint(x: maxX, y: maxY) }
public var topLeft : CGPoint { CGPoint(x: minX, y: minY) }
public var topRight : CGPoint { CGPoint(x: maxX, y: minY) }
/// 4 corner points of a rect
public var corners: [CGPoint] { [bottomLeft, bottomRight, topRight, topLeft] }
/// a point relative to the rectangle's size (width, height)
///
/// ```
/// rect[1,0] == rect.topRight
/// rect[0,1] == rect.bottomLeft
/// ```
public subscript(_ s: CGFloat, _ t: CGFloat) -> CGPoint {
let x = minX + s * abs(width)
let y = minY + t * abs(height)
return CGPoint(x: x, y: y)
}
// MARK: - Dimensions -
public var width : CGFloat { abs(size.width) } // โ ๏ธ abs() ็่จญๅฎๅฐๅทฒ็ถๆ width ๅฑฌๆง็้กๅฅๆ่ฉฒ็กๆโ๏ธ
public var height : CGFloat { abs(size.height) }
public var minSide: CGFloat { min(abs(width), abs(height)) }
public var maxSide: CGFloat { max(abs(width), abs(height)) }
public var aspectRatio: CGFloat { abs(width/height) }
// MARK: - Rect -
/// unit square of conforming type
public static var unitSquare: CGRect {
CGRect(origin: [0.0, 0.0], size: [1.0, 1.0])
}
public var rect: CGRect {
CGRect(origin: origin, size: size)
}
public var boundingSquare: CGRect {
let d = maxSide / 2
let x = midX - d
let y = midY - d
return CGRect(x: x, y: y, width: maxSide, height: maxSide)
}
public var inscribedSquare: CGRect {
let d = minSide / 2
let x = midX - d
let y = midY - d
return CGRect(x: x, y: y, width: minSide, height: minSide)
}
}
// MARK: - Conforming Types -
// ๐CGRect + ๐
ฟ๏ธ Rectangular
extension CGRect: Rectangular { }
// ๐CGSize + ๐
ฟ๏ธ Rectangular
extension CGSize: Rectangular {
public var origin: CGPoint { .zero }
public var size : CGSize { self }
}
// ๐CGPoint + ๐
ฟ๏ธ Rectangular
extension CGPoint: Rectangular {
public var origin: CGPoint { .zero }
public var size : CGSize { CGSize(width: x, height: y) }
}
// ๐UnitPoint + ๐
ฟ๏ธ Rectangular
extension UnitPoint: Rectangular {
public var origin: CGPoint { .zero }
public var size : CGSize { CGSize(width: x, height: y) }
}
// ๐GeometryProxy + ๐
ฟ๏ธ Rectangular
extension GeometryProxy: Rectangular {
public var origin: CGPoint { .zero }
}
//
// Vector2D.swift
// --------------
//
// 1.0.0: 2020.10.10
//
import SwiftUI
// โญ๏ธ define custom operators
infix operator ร : MultiplicationPrecedence // cross (determinant)
infix operator โข : MultiplicationPrecedence // dot (โข : opt + 8)
infix operator ** : MultiplicationPrecedence // non-proportional scale
infix operator **=: MultiplicationPrecedence
// ๐
ฟ๏ธ Vector2D
public protocol Vector2D: ExpressibleByArrayLiteral {
associatedtype Field: FloatingPoint // support +,-,*,/
// x, y coordinates
var x: Field { get }
var y: Field { get }
// initializer
init(x: Field, y: Field)
// vector addition: u + v
static func + (u: Self, v: Self) -> Self
// additive inverse: -v
static prefix func - (u: Self) -> Self
// scalar multiplication: a * v
// has default implementation in extension
}
// MARK: - Vector2D protocol extension -
// default behaviors
extension Vector2D {
/* ------- Constants ------- */
public static var i: Self { return Self.init(x: 1, y: 0) }
public static var j: Self { return Self.init(x: 0, y: 1) }
/* ------- Initializers ------- */
/// make `Vector2D` conform to `ExpressibleByArrayLiteral`
///
/// Example:
/// ```
/// let p: CGPoint = [1, 2]
/// ```
public init(arrayLiteral elements: Field...) {
let a = elements + [0, 0] // padding 0's
self.init(x: a[0], y: a[1])
}
/// convenience initializer
///
/// example:
/// ```
/// let p = CGPoint(1, 2)
/// ```
public init(_ x: Field, _ y: Field) {
self.init(x: x, y: y)
}
/* ------- Linear Combinations ------- */
/// u + v
public static func + (u: Self, v: Self) -> Self {
return Self.init(x: u.x + v.x, y: u.y + v.y)
}
/// -v (additive inverse)
public static prefix func - (v: Self) -> Self {
return Self.init(x: -v.x, y: -v.y)
}
/// vector subtraction
/// `u - v == u + (-v)`
public static func - (u: Self, v: Self) -> Self {
return u + (-v)
}
/* ------- Scalar Multiplication ------- */
/// a * v
public static func * (a: Field, v: Self) -> Self {
return Self.init(x: a * v.x, y: a * v.y)
}
/// v * a
public static func * (v: Self, a: Field) -> Self {
return a * v
}
/// v / a
public static func / (v: Self, a: Field) -> Self {
return Self.init(x: v.x / a, y: v.y / a)
}
/* ------- Dot Product, Cross Product ------- */
/// cross product: `u ร v`
public static func ร (u: Self, v: Self) -> Field {
return u.x * v.y - u.y * v.x // x1 y2 - x2 y1
}
/// dot product: `u โข v`
public static func โข (u: Self, v: Self) -> Field {
return u.x * v.x + u.y * v.y // x1 x2 + y1 y2
}
/* ------- Complex Number ------- */
/// complex multiplication: `u * v`
public static func * (u: Self, v: Self) -> Self {
let (a,b,c,d) = (u.x, u.y, v.x, v.y) // u = a + bi, v = a - bi
return Self.init(x: a * c - b * d, y: a * d + b * c) // uv = (ac-bd) + (ad+bc)i
}
/// complex multiplication: u *= v
public static func *= (u: inout Self, v: Self) {
u = u * v
}
/* ------- Other Operations ------- */
/// (non-proportional scale)
/// if `u = [a,b], v = [c,d]`, then `u ** v = [ac, bd]`
public static func ** (u: Self, v: Self) -> Self {
return Self.init(x: u.x * v.x, y: u.y * v.y)
}
}
// MARK: - Field == CGFloat -
extension Vector2D where Field == CGFloat {
/* ------- Polar Coordinates ------- */
/// polar form of a 2D vector
/// - Parameters:
/// - r: radius
/// - a: angle (in radian)
/// - Returns: `Self`: the conforming type
public static func polar(_ r: Field, _ a: Field) -> Self {
Self.init(x: r * cos(a), y: r * sin(a))
}
}
// MARK: - Conforming Types -
// ๐CGPoint + Vector2D
extension CGPoint: Vector2D {}
// ๐UnitPoint + Vector2D
extension UnitPoint: Vector2D {}
// ๐CGSize + Vector2D
extension CGSize: Vector2D {
public var x: CGFloat { width }
public var y: CGFloat { height }
public init(x: CGFloat, y: CGFloat) {
self.init(width: x, height: y)
}
}
// ๐CGVector + Vector2D
extension CGVector: Vector2D {
public var x: CGFloat { dx }
public var y: CGFloat { dy }
public init(x: CGFloat, y: CGFloat) {
self.init(dx: x, dy: y)
}
}
Extensions
View+ext.swift (2)
CGSize+ext.swift (3)
AnyTransition+ext.swift (6)
Animation+ext.swift (7)
import SwiftUI
extension View {
/// set the device (by device name) for preview.
///
/// example:
/// ```
/// // deviceName: String
/// .previewDevice(deviceName)
/// ```
/// - Parameter deviceName: device name, e.g. `"iPhone X"`
/// - Returns: `some View`
public func previewDevice(_ deviceName: String) -> some View {
previewDevice(PreviewDevice(rawValue: deviceName))
}
}
import CoreGraphics
extension CGSize {
/// `size.point`: turn a `CGSize` into `CGPoint`
///
/// Example:
/// ```
/// let size = CGSize(width: 20, height: 30)
/// let p = size.point // CGPoint(x: 20, y: 30)
/// ```
public var point: CGPoint { CGPoint(x: width, y: height) }
}
import SwiftUI
extension AnyTransition {
/// AnyTransition.moveAndFade
static var moveAndFade: AnyTransition {
/// Use `asymmetric(insertion:removal:)` to provide different transitions
/// for when the view appears and disappears.
.asymmetric(
insertion: .move(edge: .trailing).combined(with: .opacity),
removal : .scale.combined(with: .opacity)
)
}
}
import SwiftUI
extension Animation {
/// `Animation.ripple()`
/// - Returns: `Animation`
static func ripple(index: Int) -> Animation {
Animation
/// a reduced damping fraction makes the views hop.
.spring(dampingFraction: 0.5)
/// speed up the animation, to shorten the time
/// it takes to move to its new position.
.speed(2)
/// add a delay to each animation based on
/// the view's `index` on the graph.
.delay(0.03 * Double(index))
}
}
Last updated