[SwiftUI] TabView page indicator ์ปค์Šคํ…€ํ•˜๊ธฐ

2024. 4. 25. 15:27ยท๐ŸŽ Dev/๊ตฌํ˜„

 

 

์ด๋ฏธ์ง€ ์Šฌ๋ผ์ด๋“œ๋ทฐ๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ–ˆ๋Š”๋ฐ, ํŽ˜์ด์ง€ ์ธ๋””์ผ€์ดํ„ฐ๊ฐ€ ์• ํ”Œ์ด ์ œ๊ณตํ•ด์ฃผ๋Š” ๊ธฐ๋ณธ ์ธ๋””์ผ€์ดํ„ฐ๊ฐ€ ์•„๋‹ˆ๋ผ ์ปค์Šคํ…€์„ ํ•ด์•ผํ–ˆ๋‹ค.

 

์ฐธ๊ณ ๋กœ ์ธ๋””์ผ€์ดํ„ฐ๋Š”

 

์œ„์™€ ๊ฐ™์€ ๋ทฐ๊ฐ€ ์žˆ๋‹ค๋ฉด,

 

์ด๋ฏธ์ง€์˜ ์ˆœ์„œ๋ฅผ ๋‚˜ํƒ€๋‚ด์ฃผ๋Š” ๋ถ€๋ถ„์— ํ•ด๋‹นํ•œ๋‹ค.

 

ํ”ผ๊ทธ๋งˆ์— ์˜ฌ๋ผ์™€ ์žˆ๋Š” ์ธ๋””์ผ€์ดํŠธ ๋””์ž์ธ์ด ํ˜„์žฌ ์ด๋ฏธ์ง€์˜ ์ˆœ์„œ์ผ ๊ฒฝ์šฐ ์ธ๋””์ผ€์ดํ„ฐ ํ˜•ํƒœ๊ฐ€ ๋ฐ”๋€Œ๋Š” ๋””์ž์ธ์ด์—ˆ๋‹ค. ๋”ฐ๋ผ์„œ, ๋””์ž์ธ ๊ฐ€์ด๋“œ๋ฅผ ์ถฉ์กฑ์‹œํ‚ค๊ธฐ ์œ„ํ•ด์„œ 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

 

[SwiftUI] TabView + DragGesture๋กœ ๋ฌดํ•œ ์Šคํฌ๋กค ํƒ€์ด๋จธ ์กฐ์ ˆํ•˜๊ธฐ

ํ”„๋กœ๋ชจ์…˜ ํŒ์—… ํ™”๋ฉด์„ ๋‹ด๋‹นํ•ด์„œ ๋งŒ๋“ค์—ˆ๋Š”๋ฐ, ๋ฌดํ•œ ์Šคํฌ๋กค(์ด๋ฏธ์ง€ ๋ฐฐ์—ด ์ˆœํ™˜) + ํƒ€์ด๋จธ ์ž‘๋™์ด ๊ฐ€๋Šฅํ† ๋ก ๊ตฌํ˜„์„ ํ•ด์•ผํ–ˆ๋‹ค. - ๋‚ด๊ฐ€ ๊ตฌํ˜„ํ•˜๊ณ ์ž ํ•œ ๊ฒƒ: 1. ์ฒซ๋ฒˆ์งธ ์ธ๋ฑ์Šค ์ด๋ฏธ์ง€์—์„œ ์™ผ์ชฝ์œผ๋กœ ์Šค์™€

calliek.tistory.com

 

 

 

 

References

 

https://seons-dev.tistory.com/entry/SwiftUI-TabView

 

SwiftUI : TabView / TabViewStyle

๋ชฉ์ฐจ TabView ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๋„๋ก ํ•ฉ์‹œ๋‹ค. TabView SwiftUI๋Š” UIKit์˜ UITabBarController์™€ ๋™๋“ฑํ•œ ๊ธฐ๋Šฅ์„ ๊ฐ€์ง„ TabView๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ํ™”๋ฉด ํ•˜๋‹จ์˜ ๋ง‰๋Œ€๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—ฌ๋Ÿฌ View๋ฅผ ์ „ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

seons-dev.tistory.com

 

์ €์ž‘์žํ‘œ์‹œ (์ƒˆ์ฐฝ์—ด๋ฆผ)

'๐ŸŽ 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
'๐ŸŽ Dev/๊ตฌํ˜„' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • [SwiftUI] CustomPopUpView ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ ํ•ด๊ฒฐํ•˜๊ธฐ
  • [SwiftUI] ์Šค์œ ๋กœ ์„น์…˜ ์ ‘์—ˆ๋‹คํˆ๋‹ค ๊ตฌํ˜„ํ•˜๊ธฐ
  • [SwiftUI] pre/next buttons๊ฐ€ ์žˆ๋Š” ์ด๋ฏธ์ง€ ์Šฌ๋ผ์ด๋” ๊ตฌํ˜„ํ•˜๊ธฐ
  • [UIKit] Enum with Reusable VC
Callie_
Callie_
  • Callie_
    CalliOS
    Callie_
  • ์ „์ฒด
    ์˜ค๋Š˜
    ์–ด์ œ
    • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ
      • ๐ŸŽ APPLE
      • ๐ŸŽ Dev
        • Swift
        • UIKit
        • SwiftUI
        • Issue
        • ๊ตฌํ˜„
      • ๐ŸŽ Design
        • HIG
      • โš™๏ธ CS
      • ๐Ÿ’ก ์•Œ๊ณ ๋ฆฌ์ฆ˜
        • ํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค
        • ๋ฐฑ์ค€
      • ๐ŸŸ๏ธ ์ง๊ด€๋กœ๊ทธ (์ถœ์‹œ์•ฑ)
        • ์—…๋ฐ์ดํŠธ
      • ๐ŸŒฑ SeSAC iOS 3๊ธฐ
  • ๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

    • ํ™ˆ
    • ํƒœ๊ทธ
  • ๋งํฌ

  • ๊ณต์ง€์‚ฌํ•ญ

  • ์ธ๊ธฐ ๊ธ€

  • ํƒœ๊ทธ

    Snapshot
    IBOutlet
    stroyboard
    ios
    TapGestureRecognizer
    .fullScreen
    Swift
    IBAction
    TableViewCell
    SeSAC
    Infoํƒญ
    ํ™”๋ฉด์ „ํ™˜
    cornerradius
    Enum
    DidEndOnExit
    diffable
    ์ƒ๋ช…์ฃผ๊ธฐ
    Entry Point
    modalPresentStyle
    .OverFullScreen
    SwiftUI
    CustomView
    clipsToBound
    assets
    addTarget
    DiffableDataSource
    ๋„คํŠธ์›Œํฌํ†ต์‹ 
    CocoaTouchFramework
    layer.shadow
    apply
  • ์ตœ๊ทผ ๋Œ“๊ธ€

  • ์ตœ๊ทผ ๊ธ€

  • hELLOยท Designed By์ •์ƒ์šฐ.v4.10.0
Callie_
[SwiftUI] TabView page indicator ์ปค์Šคํ…€ํ•˜๊ธฐ
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”