https://calliek.tistory.com/62
๋ฌธ์ ์ :
- ๋ถ๊ณผ ์ผ์ฃผ์ผ ์ ๊ฐ์ ๋งฅ๋ฝ์ ๊ฒ์๊ธ์ ์์ฑํ์๋๋ฐ, ํด๋น ๋ฐฉ์๋๋ก ๊ตฌํํ๋ฉด ํ์ด๋จธ๊ฐ ์๋ ๋ ๋ index ๋ณ๊ฒฝ ์ ์ ๋๋ฉ์ด์ ์ ์ฉ์ด ์ ๋๋ ๋ฌธ์ ๊ฐ ์์๋ค.
- ๊ทธ๋์ ํด๋น ๊ธ์ ์ถ๊ฐํด๋์๋ ๋ธ๋ก๊ทธ๊ธ๋ก ์ ๋ฉด ์์ ์ ํด๋ณด์์ผ๋, ๋๋๊ทธ๋ฅผ ํ ๋ ํด๋น ์ธ๋ฑ์ค์ ํ๋ฉด์ด ๋๋๊ทธ ๋๋ ๋ชจ์ต์ด ๋ณด์ด์ง ์๊ณ ์ก์ ์ด ๋ค ๋๋๊ณ ์ฌ๋ผ์ด๋ ์ ๋๋ฉ์ด์ ์ผ๋ก ํ๋ฉด์ด ์ ํ๋์๋ค.
ํด๊ฒฐ:
- ๋ค๋ฅธ ์ฌ๋๋ค ์ฝ๋๋ฅผ ์ปค์คํ ํ๋ ๋์ ๊ทธ๋ฅ ๋ด๊ฐ ์ปค์คํ ํ๊ธฐ๋ก ํ๋ค^^...
๊ตฌํ (ํ์ด๋จธ X, ์ค์ง infinite Carousel):
1. ํ์ดํฌ ์์ดํ ์ ์ถ๊ฐํด์ค๋ค.
/// ํ์ดํฌ ์์ดํ
์ถ๊ฐ
private func configureCircularItemList() {
// ์์๊ณผ ๋์ ๋ง์ง๋ง, ์ฒซ ๋ฒ์งธ ์์ ์ถ๊ฐ
colors.insert(colors[colors.count - 1], at: 0)
colors.append(colors[1])
}
- UIKit์ผ๋ก ๋ฌดํ ์คํฌ๋กค์ ๊ตฌํํ ์ฌ๋๋ค์ ๋ณด๋๊น, ์ํ ๋ ๋ฐฐ์ด์ 0๋ฒ์งธ์ ๋ง์ง๋ง ์ธ๋ฑ์ค์ ์ ๋ค๋ก ๋ง์ง๋ง, 0๋ฒ์งธ ์์ดํ ์ ์ถ๊ฐํด์ฃผ๊ณ ๊ฐ๊ฐ ๋ฌดํ์คํฌ๋กค์ด ๋์ด์ผ ํ๋ ์ธ๋ฑ์ค์ ๋๋ฌํ๋ฉด ๋ณด์ฌ์ค์ผํ๋ ์ธ๋ฑ์ค๋ฅผ ๋ฌ๋ฆฌ ํ๋ ์์(?) ๋์์ ๋ฐฉ์์ ํํ๋ค.
2. ๋ฌดํ ์คํฌ๋กค์ ์ํ ํ์ดํฌ ์ธ๋ฑ์ค ๊ณ์ฐํ๊ธฐ
/// ๋ฌดํ ์คํฌ๋กค ๊ตฌํ
private func getInfiniteScrollIndex(newValue: Int) {
if newValue == 0 {
// ์ฒ์์ผ๋ก ๊ฐ์ ๋ ๋์ชฝ์ผ๋ก ์ด๋
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
currentIndex = colors.count - 2
}
} else if newValue == colors.count - 1 {
// ๋ง์ง๋ง์ผ๋ก ๊ฐ์ ๋ ์ฒซ์ชฝ์ผ๋ก ์ด๋
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
currentIndex = 1
}
}
}
3. ๋ฉ์ธ ๊ตฌํ (์ ์ฒด ์ฝ๋)
import SwiftUI
struct CustomCarouselSlides3: View {
/// ์ํ๋ฐฐ์ด
@State var colors: [Color] = [.red, .orange, .yellow, .green, .blue]
/// ํ์ฌ ์ธ๋ฑ์ค
@State private var currentIndex: Int = 1
var body: some View {
TabView(selection: $currentIndex) {
// ์ํ ๋ฆฌ์คํธ์์ ์ฒซ ๋ฒ์งธ์ ๋ง์ง๋ง ์์ ์ถ๊ฐ
ForEach(0..<colors.count, id: \.self) { index in
Rectangle()
.fill(colors[index])
.frame(width: UIScreen.main.bounds.width)
.tag(index) // ํ์ฌ ์ธ๋ฑ์ค๋ฅผ ๊ตฌ๋ถํ๊ธฐ ์ํ ํ๊ทธ
} //: ForEach
.ignoresSafeArea()
} //: Tabview
.ignoresSafeArea()
// MARK: - ์ธ๋์ผ์ดํฐ
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
/// Custom Indicator
.overlay(alignment:.bottom) { }
// MARK: - LifeCycle
.onAppear {
configureCircularItemList() // ์๋ค ๋ฐฐ์ด์ ์ถ๊ฐ
currentIndex = 1
}
// MARK: - Action
.onChange(of: currentIndex) { newValue in
getInfiniteScrollIndex(newValue: newValue) // ๋ฌดํ ์คํฌ๋กค ๊ตฌํ
} //: onChange
} //: Body
} //: View
extension CustomCarouselSlides3 {
/// ๋ฌดํ ์คํฌ๋กค ๊ตฌํ
private func getInfiniteScrollIndex(newValue: Int) {
if newValue == 0 {
// ์ฒ์์ผ๋ก ๊ฐ์ ๋ ๋์ชฝ์ผ๋ก ์ด๋
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
currentIndex = colors.count - 2
}
} else if newValue == colors.count - 1 {
// ๋ง์ง๋ง์ผ๋ก ๊ฐ์ ๋ ์ฒซ์ชฝ์ผ๋ก ์ด๋
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
currentIndex = 1
}
}
}
/// ํ์ดํฌ ์์ดํ
์ถ๊ฐ
private func configureCircularItemList() {
// ์์๊ณผ ๋์ ๋ง์ง๋ง, ์ฒซ ๋ฒ์งธ ์์ ์ถ๊ฐ
colors.insert(colors[colors.count - 1], at: 0)
colors.append(colors[1])
}
/// ์ฐ ์ธ๋ฑ์ค ๊ตฌํ๊ธฐ
/// - ์ธ๋์ผ์ดํฐ์ ํ์ฌ ์ธ๋ฑ์ค ๋ง์ถ๊ธฐ ์ํจ
private func getRealIndex() -> Int { }
}
OnAppear:
- ์ํ ๋๋ ๋ฐฐ์ด์ ์์ดํ ์ ์ถ๊ฐํด์ฃผ์๊ธฐ ๋๋ฌธ์ ๊ธฐ๋ณธ ์ธ๋ฑ์ค๋ 0์ด ์๋๋ผ 1๋ก ์ค์
- ํ๋ฉด์ ๋ค์ด์ฌ ๋ ๋ฐฐ์ด์ ๊ฐ๊ฐ ๋ง์ง๋ง, ์ฒซ๋ฒ์งธ ์ธ๋ฑ์ค๋ฅผ ์ถ๊ฐ (configureCircularItemList)
ForEach:
- TabView์ selection์ผ๋ก current Index๋ฅผ ์ถ์ ํ๊ณ ์๊ธฐ ๋๋ฌธ์, ๊ฐ ์์ดํ ์ ์ธ๋ฑ์ค๋ tag๋ฅผ ์ฌ์ฉํด์ ์ถ์ ํ๋ค.
onChange:
- current Index๊ฐ ๋ฐ๋ ๋์ ํ์ ์ก์ ์ ์ถ๊ฐ ํ๋๋ฐ, ์ธ์์ ์ผ๋ก ์ถ๊ฐํ ํ์ดํฌ ์์ดํ ์ ํตํด ๋ฌดํ ์คํฌ๋กค์ ๊ตฌํ
- ํด๋น ๋ถ๋ถ์ getInfiniteScrollIndex
๊ตฌํ (ํ์ด๋จธ O):
1. ์ ์ฒด์ฝ๋
import SwiftUI
struct CustomCarouselSlides3: View {
/// ์ํ๋ฐฐ์ด
@State var colors: [Color] = [.red, .orange, .yellow, .green, .blue]
/// ํ์ด๋จธ
@State var timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
/// ํ์ฌ ์ธ๋ฑ์ค
@State private var currentIndex: Int = 1
var body: some View {
TabView(selection: $currentIndex) {
// ์ํ ๋ฆฌ์คํธ์์ ์ฒซ ๋ฒ์งธ์ ๋ง์ง๋ง ์์ ์ถ๊ฐ
ForEach(0..<colors.count, id: \.self) { index in
Rectangle()
.fill(colors[index])
.frame(width: UIScreen.main.bounds.width)
.tag(index) // ํ์ฌ ์ธ๋ฑ์ค๋ฅผ ๊ตฌ๋ถํ๊ธฐ ์ํ ํ๊ทธ
} //: ForEach
.ignoresSafeArea()
} //: Tabview
.ignoresSafeArea()
// MARK: - ์ธ๋์ผ์ดํฐ
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
/// Custom Indicator
.overlay(alignment:.bottom) { }
// MARK: - LifeCycle
.onAppear {
configureCircularItemList() // ์๋ค ๋ฐฐ์ด์ ์ถ๊ฐ
currentIndex = 1
}
// MARK: - Action
.gesture(
DragGesture()
.onChanged { _ in
timer.upstream.connect().cancel()
DispatchQueue.main.asyncAfter(deadline: .now() + 3){
timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
}
}
)
.onChange(of: currentIndex) { newValue in
getInfiniteScrollIndex(newValue: newValue) // ๋ฌดํ ์คํฌ๋กค ๊ตฌํ
} //: onChange
.onReceive(timer) { _ in
/// ํ์ด๋จธ ๊ฐ๋ ์ ์ธ๋ฑ์ค ํ๋์ฉ ์ฎ๊น
withAnimation(.easeIn) {
currentIndex += 1
}
}
} //: Body
} //: View
2. ๊ธฐ์กด ์ฝ๋์ ์ถ๊ฐ ๋ ๋ถ๋ถ
/// 1.ํ์ด๋จธ
@State var timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
-------------------------------
/// 2.ํ์ด๋จธ ๊ฐ๋
.onReceive(timer) { _ in
/// ํ์ด๋จธ ๊ฐ๋ ์ ์ธ๋ฑ์ค ํ๋์ฉ ์ฎ๊น
withAnimation(.easeIn) {
currentIndex += 1
}
}
-------------------------------
/// 3.ํ์ด๋จธ ์ด๊ธฐํ + ์ฌ๊ธฐ๋
.gesture(
DragGesture()
.onChanged { _ in
timer.upstream.connect().cancel()
DispatchQueue.main.asyncAfter(deadline: .now() + 3){
timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
}
}
)
1. 3์ด ํ์ด๋จธ ์ถ๊ฐ
2. onReceive
- ํ์ด๋จธ๊ฐ ๋ณ๊ฒฝ ๋ ๋๋ง๋ค (3์ด๋ง๋ค) ์ธ๋ฑ์ค๋ฅผ ๋ณ๊ฒฝ
- withAnimation์ ์ถ๊ฐํด์ ์ฌ๋ผ์ด๋ ์ ๋๋ฉ์ด์ ๊ตฌํ
3. gesture()๋ก ์ฌ์ฉ์๊ฐ ์๋์ผ๋ก ํ๋ฉด ์ด๋ ์ ํ์ด๋จธ ๊ด๋ฆฌ
...๋ฅผ ์ํด์ฃผ๋ฉด ๋๋๊ทธ๋ฅผ ํ๋ ์์ค์ 3์ด๊ฐ ์ง๋๋ฉด ์ธ๋ฑ์ค๊ฐ +1์ด ๋์ด๋ฒ๋ฆฐ๋ค.
- ์ฌ์ฉ์์ ๋๋๊ทธ ์ก์ ์ด ๊ฐ์ง ๋๋ฉด ํ์ด๋จธ๋ฅผ ์ด๊ธฐํ
- 3์ด ๋ค ํ์ด๋จธ ๊ฐ๋
References
https://ios-development.tistory.com/1197
์ฌ์ค ์ด๊ฒ ์ต์ ์ ๋ฐฉ๋ฒ์ธ์ง๋ ๋ชจ๋ฅด๊ฒ ๋ค...
๋์ค์ ๋ ์ข์ ๋ฐฉ๋ฒ์ ์๊ฒ ๋๋ฉด ์๋ฆฌ์ฆ์ฒ๋ผ ๋ค์ ๊ฒ์๊ธ์ ์ฐ๊ฑฐ๋ ์ด๊ณณ์ ์ถ๊ฐํด์ผ๊ฒ ๋ค.
'๐ Dev > ๊ตฌํ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[SwiftUI] Infinite Carousel ๊ตฌํํ๊ธฐ 1 (feat. Timer) (0) | 2024.09.05 |
---|---|
[SwiftUI] pagerView ๋ง๋ค๊ธฐ (iOS ๋ฒ์ ๋์) (2) | 2024.08.14 |
[SwiftUI] CustomPopUpView ์ ๋๋ฉ์ด์ ํจ๊ณผ ํด๊ฒฐํ๊ธฐ (0) | 2024.06.27 |
[SwiftUI] ์ค์ ๋ก ์น์ ์ ์๋คํ๋ค ๊ตฌํํ๊ธฐ (0) | 2024.06.21 |
[SwiftUI] pre/next buttons๊ฐ ์๋ ์ด๋ฏธ์ง ์ฌ๋ผ์ด๋ ๊ตฌํํ๊ธฐ (2) | 2024.05.16 |