iOS 17이전까지 ScrollView를 활용할 때 제한적인 부분이 많았다.
특히 오프셋을 직접 구현해서 페이징 기능을 커스텀으로 만들어야한다는 불편함이 있었는데, 애플에서 scrollTargetLayout과 scrollTargetBehavior를 iOS 17때 추가해주어서 이제 코드 단 두 줄로 구현이 가능해졌다.
우선, 해당 기능에 대해 애플문서로 간략하게 알아보자.
scrollTargetLayout은 스크롤 타겟으로 설정할 레이아웃을 정하는 역할을 해준다. ...라고 쓰려니까 이게 무슨 소리야, 싶은데 이 메서드는 스크롤 액션이 이루어질 때 어떤 레이아웃을 기점으로 스크롤이 이루어질지 정해준다.
그리고 scrollTargetLayout은 단독으로 쓰이지 않고, 대신 ViewAlignedScrollTargetBehavior(ScrollTargetBehavior)와 함께 사용해서 스크롤 뷰가 특정 뷰나 콘텐츠에 정확하게 정렬될 수 있도록 돕는 역할을 수행한다.
ScrollTargetBehavior는 스크롤가능한 뷰의 스크롤 동작을 정의해주는 프로토콜이다. Paging Behavior와 View Aligned Behavior 방식이 있다.
활용은 아래와 같이 할 수 있다.
1-1. scrollView로 UI를 잡아준다.
구현코드:
import SwiftUI
struct CarouselViewSwiftUI: View {
/// Carousel에서 보여줄 색상 배열
let colors: [Color] = [.red, .blue, .green, .pink, .purple]
/// 아이템 사이 간격
let itemSpacing: CGFloat = 12
/// 아이템 너비
let itemWidth = 200
var body: some View {
ScrollView(.horizontal) {
HStack(alignment: .center, spacing: itemSpacing) {
ForEach(colors, id: \.self) { color in
color
.frame(width: CGFloat(itemWidth), height: 300)
.cornerRadius(12)
} // forEach
} // hStack
.padding(.leading, 24)
} // scrollView
.scrollIndicators(.hidden)
.frame(height: 300)
} // body
} // view
여기까지는 우리가 아는 스크롤뷰다. 위 코드처럼 구현했을 때 드래그하면 스르륵~ 넘어간다.
1-1. Paging Behavior
구현코드:
import SwiftUI
struct CarouselViewSwiftUI: View {
/// Carousel에서 보여줄 색상 배열
let colors: [Color] = [.red, .blue, .green, .pink, .purple]
/// 아이템 사이 간격
let itemSpacing: CGFloat = 12
/// 아이템 너비
let itemWidth = 200
var body: some View {
ScrollView(.horizontal) {
HStack(alignment: .center, spacing: itemSpacing) {
ForEach(colors, id: \.self) { color in
color
.frame(width: CGFloat(itemWidth), height: 300)
.cornerRadius(12)
} // forEach
} // hStack
.padding(.leading, 24)
} // scrollView
.scrollIndicators(.hidden)
.scrollTargetBehavior(.paging) // <<<<< 스크롤 방식 정의
.frame(height: 300)
} // body
} // view
.scrollTargetBehavior(.paging)을 scrollView에 추가해주면, 페이징 처리가 된다. 스르륵~ 넘어가던게 스륵탁! 하며 넘어가는 걸 확인할 수 있다. 해당 모디파이어는 스크린 너비가 스크롤 오프셋이 되도록 해준다.
그런데..... 단순히 페이징 기능이 필요하면 tabView를 쓰는 게 더 낫지 않을까... 하는 생각도 들었다.
1-2. ViewAligned
구현코드:
import SwiftUI
struct CarouselViewSwiftUI: View {
/// Carousel에서 보여줄 색상 배열
let colors: [Color] = [.red, .blue, .green, .pink, .purple, .gray, .mint]
/// 아이템 사이 간격
let itemSpacing: CGFloat = 12
/// 아이템 너비
let itemWidth = 200
var body: some View {
ScrollView(.horizontal) {
HStack(alignment: .center, spacing: itemSpacing) {
ForEach(colors, id: \.self) { color in
color
.frame(width: CGFloat(itemWidth), height: 300)
.cornerRadius(12)
} // forEach
} // hStack
.padding(.leading, 24)
} // scrollView
.scrollIndicators(.hidden)
.scrollTargetBehavior(.viewAligned) // <<<<< 스크롤 방식 정의
.frame(height: 300)
} // body
} // view
.scrollTargetBehavior(.viewAligned)로 변경하면, paging으로 설정했을 때와 달리 부드럽게 스크롤이 동작되는 걸 확인할 수 있다. 그런데 랜덤하게 멈추는데, 이 때 scrollTargetLayout()를 사용하면 아이템(color)별로 멈추게 구현할 수 있다.
구현코드:
import SwiftUI
struct CarouselViewSwiftUI: View {
/// Carousel에서 보여줄 색상 배열
let colors: [Color] = [.red, .blue, .green, .pink, .purple, .gray, .mint]
/// 아이템 사이 간격
let itemSpacing: CGFloat = 12
/// 아이템 너비
let itemWidth = 200
var body: some View {
ScrollView(.horizontal) {
HStack(alignment: .center, spacing: itemSpacing) {
ForEach(colors, id: \.self) { color in
color
.frame(width: CGFloat(itemWidth), height: 300)
.cornerRadius(12)
} // forEach
} // hStack
.scrollTargetLayout() // <<<<< 추가
.padding(.leading, 24)
} // scrollView
.scrollIndicators(.hidden)
.scrollTargetBehavior(.viewAligned) // <<<<< 스크롤 방식 정의
.frame(height: 300)
} // body
} // view
.scrollTargetBehavior에서 viewAligned를 해주면, 아이템의 오프셋이 가운데로 오도록 해준다. (나는 padding을 추가하고 있어서 조금 다르게 구현되었지만..!)
References
https://developer.apple.com/documentation/swiftui/view/scrolltargetlayout(isenabled:)
https://developer.apple.com/documentation/swiftui/view/scrolltargetbehavior(_:)
https://medium.com/@myshkinasasha/swiftui-scrollview-with-paging-ios17-and-below-a293766e19e8
'🍎 Dev > SwiftUI' 카테고리의 다른 글
[SwiftUI] ObservableObject, ObservedObject, Published (6) | 2024.10.07 |
---|---|
[SwiftUI] Navigation 1 (0) | 2024.09.24 |
[SwiftUI] Property Wrapper 총정리 (0) | 2024.07.26 |
[SwiftUI] Frame (0) | 2024.07.22 |
[SwiftUI] @State (0) | 2024.05.22 |