[SwiftUI] Image Carousel View 구현하기 (이미지 갤러리 뷰)
ScrollView(.horizontal) {
HStack (spacing: 10) {
ForEach(Array(images.enumerated()), id: \.offset) { i, image in
Image(image)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: UIScreen.main.bounds.width-90, height: UIScreen.main.bounds.width-90)
.cornerRadius(10)
.shadow(color: .black.opacity(0.2), radius: 8, x: 0, y: 4)
.scrollTransition { content, phase in
content
.scaleEffect(y: phase.isIdentity ? 1 : 0.8)
.opacity(phase.isIdentity ? 1 : 0.8)
}
.id(i)
}
}
.scrollTargetLayout()
}
.contentMargins(.horizontal, 45, for: .scrollContent)
.scrollTargetBehavior(.viewAligned)
.scrollIndicators(.hidden)
.scrollPosition($position)
.scrollTargetLayout()
은 스크롤 뷰 내의 컨테이너 뷰에 modifier로 추가한다.
이 modifier의 역할
- 그 컨테이너 내부의 뷰들이 정렬(맞춤 - align) 대상 뷰로 고려되도록 함.
- 그리고 내부 뷰들에게 scrollTarget(isEnabled:) modifier를 적용해주기.
- 추가로, 이 컨테이너가 스크롤 타겟 레이아웃이고, 그 내부에 중첩되어있는 다른 뷰들은 스크롤 타겟 레이아웃이 아니야. 라고 정해주는 역할도 한다고 한다.
예를 들어, HStack(컨테이너 뷰) 내부에 Image A, Image B, Image C 가 있으면,
이들이 target layout 에 의해 정렬되어야 하는 대상들이며, 각각에게 현재 scroll target임을 전달해준다는 의미인 것 같다.
.scrollTargetBehavior()
이 modifier는 위의 .scrollTargetLayout()과 항상 함께 쓰인다. 다만, 스크롤뷰 내부의 컨테이너 뷰가 아니라 스크롤뷰에 적용되는 Modifier이다.
이는 스크롤 포지션 (지금 어떤 아이템의 포지션인지) 을 기반으로 아이템의 레이아웃을 동적으로 배치시켜주는 역할을 한다.
어떻게 배치할지는 매개변수로 정할 수 있다.
매개변수로 전달할 수 있는 값은 .paging와 .viewAligned가 있는데,
- .paging는 스크롤 타겟(대상)들을 컨테이너를 기준으로 align하고,
- .viewAligned는 스크롤 타겟들을 스크롤 뷰의 geometry를 기준으로 align한다. 여기서 view는 스크롤뷰의 (보이는) 영역이라고 생각하면 될 것 같다. (스크린에서 스크롤뷰가 차지하는 만큼을 말하는 것 같다. - 아이템이 늘 중앙에 위치하길 원하면 이걸 써야함 + 양쪽에 content margin 추가 필요)
viewAligned를 사용하면 item이 스크롤뷰의 가장 가까운 edge에 붙도록 한다고함.
.viewAligned를 커스텀할 수 있는 부분
매개변수로 limitBehavior를 지정할 수 있는데 이는 한번에 스크롤할 수 있는 아이템 수를 제한할 것인지에 대한 부분이다.
우리가 스크롤할 때 한번에 스와이프를 길게 하면 끝까지 스크롤할 수 있을 수도 있고, 몇개만 넘길 수도 있고, 한개만 넘길 수도 있는데, 이 부분을 정하는 매개변수다.
.always, .alwaysByFew, .alwaysByOne, never, automatic 이렇게 항목이 있는데,
always는 언제나 한번에 스크롤할 수 있는 수를 few 만큼으로 제한한다는 것이고,
alwaysByOne은 언제나 한번에 한개의 뷰만 스크롤할 수 있도록 한다는 것,
never는 전혀제한하지 않아서 한번에 길게 스와이프하면 처음 item에서 끝 item까지도 이동이 가능하게 한다는 것이다.
아무것도 설정하지 않으면 디폴트로 horizontal 스크롤의 경우 가로 폭이 좁으면 alwaysByFew처럼 동작하고, 그렇지 않으면 never처럼 동작하도록 한다고 한다.
.scrollTransition()
아이템이 스크롤될 때 애니메이션을 적용할 수 있도록 하는 modifier
매개변수로 클로저를 갖고, 이 클로저는 두개의 파라미터를 갖는다.
content와 phase가 그것인데, content는 지금 스크롤 된 item의 뷰를 의미하고, phase는 scroll phase (스크롤 단계..?시점?)을 의미하는데 이는 현재 스크롤 포지션(어떤 item(id)가 현재 보여지고 있는지?)와 스크롤 속도(?)를 나타내는 값을 갖고 있다고 함.
참고로 이 .scrollTransition은 각 item에 붙는다.
아무튼 phase의 isIdentity 값이 지금 이 item이 뷰에 전부 보여지고 있는지 알려주고, 이를 활용해서 현재 보여지고 있는 아이템과 그렇지 않은 아이템에게 서로 다른 크기나 투명도를 부여해서 애니메이션을 구현할 수 있다.
이 글은..아래의 고마운 medium 블로그 글을 대부분 참고해서 작성했고,
애플 공식 문서의 API Overview와 Discussion 부분도 참고했다.
https://medium.com/@kusalprabathrajapaksha/animated-carousel-view-swiftui-cee1954f0be7
Animated Carousel View — SwiftUI
scrollTargetBehavior(_:), scrollTargetLayout() & scrollTransition(_:)
medium.com
https://developer.apple.com/documentation/swiftui/view/scrolltargetlayout(isenabled:)
scrollTargetLayout(isEnabled:) | Apple Developer Documentation
Configures the outermost layout as a scroll target layout.
developer.apple.com
https://developer.apple.com/documentation/swiftui/viewalignedscrolltargetbehavior
ViewAlignedScrollTargetBehavior | Apple Developer Documentation
The scroll behavior that aligns scroll targets to view-based geometry.
developer.apple.com