안녕하세요. 린다입니다.
프로젝트 하면서 구현했던 무한(?) 스와이프 뷰가 있는데요.
그 뷰를 구현하면서 샘플로 그려봤던 뷰를 정리해보려고 해요.
샘플 사진은.. 그냥 제 맥북에 있는 제 사진을 썼기 때문에..
흐린눈 하시고 넘어가주시면 됩니다 ㅠ.ㅠ
우선 5장의 데이터를 보여주는데
스와이프는 오른쪽 -> 왼쪽 방향으로만 가능한 뷰이며,
오른쪽으로 넘길 때마다 사진이 달라지며,
다 넘기면, 새로운 5장의 사진이 무한정으로 보이는? 형태였습니다.
+) MVVM 기준으로 구현해야 하는데 아직 분리 안 해놓았습니다 😓
1. 카드 뷰 만들기
먼저 저 사각형 모양의 사진뷰에 대한 작업을 했습니다.
저희 뷰에는 유저 이미지, 이름, 게시물이 게시된 시간, 좋아요 수, 제목
이런 값들이 이미지 위에 올라가야 했어요.
ZStack으로 구현하기에는 사진의 위쪽과 아래쪽으로 모두 값이 있어야하기 때문에
사진의 크기만큼 값을 지정해서 올릴 수 있는 overlay를 활용해서 구현했습니다.
data는 카드를 indices 를 사용하여 그리기 때문에 그 때 받아오는 값들이에요.
struct RoundedCardView: View {
@ObservedObject var testViewModel = TestViewModel()
var data: HomeSample
var body: some View {
VStack(alignment: .leading) {
Image("\(data.image)")
.resizable()
.clipShape(RoundedRectangle(cornerRadius: 20))
.overlay {
VStack(alignment: .leading) {
HStack(spacing: 5) {
Image("서연")
.resizable()
.frame(width: 50, height: 50)
VStack(spacing: 4) {
Text("\(data.user)")
Text("\(data.time)")
}
.font(.system(size: 14, weight: .bold))
.foregroundStyle(.white)
Spacer()
}
Spacer()
VStack(alignment: .leading, spacing: 6) {
Text("\(data.whisky)")
Text("\(data.title)")
.lineLimit(2)
}
.padding(10)
.foregroundColor(.white)
}
}
}
}
}
2. Foreach를 사용해서 무한반복
이제 카드들을 무한정으로 반복해야 하기 때문에 foreach를 사용했습니다.
우선 data를 index 값을 가져올 수 없더라구요....? index로 쓰고 싶어서 indices 쓴건데..
그래서 index 값을 별도로 빼서 적용하려고 activeIndex라는 변수를 만들어서 사용했어요.
swipe되는 정도를 조절하고 싶으면 value 값을 조절하시면 됩니다.
value.translation.width < -100 해당 값이 드래그 된 정도에 대한 값이에요.
struct CardSwipeTestView: View {
@StateObject var testViewModel = TestViewModel()
@State private var activeIndex = 1 // 사진 index 처리를 위한 변수
@State private var countIndex = 1
var height = UIScreen.main.bounds.height * 0.45 // 사진 높이
var body: some View {
ScrollView {
ZStack {
ForEach(testViewModel.sampleData.indices, id: \.self) { data in
RoundedCardView(data: testViewModel.sampleData[data])
.gesture(
DragGesture()
.onEnded({ value in
if value.translation.width < -100 {
let temp = testViewModel.sampleData[data]
testViewModel.sampleData.remove(at: data)
testViewModel.sampleData.append(temp)
activeIndex = data
activeIndex = activeIndex + 1
countIndex += 1
// 만약에 무한으로 추가가 되어야 한다면??
// 5장으로 반복할거면 없어도 됨
if countIndex > 5 {
testViewModel.sampleData = HomeSample.moreSample
countIndex = 0
}
}
})
)
.zIndex(Double(-data))
.animation(.easeInOut, value: testViewModel.sampleData[data].image)
.offset(x: data == activeIndex ? 0 : 0 + (CGFloat(data - activeIndex) * 16))
.frame(height: data == activeIndex ? height : height - (CGFloat(data - activeIndex) * 16))
.opacity(data == 0 ? 1 : 0.6)
.disabled(data != 0)
.padding(.horizontal, 40)
.padding(.trailing, 30)
}
.shadow(radius: 3)
}
}
}
}
modifier만 따로 빼서 설명을 써볼게요.
- zIndex : Z스택이기 때문에 마지막 사진이 가장 위에 올라옵니다. 그거에 대한 처리를 위해 -data를 했어요.
- animation : 애니메이션이 없으면 뚝뚝 끊기는 느낌이 들어서 추가했습니다.
- offset : 첫번째 오프셋은 모든 사진들의 위치가 data 값에 따라서 조금씩 뒤에 보여야 하기 때문에
차이를 두기 위해서 20 뒤로 가도록 설정했습니다. - frame : 뒤에 있는 사진일수록 사이즈도 작아야 하기 때문에 offset과 동일하게 설정했어요.
- opacity : 뒤에 있을수록 투명하게 보이도록 설정
- disabled : 지금 뷰가 ZStack으로 쌓여 있기 때문에 뒤에 있는 뷰들도 터치가 가능합니다.
뒤에 있는 뷰를 기준으로 swipe하려고 하면 뷰가 깨지기 때문에 뒤에 있는 뷰들은 터치를 막았어요.
해당 처리를 안 하면 뷰 사이즈가 이상합니다..!!
// Z스택으로 쌓았기 때문에 마지막 사진부터 보여지므로, 반대로 뒤집게 위한 처리
.zIndex(Double(-data))
// swipe 시 자연스러운 애니메이션 처리
.animation(.easeInOut, value: testViewModel.sampleData[data].image)
// 사진을 일정한 비율씩 겹겹이 높기 위한 offset
.offset(x: data == activeIndex ? 0 : 0 + (CGFloat(data - activeIndex) * 20))
// 뒤로 갈수록 height를 작아지게 하기 위한 처리 (첫장보다 20씩 작아지게 처리)
.frame(height: data == activeIndex ? height : height - (CGFloat(data - activeIndex) * 20))
// 첫장을 제외하고는 투명도 적용
.opacity(data == 0 ? 1 : 0.6)
// 맨 위에 있는 뷰를 제외하고 터치 비활성화
.disabled(data != 0)
그리고 countIndex는 5장의 카드 중에서 몇장까지 넘어간건지를 계산하는 변수에요.
중간에 if문을 통해서 5장까지 다 돌았다면, 새로운 데이터를 가져오도록 해봤는데 잘 되더라구요. ..
혹시나 싶어서 추가해 놓은 부분입니다.
이 if문 지우면 위에 gif처럼 5장의 사진이 무한 반복돼요!!!
// 만약에 무한으로 추가가 되어야 한다면??
if countIndex > 5 {
testViewModel.sampleData = HomeSample.moreSample
countIndex = 0
}
이렇게 하면.. 이렇게 연결돼서 보입니다.
지금 1번째 사진이랑 추가로 들어온 moreSample의 마지막사진이 동일해서
헷갈려 보이긴 하는데요.. 어쨌든 잘 바뀝니다.
(밑은 스크롤까지 들어가 있는 구현 화면이에요. 위에 gif는 스크롤 없음)
사실 마음에 안 드는 부분이 몇가지 있긴해요.
일단.. padding 값을 저렇게 주는게 맞나? 라는 의문이 있습니다.
ZStack으로 쌓기도 했고, offset으로 x 위치를 다 조절해서 패딩값이
생각하는대로 안 들어가서 일단 프리뷰 보면서 맞춘거거든요.
맨처음에는 width를 고정값으로 했는데,
ScrollView로 묶은 다음에 고정값으로 가져가니 뒤로 나열되어 있는 카드들이 잘려서
..우선 padding 값으로 저렇게 적용해놓았습니다..
다시 확인해 보니 offset 크기 만큼 계산해서 padding값을 넣어주면 맞긴하네요ㅠ
(16을 기준으로 계산해서 넣어주면 됨..)
혹시 이 외에도 좋은 방법이나 잘리는 이유를 아시는 분은 공유 부탁드려요 ㅠㅠ
그리고 지금보니 5장씩 다 돌때 사진을 다시 가져오면 바뀌기전까지는 사진이 똑같이니
애초에 통신으로 10장을 받아와서 5장씩 나눠서 처리를 하는게 더 좋은 방법이겠다 싶네요.
이 부분은 사용하게 된다면.. 고쳐서 가져오겠습니다 ㅎㅎ ...
코드는 짧지만.. 구현하는데 사실 2일 걸렸습니다 🥹
맨처음에는 어떻게 해야할지 감이 안 잡혀서..
라이브러리도 엄청 찾아봤는데 뭔가 조건에 딱 맞는 건 없더라구요 ㅠ
Card Stack View, infinite card stack view, card carousel 등등...
모두 비슷한 키워드니 저랑 비슷하게 구현을 해야한다면 이런 키워드로도 한번 검색해보시는것을 추천드려요!!
'Swift > SwiftUI' 카테고리의 다른 글
SwiftUI, Layout과 Alignment을 알아보자 (2019 WWDC) (1) | 2024.03.28 |
---|---|
SwiftUI, Custom TextEditor 만들기 (0) | 2024.03.20 |
SwiftUI, Line Height 명확하게 설정하기 (feat, linespacing) (0) | 2024.03.02 |
SwiftUI, TextField에서 한글과 영어만 입력받는 방법 (특수문자, 초성 제외하기) (0) | 2024.02.22 |
SwiftUI로 Apple Login 구현 시 Button Custom을 쉽게 구현하는 방법 - (.blendMode 사용기) (0) | 2024.02.09 |