# Polygon

![💈範例](https://1830103165-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M5-JmwCZMKh_d7RfBaN%2Fuploads%2FXSSbds8x1qk6axeW2mTH%2Fpolygon.gif?alt=media\&token=a91eb2ae-4785-4ccd-a372-3ebbbc6ddf97)

{% tabs %}
{% tab title="👔 Polygon" %}
⬆️ 需要： [vector2d](https://lochiwei.gitbook.io/ios/custom/package/geometrykit/vector2d "mention"), [floating-+-int](https://lochiwei.gitbook.io/ios/swift/type/category/basic/numbers/floating-point/floating-+-int "mention")

````swift
// 2022.03.01

import SwiftUI
import Extensions    // FloatingPoint+
import Protocols     // Vector2D

/// an animatable polygon shape
/// ```
/// Polygon(sides: 6, scale: 1.2)
/// Polygon(5)
/// ```
public struct Polygon: Shape {
    
    var sides: CGFloat
    var scale: CGFloat = 1.0
    
    public init(sides: CGFloat, scale: CGFloat = 1.0) {
        self.sides = sides
        self.scale = scale
    }
    
    // ⭐️ `animatableData` (AnimatablePair)
    public var animatableData: AnimatablePair<CGFloat, CGFloat> {
        get { AnimatablePair(sides, scale) }
        // ⭐️ set new animation value
        set {
            sides = newValue.first
            scale = newValue.second
        }
    }
    
    public func path(in rect: CGRect) -> Path {
        
        let r = scale * rect.minSide / 2   // radius
        let c = rect.center                // polygon center
        let a0 = -0.5 * CGFloat.pi         // start angle
        let a = CGFloat.pi / sides * 2     // difference to next angle
        let n = Int(sides.rounded(.up))    // integral sides
        
        return Path { path in 
            path.move(to: c + .polar(r, a0))
            for i in 1..<n {
                path.addLine(to: c + .polar(r, a0 + a * i))
            }
            path.closeSubpath()
        }
    }
}

// convenience init
extension Polygon {
    /// `Polygon(3)`
    public init(_ n: CGFloat) {
        self.init(sides: n)
    }
}
````

{% endtab %}

{% tab title="⭐️ 重點" %}
{% hint style="info" %}
Animations:&#x20;

* implicit animations: `.animation()` modifier&#x20;
* disable animation: `.animation(nil)`
* explicit animations: `withAnimation{ ... }` closure
  {% endhint %}

{% hint style="info" %}
related protocol: \`Animatable\`

* \`animatableData\`: computed property (with default implementation by doing nothing)
* \`Shape\` conforms to \`Animatable\`
* \`AnimatablePair\<First, Second>\`: animate ove 2 parameters
  {% endhint %}

{% hint style="info" %}

* \`Animatable\`: \
  Angle, CGPoint, CGRect, CGSize, EdgeInsets, StrokeStyle, UnitPoint.&#x20;
* \`VectorArithmetic\`: \
  AnimatablePair, CGFloat, Double, EmptyAnimatableData, Float.
  {% endhint %}
  {% endtab %}

{% tab title="💈範例" %}
⬆️ 需要： [.decimalplaces](https://lochiwei.gitbook.io/ios/swift/type/category/basic/numbers/floating-point/.decimalplaces "mention"), [floating-+-int](https://lochiwei.gitbook.io/ios/swift/type/category/basic/numbers/floating-point/floating-+-int "mention")

```swift
// 2022.03.01
import SwiftUI

struct ContentView: View {
    @State private var sides: CGFloat = 3
    @State private var scale: CGFloat = 1
    var body: some View {
        VStack {
            polygon
            Slider(value: $sides, in: 1...15)
            buttons
        }
        .padding()
    }
}

extension ContentView {
    
    /// polygon
    var polygon: some View {
        Polygon(sides: sides, scale: scale)        // 👔 Polygon
            .stroke(.green)
            .overlay {
                Text("\(scale.decimalPlaces(2))")  // 🌀FloatingPoint+
                    .font(.caption)
                    .bold()
                    .shadow(radius: 6)
            }
            .background {
                Circle().stroke(.secondary)
                Polygon(sides).stroke(.blue)        // 👔 Polygon
            }
            .border(.secondary)
            .frame(height: 200)
    }
    
    /// buttons
    var buttons: some View {
        HStackForEach(1..<10){i in
            Button { 
                withAnimation(.easeInOut(duration: 1.5)) { 
                    sides = 1.0 * i             // 🌀FloatingPoint+
                    scale = CGFloat.random(in: 0.2...1.2)
                }
            } label: { 
                Text("\(i)")
                    .font(.title3).bold()
                    .foregroundColor(.primary)
                    .padding()
                    .background(.pink)
                    .cornerRadius(6)
            }
        }
    }
}
```

{% endtab %}

{% tab title="📘 手冊" %}

* [SwiftUI](https://developer.apple.com/documentation/swiftui) ⟩ [Animations](https://developer.apple.com/documentation/swiftui/animations) ⟩&#x20;
  * [Animatable](https://developer.apple.com/documentation/swiftui/animatable) (<mark style="color:orange;">**protocol**</mark>)
    * [animatableData](https://developer.apple.com/documentation/swiftui/animatable/animatabledata-swift.property-6nydg) - conforms to [VectorArithmetic](https://developer.apple.com/documentation/swiftui/vectorarithmetic) (<mark style="color:orange;">**protocol**</mark>).
  * [AnimatablePair](https://developer.apple.com/documentation/swiftui/animatablepair) (<mark style="color:red;">**struct**</mark>)
    {% endtab %}

{% tab title="📗 參考" %}

* SwiftUI Lab ⟩ Advanced SwiftUI Animations ⟩
  * [x] [Part 1: Paths](https://swiftui-lab.com/swiftui-animations-part1/) (💈範例)
    {% endtab %}

{% tab title="👥 相關" %}

* is an [animatable](https://lochiwei.gitbook.io/ios/swiftui/anim/animatable "mention") [](https://lochiwei.gitbook.io/ios/swiftui/shapes/shape "mention").
  {% endtab %}
  {% endtabs %}
