SwiftUI Coffee App Animations | SwiftUI Challenge | Animations | Xcode 14
...
.gesture(
DragGesture()
.onChanged({ value in
offsetY = value.translation.height * 0.5
})
.onEnded({ value in
let translation = value.translation.height
withAnimation(.easeInOut) {
if translation > 0 {
if currentIndex > 0 && translation > 250 {
currentIndex -= 1
}
} else {
if currentIndex < CGFloat(viewModel.model.count - 1) && -translation > 250 {
currentIndex += 1
}
}
offsetY = .zero
}
})
)
import SwiftUI
struct HomeView: View {
private let viewModel = CoffeeViewModel()
@State private var offsetY: CGFloat = 0
@State private var currentIndex: CGFloat = 0
var body: some View {
GeometryReader { geometry in
let size = geometry.size
let cardSize = size.width
gradientView
headerView
VStack(spacing: 0) {
ForEach(viewModel.model) { coffee in
CoffeeView(coffee: coffee, size: size)
}
}
.frame(width: size.width)
.padding(.top, size.height - cardSize)
.offset(y: offsetY)
.offset(y: -currentIndex * cardSize)
}
.coordinateSpace(name: "SCROLL")
.contentShape(Rectangle())
.gesture(
DragGesture()
.onChanged({ value in
offsetY = value.translation.height * 0.5
})
.onEnded({ value in
let translation = value.translation.height
withAnimation(.easeInOut) {
if translation > 0 {
if currentIndex > 0 && translation > 250 {
currentIndex -= 1
}
} else {
if currentIndex < CGFloat(viewModel.model.count - 1) && -translation > 250 {
currentIndex += 1
}
}
offsetY = .zero
}
})
)
}
}
coordinateSpace
를 통해 자식 뷰 커피 뷰에서 부모 뷰인 홈 뷰의 스크롤 이벤트를 감지 가능extension HomeView {
private var gradientView: some View {
LinearGradient(colors: [.clear, Color.brown.opacity(0.2), Color.brown.opacity(0.45), Color.brown], startPoint: .top, endPoint: .bottom)
.frame(height: 300)
.frame(maxHeight: .infinity, alignment: .bottom)
.ignoresSafeArea()
}
private var headerView: some View {
VStack {
HStack {
Button {
} label: {
Image(systemName: "chevron.left")
.font(.title2.bold())
.foregroundColor(.black)
}
Spacer()
Button {
} label: {
Image(systemName: "cart")
.resizable()
.renderingMode(.template)
.aspectRatio(contentMode: .fit)
.frame(width: 30, height: 30)
.foregroundColor(.black)
}
}
GeometryReader { geometry in
let size = geometry.size
HStack(spacing: 0) {
ForEach(viewModel.model) { coffee in
VStack(spacing: 15) {
Text(coffee.title)
.font(.title.bold())
.multilineTextAlignment(.center)
Text(coffee.price)
.font(.title)
}
.frame(width: size.width)
}
}
.offset(x: currentIndex * -size.width)
.animation(.interactiveSpring(response: 0.6, dampingFraction: 0.8, blendDuration: 0.8), value: currentIndex)
}
.padding(.top, -5)
}
.padding(15)
}
}
x
축 기준currentIndex
는 이미지 뷰를 위아래로 움직일 때 변경 가능, 이 변경된 값을 통해 HStack
으로 쌓인 현재 헤더 뷰의 오프셋을 이동import SwiftUI
struct CoffeeView: View {
let coffee: CoffeeModel
let size: CGSize
var body: some View {
let cardSize = size.width * 1
let maxCardsDisplaySize = size.width * 4
GeometryReader { geometry in
let _size = geometry.size
let offset = geometry.frame(in: .named("SCROLL")).minY - (size.height - cardSize)
let scale = offset <= 0 ? (offset / maxCardsDisplaySize) : 0
let reducedScale = 1 + scale
let currentCardScale = offset / cardSize
Image(coffee.imageName)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: _size.width, height: _size.height)
.scaleEffect(reducedScale < 0 ? 0.001 : reducedScale, anchor: .init(x: 0.5, y: 1 - (currentCardScale / 2.4)))
.scaleEffect(offset > 0 ? 1 + currentCardScale : 1, anchor: .top)
.offset(y: offset > 0 ? currentCardScale * 200 : 0)
.offset(y: currentCardScale * -130)
}
.frame(height: cardSize)
}
}
import Foundation
class CoffeeViewModel {
let model: [CoffeeModel]
init() {
var mockData = [CoffeeModel]()
for x in 1...6 {
let imageName = "item_\(x)"
let title = "Coffee_\(x)"
if let randomNumber = (100...150).randomElement() {
let priceNumber = Double(randomNumber) / 10.0
let priceString = "$" + String(priceNumber)
let mockItem = CoffeeModel(imageName: imageName, title: title, price: priceString)
mockData.append(mockItem)
}
}
self.model = mockData
print(mockData)
}
}
Assets
의 이미지 이름을 담고 있는 데아토 배열import Foundation
struct CoffeeModel: Identifiable {
let id = UUID().uuidString
let imageName: String
let title: String
let price: String
}
Assets
에 저장된 이름을 담고 있는 데이터 모델iOS 환경에서는 낯선 애니메이션이었다. 하지만 애플 프로덕트 페이지 등 뛰어난 시각적 효과를 주는 웹 디자인에서는 그리 낯설지 않은 모습인 듯하다. 무엇보다도 아직까지 이해가 잘 되지 않은 강의 중 하나다. 사실 앱 내 원활한 애니메이션을 적용하기 위해서는 기본적인 수학이 전제되어 있어야 한다는 점에서 공부가 더 필요하다! 열심히 하자!