---
description: 2024.12.12 由 Rectangular 改名為 Frame
---

# Frame

[swift](../../../swift) ⟩ [type](../../../swift/type) ⟩ [custom](../..) ⟩ [package](..) ⟩ [GeometryKit]() ⟩ Frame ([protocol](../../../swift/type/category/protocol))

{% hint style="info" %}
簡化 [frame](../../../swiftui/view/layout/frame/ext) 與 [CGRect](../../../swift/scope/framework/built-in-frameworks/core-graphics/cgrect+ext) 的相關計算
{% endhint %}

{% tabs %}
{% tab title="⬇️ 應用" %}
* [🌀 CGRect](../../../swift/scope/framework/built-in-frameworks/core-graphics/cgrect+ext)
* [🌀 CGSize](../../../swift/scope/framework/built-in-frameworks/core-graphics/cgsize)
* [🌀 GeometryProxy](../../../swiftui/view/measure/geometryproxy/geometryproxy)
* [path](../../../swiftui/shapes/path "mention")
{% endtab %}

{% tab title="👥 相關" %}
* :parking: [protocol](../../../swift/type/category/protocol "mention")
{% endtab %}
{% endtabs %}

## History

{% tabs %}
{% tab title="✏️" %}
1. 2021.11.15
2. 2022.03.09
3. 2024.12.12 封存
{% endtab %}

{% tab title=" 紀錄" %}
* 2020.10.11：🐣 新加入。
* 2020.10.12：➕  .**rect**, .**boundingSquare**
* 2020.10.22：
  * ✏️  .minSide, .maxSide 加入 **abs**(), 避免有些型別的 width, height 有負值
  * ➕  .**aspectRatio** (width / height)
  * ➕  .**inscribedSquare**&#x20;
{% endtab %}

{% tab title="1" %}
````swift
//
//  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
//

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 -
    
    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) }
}

// 🌀GeometryProxy + 🅿️ Rectangular
extension GeometryProxy: Rectangular {
    public var origin: CGPoint { .zero }
}

````
{% endtab %}

{% tab title="2" %}
````swift
// [2021]
//   11.18
//   11.24 + 🌀CGPoint + 🅿️ Rectangular
//   11.25 - fix `inscribedSquare`,
//         + Self.unitSquare
//         + 🌀UnitPoint + 🅿️ Rectangular
// 2022.02.22 (r) unitSquare: minor edit

// ------------------------
//      🅿️ 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 -
    
    // ⚠️ abs() 的設定對已經有 width 屬性的類別應該無效❗️
    public var width  : CGFloat { abs(size.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: .zero, size: .init(width: 1, height: 1))
    }
    
    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 { }

// 🌀GeometryProxy + Rectangular
@available(iOS 13, macOS 10.15, *)
extension GeometryProxy: Rectangular {
    public var origin: CGPoint { .zero }
}

// the following conforms to `Rectangular` automatically
// 🌀CGSize + 🅿️ Rectangular
// 🌀CGPoint + 🅿️ Rectangular
// 🌀UnitPoint + 🅿️ Rectangular
````
{% endtab %}

{% tab title="3" %}
````swift
// 2021.11.24 + conform  : 🌀CGPoint + 🅿️ Rectangular
// 2021.11.25 / fix      : rect.inscribedSquare
//            + property : rect.unitSquare (CGRect)
//            + conform  : 🌀UnitPoint + 🅿️ Rectangular
// 2022.03.09 + subscript: rect[point]

// ------------------------
//      🅿️ 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 rect
    /// ```
    /// 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)
    }
    
    // 2022.03.09
    /// relative point in rect.
    /// ```
    /// rect[point]
    /// ```
    public subscript<V: Vector2D>(_ v: V) -> CGPoint where V.Field == CGFloat {
        let x = minX + v.x * abs(width)
        let y = minY + v.y * abs(height)
        return CGPoint(x: x, y: y)
    }
    
    // MARK: - Dimensions -
    
    // ⚠️ abs() 的設定對已經有 width 屬性的類別應該無效❗️
    public var width  : CGFloat { abs(size.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 { }

// 🌀GeometryProxy + Rectangular
@available(iOS 13, macOS 10.15, *)
extension GeometryProxy: Rectangular {
    public var origin: CGPoint { .zero }
}

// the following conforms to `Rectangular` automatically
// 🌀CGSize + 🅿️ Rectangular
// 🌀CGPoint + 🅿️ Rectangular
// 🌀UnitPoint + 🅿️ Rectangular
````
{% endtab %}
{% endtabs %}
