Skip to content

Instantly share code, notes, and snippets.

@HonmaMasaru
Last active March 26, 2023 22:08
Show Gist options
  • Save HonmaMasaru/7a13f53b9ea839f63f7990638d7fc71f to your computer and use it in GitHub Desktop.
Save HonmaMasaru/7a13f53b9ea839f63f7990638d7fc71f to your computer and use it in GitHub Desktop.
Shapeのアニメーション
import SwiftUI
import Combine
import PlaygroundSupport
/// アニメーション対応のシェイプ
struct AnimatableShape: Shape {
/// ポイントデータ
var points: AnimatableCGPointVector
/// アニメーション対象
var animatableData: AnimatableCGPointVector {
get { points }
set { points = newValue }
}
/// パスの作成
/// - Parameter rect: 領域
/// - Returns: パス
func path(in rect: CGRect) -> Path {
.init { $0.addLines(points.values) }
}
}
/// 形状の切り替え
struct SwitchShapeView: View {
/// ポイントの数
private let length: Int = 30
/// 形状
enum Mode {
case rect, circle
/// トグル
mutating func toggle() {
self = self == .rect ? .circle : .rect
}
}
var mode: Mode
/// ポイントデータ
@State var points: AnimatableCGPointVector = .zero
/// 色
var fillColor: Color
/// 本体
var body: some View {
GeometryReader { proxy in
AnimatableShape(points: points)
.fill(fillColor)
.onReceive(Just(mode)) { newValue in
withAnimation {
switch newValue {
case .rect:
points = .init(values: getRectPoints(proxy.size))
case .circle:
points = .init(values: getCirclePoints(proxy.size))
}
}
}
}
}
/// 矩形のポイントリストを取得
/// - Parameter size: 矩形のサイズ
/// - Returns: ポイントリスト
private func getRectPoints(_ size: CGSize) -> [CGPoint] {
let radius = sqrt(size.width * size.height / .pi) // 半径
let vRange = -(size.height / 2)...(size.height / 2)
let hRange = -(size.width / 2)...(size.width / 2)
return (0..<length).map {
let radian = 2 * .pi / CGFloat(length) * CGFloat($0)
let point = CGPoint(x: radius * cos(radian), y: radius * sin(radian))
let x = point.x < 0 ? min(hRange.lowerBound, point.x) : max(hRange.upperBound, point.x)
let y = point.y < 0 ? max(vRange.lowerBound, point.y) : min(vRange.upperBound, point.y)
return .init(x: x + size.width / 2, y: y + size.height / 2)
}
}
/// 円のポイントリストを取得
/// - Parameter size: 矩形のサイズ
/// - Returns: ポイントリスト
private func getCirclePoints(_ size: CGSize) -> [CGPoint] {
let radius = sqrt(size.width * size.height / .pi) // 半径
return (0..<length).map {
let radian = 2 * .pi / CGFloat(length) * CGFloat($0)
var point = CGPoint(x: radius * cos(radian), y: radius * sin(radian)) // 座標
point.x += size.width / 2
point.y += size.height / 2
return point
}
}
}
/// メイン
struct ContentView: View {
/// 形状
@State private var mode: SwitchShapeView.Mode = .rect
/// 本体
var body: some View {
ZStack {
SwitchShapeView(mode: mode, fillColor: .red)
.frame(width: 100, height: 50)
}
.frame(width: 300, height: 200)
Button("Toggle") {
mode.toggle()
}
}
}
PlaygroundPage.current.setLiveView(ContentView())
// MARK: -
// https://gist.github.com/mecid/04ab91f45fec501e72e4d5fb02277f3f
struct AnimatableCGPointVector: VectorArithmetic {
static var zero = AnimatableCGPointVector(values: [.zero])
static func - (lhs: AnimatableCGPointVector, rhs: AnimatableCGPointVector) -> AnimatableCGPointVector {
let values = zip(lhs.values, rhs.values)
.map { lhs, rhs in lhs.animatableData - rhs.animatableData }
.map { CGPoint(x: $0.first, y: $0.second) }
return AnimatableCGPointVector(values: values)
}
static func -= (lhs: inout AnimatableCGPointVector, rhs: AnimatableCGPointVector) {
for i in 0..<min(lhs.values.count, rhs.values.count) {
lhs.values[i].animatableData -= rhs.values[i].animatableData
}
}
static func + (lhs: AnimatableCGPointVector, rhs: AnimatableCGPointVector) -> AnimatableCGPointVector {
let values = zip(lhs.values, rhs.values)
.map { lhs, rhs in lhs.animatableData + rhs.animatableData }
.map { CGPoint(x: $0.first, y: $0.second) }
return AnimatableCGPointVector(values: values)
}
static func += (lhs: inout AnimatableCGPointVector, rhs: AnimatableCGPointVector) {
for i in 0..<min(lhs.values.count, rhs.values.count) {
lhs.values[i].animatableData += rhs.values[i].animatableData
}
}
var values: [CGPoint]
mutating func scale(by rhs: Double) {
for i in 0..<values.count {
values[i].animatableData.scale(by: rhs)
}
}
var magnitudeSquared: Double {
values
.map { $0.animatableData.magnitudeSquared }
.reduce(0.0, +)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment