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 | 31 |
Tags
- Xcode
- SWIFT
- uitableview
- collectionview
- 리펙토링
- UITextView
- HIG
- uiscrollview
- map
- clean architecture
- RxCocoa
- 스위프트
- ios
- 애니메이션
- Refactoring
- Protocol
- 리팩토링
- Clean Code
- tableView
- UICollectionView
- rxswift
- combine
- MVVM
- ribs
- swiftUI
- 클린 코드
- 리펙터링
- Observable
- Human interface guide
- swift documentation
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - SwiftUI] 튜토리얼 - 8. GeometryReader를 이용한 뷰 구현 본문
GeometryReader
- ContainerView이며, 내부에 UIView들의 layout을 쉽게 변경할 수 있는 역할
* GeometryReader를 안쓴 경우) Stack안에 두가지의 뷰가 들어가고, Rectangle이 하단의 모든 자리를 차지하는 형태
struct Example: View {
var body: some View {
VStack {
Text("example GeoMetryReader")
Rectangle()
.foregroundColor(.green)
}
}
}
struct Example_Previews: PreviewProvider {
static var previews: some View {
Example()
}
}
GeometryReader를 사용한 경우)
- GeometryReader의 closure를 통해서, containerView의 size를 접근할 수 있어 사용이 가능
struct Example: View {
var body: some View {
VStack {
Text("example GeoMetryReader")
GeometryReader { proxy in // <-
Rectangle()
.foregroundColor(.green)
.frame(width: proxy.size.width / 2, height: proxy.size.height / 2, alignment: .center)
}
}
}
}
GeometryReader는 여전히 영역을 차지하고 있는중 (= ContainerView역할)
튜토리얼 - GeometryReader를 이용한 구현
* 코드 - https://developer.apple.com/tutorials/swiftui/animating-views-and-transitions
- 위 그림에 사용될 데이터 모델 준비 `Hike`
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>
}
}
- HikeGraph 뷰 구현
- Hike 데이터와 path 정보를 받는 뷰
- path에 KeyPath를 사용하는 이유?
- KeyPath를 사용하면, 마치 property를 enum타입의 case와 같이 사용이 가능 (switch로 분류하여 각각 property에 해당되는 값 분기가 가능)
struct HikeGraph: View {
var hike: Hike
var path: KeyPath<Hike.Observation, Range<Double>>
// TODO
}
* KeyPath 사용법
- 첫번째 인수 값(Hike.Observation) 사용 - 조건문에서 사용 `(path == \.elevation)`
- 두번째 인수 값 사용 - `인스턴스[keyPath: path]` (각 프로퍼티에 해당되는 값을 얻을 수 있음)
var path: KeyPath<Hike.Observation, Range<Double>>
// 초기화 하는 쪽 - keyPath로 주입
HikeGraph(hike: hike, path: \Hike.Observation.elevation)
- KeyPath를 통해 분기하여 각 property마다 색상을 다르게 적용
// in HikeGraph
var color: Color {
switch path {
case \.elevation:
return .gray
case \.heartRate:
return Color(hue: 0, saturation: 0.5, brightness: 0.7)
case \.pace:
return Color(hue: 0.7, saturation: 0.4, brightness: 0.7)
default:
return .black
}
}
- 각 데이터를 처리하여 적절한 값을 가져오게하는 헬퍼함수 정의 (별로 안중요한 부분)
func rangeOfRanges<C: Collection>(_ ranges: C) -> Range<Double>
where C.Element == Range<Double> {
guard !ranges.isEmpty else { return 0..<0 }
let low = ranges.lazy.map { $0.lowerBound }.min()!
let high = ranges.lazy.map { $0.upperBound }.max()!
return low..<high
}
func magnitude(of range: Range<Double>) -> Double {
range.upperBound - range.lowerBound
}
- 뷰 구현 (HikeGraph의 body)
- GeometryReader 사용
* GeometryReader는 이전 포스팅 글에서 구현한 GraphCapsule 뷰
- body 부분 구현
// in HikeGraph
var body: some View {
let data = hike.observations
let overallRange = rangeOfRanges(data.lazy.map { $0[keyPath: path] })
let maxMagnitude = data.map { magnitude(of: $0[keyPath: path]) }.max()!
let heightRatio = 1 - CGFloat(maxMagnitude / magnitude(of: overallRange))
return GeometryReader { ... }
- GeometryReader 구현
- GeometryReader 부분에서 proxy를 사용하여, HStack의 spacing 크기와 내부 height계산에 사용
// GeometryReader: 그 자체로 View이고 container 안 View 스스로의 크기와 위치를 함수로 정의
return GeometryReader { proxy in
HStack(alignment: .bottom, spacing: proxy.size.width / 120) {
ForEach(Array(data.enumerated()), id: \.offset) { index, observation in
GraphCapsule(
index: index,
color: color,
height: proxy.size.height,
range: observation[keyPath: path],
overallRange: overallRange
)
.animation(.ripple(index: index))
}
.offset(x: 0, y: proxy.size.height * heightRatio)
}
}
}
- Previews 정의
struct HikeGraph_Previews: PreviewProvider {
static var hike = ModelData().hikes[0]
static var previews: some View {
Group {
HikeGraph(hike: hike, path: \.elevation)
.frame(height: 200)
HikeGraph(hike: hike, path: \.heartRate)
.frame(height: 200)
HikeGraph(hike: hike, path: \.pace)
.frame(height: 200)
}
}
}
* 참고
https://developer.apple.com/tutorials/swiftui/animating-views-and-transitions
https://developer.apple.com/documentation/swiftui/geometryreader
'iOS 튜토리얼 (SwiftUI)' 카테고리의 다른 글
Comments