์ด๋ฏธ์ง ์ฌ๋ผ์ด๋๋ทฐ๋ฅผ ๋ง๋ค์ด์ผ ํ๋๋ฐ, ํ์ด์ง ์ธ๋์ผ์ดํฐ๊ฐ ์ ํ์ด ์ ๊ณตํด์ฃผ๋ ๊ธฐ๋ณธ ์ธ๋์ผ์ดํฐ๊ฐ ์๋๋ผ ์ปค์คํ ์ ํด์ผํ๋ค.
์ฐธ๊ณ ๋ก ์ธ๋์ผ์ดํฐ๋
์์ ๊ฐ์ ๋ทฐ๊ฐ ์๋ค๋ฉด,
์ด๋ฏธ์ง์ ์์๋ฅผ ๋ํ๋ด์ฃผ๋ ๋ถ๋ถ์ ํด๋นํ๋ค.
ํผ๊ทธ๋ง์ ์ฌ๋ผ์ ์๋ ์ธ๋์ผ์ดํธ ๋์์ธ์ด ํ์ฌ ์ด๋ฏธ์ง์ ์์์ผ ๊ฒฝ์ฐ ์ธ๋์ผ์ดํฐ ํํ๊ฐ ๋ฐ๋๋ ๋์์ธ์ด์๋ค. ๋ฐ๋ผ์, ๋์์ธ ๊ฐ์ด๋๋ฅผ ์ถฉ์กฑ์ํค๊ธฐ ์ํด์ SwiftUI์์ Tabview๊ฐ ์ ๊ณตํด์ฃผ๋ ์ํ ์ธ๋์ผ์ดํฐ๋ฅผ ์ฌ์ฉํ ์๊ฐ ์์๊ณ , custom ํ์์ผ๋ก ์ง์ ๊ตฌํํด์ผํ๋ค.
๋ด๊ฐ ๊ตฌํํด์ผ ํ๋ ๊ฒ:
1. ์ด๋ฏธ์ง ์ฌ๋ผ์ด๋
2. ์ผ์ ์๊ฐ ๊ฐ๊ฒฉ์ ๋๊ณ ์ฌ๋ผ์ด๋์ ์ด๋ฏธ์ง๊ฐ ๋ฐ๋์ด์ผ ํจ
3. ์ด๋ฏธ์ง๊ฐ ๋ฐ๋ ๋, ํด๋น ์ด๋ฏธ์ง ์์์ ์ธ๋์ผ์ดํฐ ๋ชจ์์ด ์ํ์์ ๊ฐ๋ก๋ก ๊ธด ํ์ํ์ผ๋ก ๋ณ๊ฒฝ๋์ด์ผ ํจ
๊ทธ๋์
https://www.youtube.com/watch?v=uo8gj7RT3H8
SwiftUI๋ก ์์ ํ๋ ๊ฒ ๊ฑฐ์ ์ฒ์์ด์๊ธฐ ๋๋ฌธ์ ์ ์์์ ์ฐธ๊ณ ํ์ฌ ์ฝ๋๋ฅผ ์์ฑํ์๋ค.
iOS 15๊ธฐ์ค์ผ๋ก ์ ์์์ ์ฝ๋๋ก ๊ตฌํ์ ํ๋ฉด index๊ฐ 0์์ 1์ ๋์ด๊ฐ ๊ฒฝ์ฐ ์ธ์๋ ์ฌ๋ผ์ด๋ ๊ธฐ๋ฅ์ด ๋์ง ์์๋ค.
iOS 15์ดํ ๋ถํฐ ๋ฐ์ํ๋ ๋ฌธ์ ๋ก, ์์ ์ ์ฝ๋๋ ์ฌ๋ผ์ด๋๊ฐ ๋์ด๊ฐ๋ฉด ์ธ๋ฑ์ค๊ฐ ์ธ์ ๋์ง ์๊ณ ์ฌ๋ผ์ง๋ ๊ฒ ๊ธฐ๋ฅ ๋ถ๋ฅ์ ์์ธ์ด์๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
- ์ฌ๋ผ์ด๋ ๋ฐฐ์ด์ ์ธ๋ฑ์ค๋ฅผ ์ธ์ํ์ง ๋ชป ํ๊ณ ์๋ ๊ฒ ๋ฌธ์ ์๊ธฐ ๋๋ฌธ์, ์ธ๋ฑ์ค๋ฅผ ์ธ์ ์์ผ์ฃผ๊ณ ๊ทธ ์ธ๋ฑ์ค ๊ธฐ์ค์ผ๋ก ์ฌ๋ผ์ด๋ ์ด๋ฏธ์ง ๊ต์ฒด + ์ธ๋์ผ์ดํฐ ํํ custom ํด์ฃผ๋ ์์ผ๋ก ๋ฐฉํฅ์ ์ก์๋ค.
- ์ข ํฉํ์๋ฉด (1) currentIndext์ ํ์ฌ ํ์ด์ง ์ธ๋ฑ์ค๋ฅผ ์ ์ฅํ๋ @state ํ์์ผ๋ก ์ ์ธํด์ฃผ๊ณ , (2) currentIndex๋ฅผ ๊ธฐ์ค์ผ๋ก ์คํ์ ๋ณ๊ฒฝ ์ฌ๋ถ๋ฅผ ๊ตฌํ๊ณ , (3) tag๋ก ํ์คํ๊ฒ ์ ์ฅํ๊ณ , (4) ์ธ๋์ผ์ดํฐ ํํ๋ฅผ ๊ทธ์ ๋ง๊ฒ ๋ฐ๊ฟ์คฌ๋ค.
24.04.25 ์์ฑ
์๋๋ ์์์ ๊ธฐ๋ฐ์ผ๋ก ์์ฑํ ์ฝ๋๋ฅผ ๊ฐ์ ํ ์ฝ๋์ด์ ๋ด๊ฐ ์ํ UI ๋ฐ ๊ธฐ๋ฅ ๊ตฌํ์ ํ ์ฝ๋๋ค.
๊ธฐํ ์ค์ ๋ฑ์ ์ฃผ์ ์ฐธ๊ณ .
....์๋๋ฐ ๋ฆฌํฉํ ๋งํ ์ฝ๋๋ ์๋ ๋ฆฌํฉํ ๋งํ ๋ ์ง(24.09.06)์ ํจ๊ป ์์ผ๋ ํด๋น ์ฝ๋ ์ฐธ๊ณ ๋ฅผ ์ถ์ฒํ๋ค.
๋ฆฌํฉํ ๋ง ์ด์ ์ ์ฒด์ฝ๋:
import SwiftUI
struct customIndicatorSlides: View {
// ํ์ด๋จธ ์ค์
private let timer = Timer.publish(
every: 3,
on: .main,
in: .common
).autoconnect()
// ์ด๋ฏธ์ง ๋ฐฐ์ด
private let images: [String] = [
"image1",
"image2",
"image3"
]
@State var offset: CGFloat = 0 // ํ์ฌ ์ฌ๋ผ์ด๋ ์คํ์
์ ์ฅ
@State private var currentIndex = 0 //ํ์ฌ ํ์ด์ง ์ธ๋ฑ์ค
var body: some View {
ScrollView(.init()){
TabView(selection: $currentIndex){ // ํ์ฌ ์ ํ ํญ๋ชฉ ์ถ์ ์ฉ
ForEach(images.indices, id: \.self) { index in
if index == 0 {
// ์คํ์
๊ตฌํ๊ธฐ์ฉ
Image(images[index])
.resizable()
.scaledToFill()
.overlay(
// offset ๊ณ์ฐ
GeometryReader { proxy -> Color in
let minX = proxy.frame(in: .global).minX
DispatchQueue.main.async {
withAnimation(.default) {
self.offset = -minX
}
}
return Color.clear
}
.frame(width: 0, height: 0)
, alignment: .leading
)
.tag(index)
} else {
Image(images[index])
.resizable()
.scaledToFill()
.tag(index)
}
}
}
.tabViewStyle(
PageTabViewStyle(
indexDisplayMode: .never
)
)
// ํ์ด์ง ์ธ๋์ผ์ดํฐ
.overlay(
HStack(spacing: 4) {
ForEach(images.indices, id: \.self) { index in
Capsule()
// ์บก์ ๋์์ธ
// ์ ํ๋ ํ์ด์ง์ ์ผ์นํ๋ ๊ฒฝ์ฐ ์ฑ์ฐ๊ธฐ ์ ์ค์
.stroke(.uiColorGray350, lineWidth: 1)
// ์ ํ ์ธ๋ฑ์ค ๊ธฐ์ค ํฌ๊ธฐ ๋ณ๊ฒฝ
.frame(width: getIndex() == index ? 16 : 6, height: 6)
//์ ํ ์ธ๋ฑ์ค ๋ฐ๋ผ ํฌ๋ช
๋ ๋ณ๊ฒฝ
.opacity(getIndex() == index ? 1 : 0.5)
.background(getIndex() == index ? .uiColorGray350 : Color.clear)
.cornerRadius(3, corners: .allCorners)
}
}
.padding(.bottom, 24), alignment: .bottom
)
}
.ignoresSafeArea()
// ํ์ด์ง ๋ณ๊ฒฝ ๋ ๋ ์คํ์
์
๋ฐ์ดํธ
.onChange(of: currentIndex) { newIndex in
let newOffset = CGFloat(newIndex) * getWidth()
withAnimation {
offset = newOffset
}
}
// ํ์ด๋จธ ์ค์
.onReceive(timer) { _ in
let newIndex = currentIndex < images.count - 1 ? currentIndex + 1 : 0
currentIndex = newIndex
}
}
/// ํ์ฌ ํ์ด์ง์ ์ธ๋ฑ์ค๋ฅผ ๊ฐ์ ธ์ด
func getIndex() -> Int {
let index = Int(round(Double(offset / getWidth())))
return index
}
/// ์คํ์
๊ฐ ๊ฐ์ ธ์ด
func getOffset() -> CGFloat {
let progress = offset / getWidth()
return 22 * progress
}
}
extension View {
/// ํ๋ฉด ๋๋น ๊ฐ์ ธ์ด
func getWidth() -> CGFloat {
return UIScreen.main.bounds.width
}
}
2024.09.06 ์ฝ๋ ๋ฆฌํฉํ ๋ง
๋ฆฌํฉํ ๋งํ๊ฒ ๋ ์ด์ :
1. 4์์ ์์ฑํ ์ฝ๋์ ๊ฐ๋ ์ฑ์ด ๊ต์ฅํ ์ข์ง ์์๋ค
2. ์์์ ๋ณด๊ณ ๋น์ฅ์ "๊ธฐ๋ฅ๊ตฌํ"์ ์ด์ ์ ๋ง์ถฐ ์์ฑํ ์ฝ๋์๊ธฐ ๋๋ฌธ์, swiftUI๋ฅผ ์กฐ๊ธ ์ดํดํ๊ฒ ๋ ์ง๊ธ์ ๋ด๊ฐ ์ฝ๋๋ฅผ ๋ค์ ๋ดค์ ๋ UI๋ ๊ธฐ๋ฅ์ ์ผ๋ก ๋ถํ์ํ ๋ถ๋ถ์ด ๋๋ฌด ๋ง์๋ค.
3. ์์ ์ ์์ด ๋ ์์ ๊ณผ ํ์ฌ ์์ ์ฌ์ด์ SwiftUI ๊ธฐ๋ฅ์ ์ฐจ์ด๊ฐ ์๋ค. deprecated ๋ ๋ถ๋ถ์ ์์ ํ๋ค.
- CustomIndicator ์ฝ๋:
struct CustomIndicator: View {
let colors: [Color]
let currentIndex: Int
var body: some View {
ZStack{
HStack(spacing: 4) {
ForEach(colors.indices, id: \.self) { index in
Capsule()
.stroke(.white, lineWidth: 1)
.frame(width: currentIndex == index ? 16 : 6, height: 6)
.opacity(currentIndex == index ? 1 : 0.5)
.background(currentIndex == index ? .white : Color.clear)
.tag(index)
}
} // H
}
.padding(.bottom, 24)
}
}
์ธ๋ฑ์ค๊ฐ ํ์ฌ ์ธ๋ฑ์ค์ ๋์ผ ํ ๋๋ง ํด๋น ์์์ ์ธ๋์ผ์ดํฐ ํํ๋ฅผ ๋ฐ๊ฟ์ฃผ๋๋ก ํ๋ ์ฝ๋๋ค.
- ํ์ด๋จธ ์๋ ์๋ ์ฝ๋:
import SwiftUI
struct TestSlidesView: View {
@State private var currentIndex = 0
let colors: [Color] = [.red, .orange, .blue]
var body: some View {
VStack {
TabView(selection: $currentIndex) {
ForEach(colors.indices, id: \.self) { index in
colors[index]
.tag(index)
}
}
.overlay(alignment: .bottom, content: {
CustomIndicator(colors: colors, currentIndex: currentIndex)
})
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)
)
.frame(height: 200)
.cornerRadius(10)
.padding([.leading, .trailing], 10)
.onChange(of: currentIndex) { _, newValue in
currentIndex = newValue
}
} // V
}
}
- onChange์์ currentIndex์ ๋ณํ๋ฅผ ๊ฐ์งํด์, ๋ฐ๋ ์ธ๋ฑ์ค๋ก ํด๋น ์์๊ณผ ๊ทธ ์์์ ์ธ๋์ผ์ดํฐ ์์๋ฅผ ๋ณ๊ฒฝํด์ฃผ๊ณ ์๋ค.
- ๋ฆฌํฉํ ๋ง ์ด์ ์ฝ๋์ overlay์ onChange๊ฐ ์ผ๋ถ deprecated ๋ ๋ถ๋ถ์ด ์์ด์ ํด๋น ๋ถ๋ถ์ ์์ ํด์ฃผ์๋ค.
- ํ์ด๋จธ ์๋ ์๋ ์ฝ๋:
import SwiftUI
struct TestSlidesView: View {
@State private var currentIndex = 0
let colors: [Color] = [.red, .orange, .blue]
let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
var body: some View {
VStack {
TabView(selection: $currentIndex) {
ForEach(colors.indices, id: \.self) { index in
colors[index]
.tag(index)
}
}
.overlay(alignment: .bottom, content: {
CustomIndicator(colors: colors, currentIndex: currentIndex)
})
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)
)
.frame(height: 200)
.cornerRadius(10)
.padding([.leading, .trailing], 10)
.onReceive(timer) { _ in
withAnimation {
currentIndex = (currentIndex + 1) % colors.count
}
}
} // V
}
}
- onReceive์์ timer๋ฅผ trigger๋ก ์ธ๋ฑ์ค๋ฅผ ๋ฐ๊ฟ์ฃผ๊ณ , ๋ฐ๋ ์ธ๋ฑ์ค๋ฅผ ๋ฐ์์ ์ธ๋์ผ์ดํฐ ์์์ ๋ชจ์์ ๋ฐ๊ฟ์ฃผ๊ณ ์๋ค.
- ํ์ด๋จธ ์๋ ๋ฒ์ ๊ณผ ๋์ผํ๊ฒ deprecated๋ ๋ถ๋ถ์ ์ฝ๋๋ฅผ ์์ ํ๋ค.
๊ตฌํ ์์
๋ฌดํ ์คํฌ๋กค์ด ๋๋ ๋ฒ์ ์ ์๋ ํฌ์คํ ์ฐธ๊ณ ์ถ์ฒ
https://calliek.tistory.com/62
References
https://seons-dev.tistory.com/entry/SwiftUI-TabView
'๐ Dev > ๊ตฌํ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[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 |
[UIKit] Enum with Reusable VC (0) | 2023.10.10 |