Skip to content

Instantly share code, notes, and snippets.

@YusukeHosonuma
Created May 19, 2022 11:47
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save YusukeHosonuma/198dd1a187ca4d5a49600c555b834ccd to your computer and use it in GitHub Desktop.
Save YusukeHosonuma/198dd1a187ca4d5a49600c555b834ccd to your computer and use it in GitHub Desktop.
GeometryReader と AnchorPreference で、matchedGeometry っぽいアニメーション処理。
//
// 以下の記事について matchedGeoemetry を使用せずに書いたみた的なやつ。
// https://hoshi0523.hatenablog.com/entry/2020/10/24/214520
//
// 🍊 GeometryReader version.
// 🍎 AnchorPreference version.
//
import SwiftUI
enum ButtonType: String, CaseIterable {
case share = "square.and.arrow.up"
case trash = "trash"
case folder = "folder"
case person = "person"
}
extension ButtonType: Identifiable {
var id: String { rawValue }
}
struct ContentView: View {
@State var selected: ButtonType = .share
var body: some View {
VStack {
HStack {
ForEach(ButtonType.allCases) { type in
CustomButton(selected: $selected, type: type)
}
}
//
// 🍊 GeometryReader version.
//
.coordinateSpace(name: "container")
.backgroundPreferenceValue(BoundsPreferenceKey.self) { bounds in
HStack {
circleBackground(bounds)
Spacer()
}
}
//
// 🍎 AnchorPreference version.
//
.backgroundPreferenceValue(AnchorPreferenceKey.self) { anchor in
if let anchor = anchor {
GeometryReader { geometry in
borderBackground(geometry[anchor])
}
}
}
}
.animation(.easeInOut, value: selected)
}
// 🍊
private func circleBackground(_ bounds: CGRect) -> some View {
Circle()
.fill(.orange)
.opacity(0.3)
.offset(x: bounds.minX, y: bounds.minY)
.frame(width: bounds.width, height: bounds.height)
}
// 🍎
private func borderBackground(_ bounds: CGRect) -> some View {
Rectangle()
.stroke(style: .init(lineWidth: 1, dash: [5]))
.stroke(.red)
.offset(x: bounds.minX - 4, y: bounds.minY - 4)
.frame(width: bounds.width + 8, height: bounds.height + 8)
}
}
struct CustomButton: View {
@Binding var selected: ButtonType
let type: ButtonType
var body: some View {
Button {
selected = type
} label: {
Image(systemName: type.rawValue)
.resizable()
.renderingMode(.original)
.aspectRatio(contentMode: .fit)
.frame(width: 44, height: 44)
}
.frame(width: 80, height: 80)
//
// 🍊 GeometryReader version.
//
.when(type == selected) { content in
content
.background(
GeometryReader { geometry in
Color.clear
.preference(key: BoundsPreferenceKey.self, value: geometry.frame(in: .named("container")))
}
)
}
//
// 🍎 AnchorPreference version.
//
.when(type == selected) { content in
content
.anchorPreference(key: AnchorPreferenceKey.self, value: .bounds) { $0 }
}
}
}
//
// 🍊 GeometryReader version.
//
struct BoundsPreferenceKey: PreferenceKey {
static var defaultValue: CGRect = .zero
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
value = nextValue()
}
}
//
// 🍎 AnchorPreference version.
//
struct AnchorPreferenceKey: PreferenceKey {
static var defaultValue: Anchor<CGRect>? = nil
static func reduce(value: inout Anchor<CGRect>?, nextValue: () -> Anchor<CGRect>?) {
value = nextValue()
}
}
extension View {
@ViewBuilder
func when<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
if condition {
transform(self)
} else {
self
}
}
}
@YusukeHosonuma
Copy link
Author

Simulator.Screen.Recording.-.iPhone.SE.3rd.generation.-.2022-05-19.at.20.47.53.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment