Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- clean architecture
- 리펙토링
- 스위프트
- 클린 코드
- Protocol
- 애니메이션
- rxswift
- UICollectionView
- Refactoring
- collectionview
- Xcode
- RxCocoa
- Observable
- UITextView
- Human interface guide
- swift documentation
- Clean Code
- ios
- swiftUI
- uitableview
- HIG
- ribs
- combine
- 리팩토링
- uiscrollview
- MVVM
- SWIFT
- map
- 리펙터링
- tableView
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - SwiftUI] 튜토리얼 - 7. 애니메이션 (Animation, spring, speed, delay, onTapGesture) 본문
iOS 튜토리얼 (SwiftUI)
[iOS - SwiftUI] 튜토리얼 - 7. 애니메이션 (Animation, spring, speed, delay, onTapGesture)
jake-kim 2022. 7. 9. 23:27애니메이션에 사용할 모양 정의
- SwiftUI에는 대표적으로 4가지의 모양을 쉽게 사용이 가능
- Capsule()
- Circle()
- Rectangle()
- RoundedRectangle(cornerSize:)
var body: some View {
Capsule()
.fill(color)
.frame(height: 70)
Circle()
.fill(.red)
.frame(width: 100, height: 100)
Rectangle()
.fill(.green)
.frame(width: 100, height: 100)
RoundedRectangle(cornerSize: .init(width: 12, height: 12))
.fill(.blue)
.frame(width: 100, height: 100)
}
- 이 중에서 Capsule을 사용하여 모양을 구현
- GraphCapsule이라는 struct로 정의
- 사용하는쪽에서 값을 입력하면 그 값에 따라 모양이 정해지도록 구현
- range값은 해당 Capsule의 range값을 의미하고, overallRange값은 전체 Capsule들을 한꺼번에 봤을때의 range값
import SwiftUI
struct GraphCapsule: View, Equatable {
var index: Int
var color: Color
var height: CGFloat
var range: Range<Double>
var overallRange: Range<Double>
var heightRatio: CGFloat {
max(CGFloat(magnitude(of: range) / magnitude(of: overallRange)), 0.15)
}
var offsetRatio: CGFloat {
CGFloat((range.lowerBound - overallRange.lowerBound) / magnitude(of: overallRange))
}
var body: some View {
Capsule()
.fill(color)
.frame(height: height * heightRatio)
.offset(x: 0, y: height * -offsetRatio)
}
func magnitude(of range: Range<Double>) -> Double {
range.upperBound - range.lowerBound
}
}
데이터 준비
- json 데이터
- Apple 의 튜토리얼 페이지에서 json 관련 데이터 참고
- 등산을 갈때의 거리, 고도 등이 나와 있는 값
[
{
"name":"Lonesome Ridge Trail",
"id":1001,
"distance":4.5,
"difficulty":3,
"observations":[
{
"elevation":[
291.65263635636268,
309.26016677925196
],
"pace":[
396.08716481908732,
403.68937873525232
],
"heartRate":[
117.16351898665887,
121.95815455919609
],
"distanceFromStart":0
},
{
"elevation":[
299.24001936628116,
317.44584350790012
...
- 위 데이터를 처리할 모델도 정의
- SwiftUI에서의 모델은 항상 Identifiable을 상속받아서 id값도 같이 정해주는 것을 주의 (관련 내용 List 글 참고)
import Foundation
struct Hike: Codable, Hashable, Identifiable {
var id: Int
var name: String
var distance: Double
var difficulty: Int
var observations: [Observation]
static var formatter = LengthFormatter()
var distanceText: String {
Hike.formatter
.string(fromValue: distance, unit: .kilometer)
}
struct Observation: Codable, Hashable {
var distanceFromStart: Double
var elevation: Range<Double>
var pace: Range<Double>
var heartRate: Range<Double>
}
}
- json to decoding하는 코드와, 로드하는 코드 추가
import Foundation
final class ModelData: ObservableObject {
var hikes: [Hike] = load("hikeData.json")
}
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
애니메이션
- 잔물결 애니메이션 구현
- spring으로 스프링처럼 튕기는 애니메이션을 적용
extension Animation {
// ripple: 잔물결
static func ripple(index: Int) -> Animation {
Animation.spring(dampingFraction: 0.5) // dampingFraction 튕기는 정도 0 ~ 1
.speed(2)
.delay(0.03 * Double(index))
}
}
- 예제 파일
import SwiftUI
struct Example: View {
var body: some View {
Text("Hello world")
.font(.title)
}
}
struct Example_Previews: PreviewProvider {
static var previews: some View {
Example()
}
}
- didTap이라는 상태를 두고, 이 상태는 onTapGesture할때 변경하도록 적용
- VStack도 추가하여, VStack안의 모든 탭을 적용
struct Example: View {
@State private var didTap = false // <-
var body: some View {
VStack {
Text("Hello world")
.font(.title)
}
.onTapGesture {
didTap.toggle()
}
}
}
- didTap 상태에 따라 rotationEffect를 적용
struct Example: View {
@State private var didTap = false
var body: some View {
VStack {
if didTap { // <-
Text("Hello world")
.font(.title)
.rotationEffect(.degrees(30))
} else {
Text("Hello world")
.font(.title)
}
}
.onTapGesture {
didTap.toggle()
}
}
}
- 잔물결 (ripple) 애니메이션을 정의한 후 적용
extension Animation {
// ripple: 잔물결
static func ripple(index: Int) -> Animation {
Animation.spring(dampingFraction: 0.5) // dampingFraction 튕기는 정도 0 ~ 1
.speed(2)
.delay(0.03 * Double(index))
}
}
struct Example: View {
@State private var didTap = false
var body: some View {
VStack {
if didTap {
Text("Hello world")
.font(.title)
.rotationEffect(.degrees(30))
} else {
Text("Hello world")
.font(.title)
}
}
.animation(.ripple(index: 1)) // <-
.onTapGesture {
didTap.toggle()
}
}
}
데이터를 표출할 뷰 구현
- 다음 글에서 계속 구현
* 참고
https://developer.apple.com/tutorials/swiftui/animating-views-and-transitions
'iOS 튜토리얼 (SwiftUI)' 카테고리의 다른 글
Comments