[SwiftUI] Infinite Carousel ๊ตฌํ˜„ํ•˜๊ธฐ 1 (feat. Timer)

2024. 9. 5. 13:39ยท๐ŸŽ Dev/๊ตฌํ˜„

 

 

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

 

- ๋‚ด๊ฐ€ ๊ตฌํ˜„ํ•˜๊ณ ์ž ํ•œ ๊ฒƒ:

 

1. ์ฒซ๋ฒˆ์งธ ์ธ๋ฑ์Šค ์ด๋ฏธ์ง€์—์„œ ์™ผ์ชฝ์œผ๋กœ ์Šค์™€์ดํ”„ ํ•˜๋ฉด ๋งˆ์ง€๋ง‰ ์ธ๋ฑ์Šค ์ด๋ฏธ์ง€๊ฐ€ ๋‚˜์˜ค๊ณ , ๋งˆ์ง€๋ง‰ ์ธ๋ฑ์Šค์—์„œ ์˜ค๋ฅธ์ชฝ์œผ๋กœ ์Šค์™€์ดํ”„ํ•˜๋ฉด ์ฒซ๋ฒˆ์งธ ์ธ๋ฑ์Šค ์ด๋ฏธ์ง€๊ฐ€ ๋‚˜์˜ค๋Š” ๋ฌดํ•œ ์ˆœํ™˜ ๊ตฌ์กฐ์˜ ์Šคํฌ๋กค

2. ๋ณ„๋„์˜ ์ œ์Šค์ฒ˜๊ฐ€ ์—†์œผ๋ฉด 3์ดˆ๋งˆ๋‹ค ์ด๋ฏธ์ง€ ๋ณ€๊ฒฝ + ์ด๋ฏธ์ง€ ๋ฐฐ์—ด ์ˆœํ™˜

3. ์œ ์ €๊ฐ€ ์ˆ˜๋™์œผ๋กœ ์Šค์™€์ดํ”„ ์‹œ ๋ฐฐ์—ด ์ˆœ์„œ์— ๋งž๊ฒŒ ์ด๋ฏธ์ง€ ๋ณ€๊ฒฝ + ์ธ๋””์ผ€์ดํ„ฐ ๋ณ€๊ฒฝ

 

- ๋‚ด๊ฐ€ ๊ฒช์€ ๊ฒƒ:

 

1. ํƒ€์ด๋จธ ์กฐ์ ˆ์ด ๋˜์ง€ ์•Š์•„์„œ ์ด๋ฏธ์ง€๋ฅผ ๋ฐ˜๋งŒ ์Šค์™€์ดํ”„ ํ–ˆ์„ ๋•Œ ๋ฐ˜์”ฉ ๋‚˜์˜จ ์ด๋ฏธ์ง€๋“ค์ด ์ด์ „/๋‹ค์Œ ์ด๋ฏธ์ง€๋กœ ๋ณ€๊ฒฝ + ํƒ€์ด๋จธ ์ž‘๋™

2. ์ˆ˜๋™์œผ๋กœ ์Šค์™€์ดํ”„ ํ•ด์„œ ํƒ€์ด๋จธ ๋ฉˆ์ถ”๊ฒŒ ํ–ˆ์„ ์‹œ ๋‹ค์‹œ ์ž‘๋™์ด ๋˜์ง€ ์•Š๋Š” ์ด์Šˆ --> DragGesture์˜ onEnded๊ฐ€ ์ธ์‹ ๋˜์ง€ ์•Š์Œ

3. ์ด๋ฏธ์ง€์˜ ์ธ๋ฑ์Šค๊ฐ€ ๋ฐ”๋€Œ์ง€ ์•Š์œผ๋ฉด ํƒ€์ด๋จธ ๋ฏธ์ž‘๋™

 

 

2๋ฒˆ์˜ ๋ฌธ์ œ ๊ฐ™์€ ๊ฒฝ์šฐ ์• ํ”Œ์—์„œ ๊ตฌ์ฒด์ ์œผ๋กœ ์„ค๋ช…ํ•ด์ค€ ๊ฑธ ์ฐพ์„ ์ˆ˜ ์—†์—ˆ๋Š”๋ฐ, stack overflow์—์„œ ๋น„์Šท~ํ•œ ๋ฌธ์ œ๋ฅผ ๊ฒช๋Š” ๊ธ€ ์ค‘ ์œ„์™€ ๊ฐ™์€ ์ถ”์ธก ๋‹ต๋ณ€์„ ๋ฐœ๊ฒฌํ–ˆ๋‹ค. ๋‚˜๋Š” tabview๋กœ ๋ฌดํ•œ ์Šคํฌ๋กค์„ ๊ตฌํ˜„ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์–ด๋ผ? ์‹ถ์—ˆ์œผ๋‚˜, tabview๋„ ์Šคํฌ๋กค ๊ธฐ๋Šฅ์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋™์ผํ•œ ๋ฌธ์ œ์ธ ๊ฒƒ ๊ฐ™์•˜๋‹ค.

 

๋“œ๋ž˜๊ทธ ์•ก์…˜๊ณผ ์Šคํฌ๋กค ์•ก์…˜์ด ์ถฉ๋Œ ๋‚˜์„œ onEnded๋Š” ์ธ์‹์ด ๋ถˆ๊ฐ€ํ•œ๊ฒŒ ์•„๋‹๊นŒ ์•„๋ฌด๋ž˜๋„ SwiftUI๋กœ ๋ช‡ ๋ฒˆ ์•ก์…˜๊ณผ ๊ด€๋ จํ•œ ์ผ์„ ํ•ด๊ฒฐํ•˜๋‹ค๋ณด๋‹ˆ ์Šคํฌ๋กค ์•ก์…˜๊ณผ ๋“œ๋ž˜๊ทธ ์ œ์Šค์ฒ˜์˜ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๊ตณ์ด ๋”ฐ์ง€์ž๋ฉด ์Šคํฌ๋กค๋ทฐ/ํƒญ๋ทฐ์˜ ๋“œ๋ž˜๊ทธ ์ œ์Šค์ฒ˜ > ์ œ์Šค์ฒ˜์˜ ๋“œ๋ž˜๊ทธ ์ œ์Šค์ฒ˜์ธ ๋ชจ์–‘์ด์—ˆ๋‹ค.

 

 

- ํ•ด๊ฒฐ๋ฐฉ์•ˆ:

 

๋ฌดํ•œ ์Šคํฌ๋กค์„ scroll์ด ๊ฐ€๋Šฅํ•œ view๋ฅผ ์‚ฌ์šฉ ์•ˆ ํ•˜๋ฉด ๋˜๊ฒ ์ง€๋งŒ (์‹ค์ œ๋กœ ๋ฐ”๋กœ ์•„๋ž˜์— ์ถ”๊ฐ€ํ•œ ๋‹ค๋ฅธ ๋ถ„์˜ velog ๊ฒŒ์‹œ๊ธ€์„ ์ฐธ๊ณ ํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค) ์ด๋ฏธ tabview๋กœ ๊ตฌํ˜„ํ•˜๊ธฐ๋„ ํ–ˆ๊ณ , ๋‹ค์‹œ ์ฝ”๋“œ๋ฅผ ์งœ๊ธฐ์—” ์‹œ๊ฐ„์ด ๋ถ€์กฑํ•ด์„œ ์›๋ž˜ ๊ตฌํ˜„ํ•ด๋†“์€ view์—์„œ dragGesture์™€ ์ธ๋ฑ์Šค ์ถ”์ ์œผ๋กœ ํ•ด๊ฒฐํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

 

 

SwiftUI๋กœ ๋„ค์ด๋ฒ„์›นํˆฐ ์ƒ๋‹จ ๋ฐฐ๋„ˆ ๋งŒ๋“ค๊ธฐ

๋„ค์ด๋ฒ„ ์›นํˆฐ์˜ ์ƒ๋‹จ ๋ฐฐ๋„ˆ๋ฌดํ•œ ์Šคํฌ๋กค์ƒ๋‹จ ์ด๋ฏธ์ง€๋Š” ์Šคํฌ๋กค๋˜์ง€ ์•Š์œผ๋ฉด์„œ ํ•˜๋‹จ์˜ ํƒ€์ดํ‹€๋งŒ ์Šคํฌ๋กค๋จ์ž๋™ ์Šคํฌ๋กค๊ฒ‰๋ณด๊ธฐ์—๋Š” ์Šคํฌ๋กค์ด ๋˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ scroll์€ disable ์‹œํ‚ค๊ณ  drag gesture๋ฅผ ์ธ

velog.io

 

 

 

 

๊ตฌํ˜„

 

 

 

 

 

 

- ๋ฌดํ•œ ์Šคํฌ๋กค์„ ๋ณด์กฐํ•  ์ปค์Šคํ…€ ๋ทฐ:

 

import SwiftUI

struct InfinitePageView<C, T>: View where C: View, T: Hashable {
  @Binding var selection: T
  
  // ์ด์ „ ํ•ญ๋ชฉ์„ ๊ณ„์‚ฐํ•จ
  let before: (T) -> T
  // ๋‹ค์Œ ํ•ญ๋ชฉ์„ ๊ณ„์‚ฐํ•จ
  let after: (T) -> T
  
  // ๋ทฐ ์ƒ์„ฑ
  @ViewBuilder let view: (T) -> C
  
  // ํ˜„์žฌ ํƒญ ์ธ๋ฑ์Šค ์ €์žฅ
  @State private var currentTab: Int = 0
  
  var body: some View {
    
    // ์ด์ „ ๋ฐ ๋‹ค์Œ ์•„์ดํ…œ ๊ณ„์‚ฐ
    let previousIndex = before(selection)
    let nextIndex = after(selection)
    
    TabView(selection: $currentTab) {
      
      // ์ด์ „ ํ•ญ๋ชฉ ํ‘œ์‹œ ๋ทฐ
      view(previousIndex)
        .tag(-1)
      
      // ํ˜„์žฌ ๋ทฐ
      view(selection)
        .onDisappear() {
          if currentTab != 0 {
            selection = currentTab < 0 ? previousIndex : nextIndex
            currentTab = 0
          }
        }
        .tag(0)
      
      // ๋‹ค์Œ ํ•ญ๋ชฉ์„ ํ‘œ์‹œํ•˜๋Š” ๋ทฐ
      view(nextIndex)
        .tag(1)
    }
    .tabViewStyle(.page(indexDisplayMode: .never))
    
    .onChange(of: selection) { _, newValue in
      selection = newValue
    }
    
    // FIXME: workaround to avoid glitch when swiping twice very quickly
    // ํƒญ์ด 0์ด ์•„๋‹ ๋•Œ ์Šค์™€์ดํ”„๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜์—ฌ ๋น ๋ฅธ ์Šค์™€์ดํ”„ ์‹œ ๋ฐœ์ƒํ•˜๋Š” ๊ธ€๋ฆฌ์น˜ ๋ฐฉ์ง€
    .disabled(currentTab != 0)
    
  }
}

 

์œ„ ์ฝ”๋“œ๋Š” ์•„๋ž˜ references ํ•ญ๋ชฉ์„ ๋” ์ฐธ๊ณ ํ•˜๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.

๊ฑฐ๊ธฐ์„œ ์ฐพ์€ ์ฝ”๋“œ์˜ ์ž์„ธํ•œ ์„ค๋ช…์„ ๋ณด๊ณ  ๋‚ด ์ƒํ™ฉ์— ๋งž๊ฒŒ ์‚ด์ง ๋ณ€๊ฒฝํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ์ž ์ƒํ™ฉ์— ๋งž๊ฒŒ ๋ณ€๊ฒฝํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค.

 

 

 

 

- ํƒ€์ด๋จธ ๋ฐ ์•ก์…˜ ๊ตฌํ˜„:

 

extension TimerSlides {
  
  // MARK: - Slides
  /// ๋‹ค์Œ ์•„์ดํ…œ์œผ๋กœ ์ด๋™
  private func moveToNextIndex() {
    let nextIndex = (currentIndex + 1) % colors.count
    withAnimation() {
      currentIndex = nextIndex
    }
  }
  
  // MARK: - Timer
  /// ํƒ€์ด๋จธ ์ž‘๋™ (์‹œ์ž‘)
  private func startTimer() {
    /// ๊ธฐ์กด ํƒ€์ด๋จธ๊ฐ€ ์žˆ์œผ๋ฉด ์ค‘์ง€
    stopTimer()
    /// 3์ดˆ๋งˆ๋‹ค ๋ฐ˜๋ณต๋˜๋Š” ํƒ€์ด๋จธ ์„ค์ •
    timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { _ in
      moveToNextIndex()
    }
  }
  
  /// ํƒ€์ด๋จธ ์ž‘๋™ ๋ฉˆ์ถค
  private func stopTimer() {
    /// ํƒ€์ด๋จธ ๋ฌดํšจํ™” + nil๋กœ ์„ค์ •
    timer?.invalidate()
    timer = nil
  }
  
  /// ์‚ฌ์šฉ์ž๊ฐ€ ์ˆ˜๋™์œผ๋กœ ์Šฌ๋ผ์ด๋“œ ๋„˜๊ธฐ๋ ค๊ณ  ํ•  ๋•Œ DragGesture
  /// - ์ธ๋ฑ์Šค๊ฐ€ ๋ฐ”๋€Œ์ง€ ์•Š์œผ๋ฉด ํƒ€์ด๋จธ๊ฐ€ ๊ณ„์† ๊ฐ€๋™
  /// - ์ธ๋ฑ์Šค๊ฐ€ ๋ฐ”๋€Œ๋ฉด ํƒ€์ด๋จธ๊ฐ€ ๋ฉˆ์ถ”๊ณ  ๋‹ค์‹œ ๊ฐ€๋™
  private func manageTimerWithIndex() -> some Gesture {
    /// ์ปค์Šคํ…€ ์ œ์Šค์ฒ˜
    var userDragGesture: some Gesture {
      /// gaurd ์กฐ๊ฑด: ๋“œ๋ž˜๊ทธ ๋™์ž‘ ์ค‘ ์ธ๋ฑ์Šค๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ
      guard currentIndex == currentIndex else {
        /// ์ธ๋ฑ์Šค๊ฐ€ ๋ฐ”๋€Œ๋ฉด ํƒ€์ด๋จธ ๋ฉˆ์ถค O
        return DragGesture(coordinateSpace: .global)
          .onChanged { _ in
            stopTimer()
          }
      } // guard
      
      /// ์ธ๋ฑ์Šค๊ฐ€ ๋ฐ”๋€Œ์ง€ ์•Š์œผ๋ฉด ํƒ€์ด๋จธ ๋ฉˆ์ถค X
      return DragGesture(coordinateSpace: .global)
        .onChanged { _ in
          DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            startTimer()
          }
        }
    }
    /// ์ปค์Šคํ…€ํ•œ ์ œ์Šค์ฒ˜ ๋ฐ˜ํ™˜
    return userDragGesture
  }
  
   // MARK: - Indicator
  /// ์ด๋ฏธ์ง€ ์ปค์Šคํ…€ ์ธ๋””์ผ€์ดํ„ฐ
  private func imageCustomIndicator() -> some View {
    ZStack {
      if colors.count > 1 {
        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)
          }
        } // H
        .padding(.bottom, 24)
      } // if
    } // Z
  }

}

 

์ด ๊ฒŒ์‹œ๊ธ€์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๋ถ€๋ถ„์€ ํƒ€์ด๋จธ์™€ ๋“œ๋ž˜๊ทธ ์ œ์Šค์ฒ˜๋ฅผ ๊ตฌํ˜„ํ•œ ์œ„ ๋ถ€๋ถ„์ด๋‹ค.

 

- moveToNextIndex: ํ•จ์ˆ˜ ์ด๋ฆ„์—์„œ ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด ํ˜„์žฌ ์ธ๋ฑ์Šค์—์„œ +1 ํ•œ ์ธ๋ฑ์Šค, ์ฆ‰ ๋‹ค์Œ ์ธ๋ฑ์Šค ๊ฐ’์„ ๊ณ„์‚ฐํ•˜๋Š” ํ•จ์ˆ˜๋‹ค. ์ด ํ•จ์ˆ˜๋Š” ํƒ€์ด๋จธ๊ฐ€ ์ž‘๋™ํ•˜๋ฉด ์ž๋™์œผ๋กœ ๋‹ค์Œ ์ปฌ๋Ÿฌ๊ฐ€ ๋‚˜์˜ค๋„๋ก ํ•ด์ค„ ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค.

 

- startTimer: ํƒ€์ด๋จธ ์„ค์ •๊ณผ ๋”๋ถˆ์–ด ์„ค์ •ํ•œ ํƒ€์ž„์ด ์ง€๋‚˜๋ฉด ์ปฌ๋Ÿฌ๋ฅผ ์ž๋™์œผ๋กœ ๋„˜๊ฒจ์ฃผ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. ํƒ€์ด๋จธ๋ฅผ ๋ฆฌ์…‹ํ•˜๋Š” stopTimer()๋ฅผ ํฌํ•จํ•œ ๊ฑด, ๊ธฐ์กด ํƒ€์ด๋จธ๋ฅผ ๋ฆฌ์…‹ํ•ด์ฃผ์ง€ ์•Š์œผ๋ฉด ์•ก์…˜์ด๋‚˜ ์‹œ๊ฐ„์ด ์ถฉ๋Œ๋‚  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

- stopTimer: ํƒ€์ด๋จธ๋ฅผ ๋ฆฌ์…‹ ๋ฐ ์ •์ง€ ํ•ด์ฃผ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

 

- manageTimerWithIndex: ๋“œ๋ž˜๊ทธ ์ œ์Šค์ฒ˜์—์„œ onEnded๊ฐ€ ๋””๋ฒ„๊น…์„ ํƒ€์ง€ ์•Š์•„์„œ, ๋‚ด ์ƒํ™ฉ์—์„  ํ•ด๋‹น ๊ธฐ๋Šฅ์ด ์ ์šฉ์ด ๋˜์ง€ ์•Š๋Š” ๊ฑธ ์•Œ๊ฒŒ ๋˜์–ด์„œ ๊ตฌํ˜„ํ•˜์ง€ ์•Š์•˜๋‹ค.

 

๋Œ€์‹  ์šฐํšŒ์ ์ธ ๋ฐฉ๋ฒ•์œผ๋กœ ๋“œ๋ž˜๊ทธ๋ฅผ ์‹œ์ž‘ํ–ˆ์„ ๋•Œ,

 

1. dragGesture์˜ onChanged ๊ฐ์ง€

2. ๋“œ๋ž˜๊ทธ ์•ก์…˜์ด ๊ฐ์ง€ ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ถฉ๋Œ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด ํƒ€์ด๋จธ๋ฅผ ๋ฉˆ์ถ˜๋‹ค (stopTimer ์‹คํ–‰)

3. ์ธ๋ฑ์Šค๊ฐ€ ๋ฐ”๋€Œ์—ˆ๋‹ค -> ์‚ฌ์šฉ์ž์˜ ๋“œ๋ž˜๊ทธ ์•ก์…˜์ด ๋ฉˆ์ท„๋‹ค -> ํƒ€์ด๋จธ๋ฅผ ์‹คํ–‰ํ•ด์„œ ๋‹ค์‹œ ์ปฌ๋Ÿฌ๋ฅผ ์ž๋™์œผ๋กœ ์ˆœํ™˜ํ•œ๋‹ค (startTimer ์‹คํ–‰)

4. ๋“œ๋ž˜๊ทธ ์•ก์…˜์ด ๊ฐ์ง€ (stopTimer ์‹คํ–‰) ๋˜์—ˆ๋Š”๋ฐ ์ธ๋ฑ์Šค๊ฐ€ ์•ˆ ๋ฐ”๋€Œ์—ˆ๋‹ค ->  ๋ฉˆ์ถ˜ ํƒ€์ด๋จธ๋ฅผ ์‹คํ–‰์‹œ์ผœ ๋‹ค์‹œ ์ž๋™์œผ๋กœ ์ˆœํ™˜์‹œํ‚จ๋‹ค (startTimer ์‹คํ–‰)

5. ์ธ๋ฑ์Šค๊ฐ€ ๋ฐ”๋€Œ์ง€ ์•Š์•˜์„ ๋•Œ, ํƒ€์ด๋จธ๊ฐ€ ๊ทธ๋Œ€๋กœ ๋Œ์•„๊ฐ€๋ฉด ์ปฌ๋Ÿฌ ์ „ํ™˜ ํƒ€์ž„์ด ๋ถ€์ž์—ฐ์Šค๋Ÿฌ์›Œ์„œ dispatchQueue.main.asyncAfter๋กœ 0.5์ดˆ ์ •๋„ ๋Šฆ์ถฐ์„œ ์ž‘๋™ํ•˜๋„๋ก ํ–ˆ๋‹ค.

 

..ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ˆœํ™˜๋˜๋„๋ก ํ•ด์ฃผ๋Š” ํ•จ์ˆ˜๋‹ค. ์ฆ‰, ๋‚˜๋Š” dragGesture์˜ trigger๋ฅผ ๋“œ๋ž˜๊ทธ ๊ทธ ์ž์ฒด๋กœ ํ•˜๋ฉด ํƒญ๋ฐ” ์•ก์…˜๊ณผ ํ˜ผ๋ˆ๋˜๊ธฐ ๋•Œ๋ฌธ์— currentIndex๋กœ ์ง€์ •ํ•˜์—ฌ ๋‚ด๊ฐ€ ๊ตฌํ˜„ํ•˜๊ณ ์ž ํ•œ ๋ถ€๋ถ„๋“ค์„ ๊ตฌํ˜„ํ–ˆ๋‹ค.

 

์‚ฌ์‹ค ๋ฒ ์ŠคํŠธ์ธ ๋ฐฉ๋ฒ•์€ ์•„๋‹Œ ๊ฒƒ ๊ฐ™์€๋ฐ(startTimer์™€ stopTimer๊ฐ€ ๋”ฑ ๊น”๋”ํžˆ ์„ ์–ธ๋œ ๊ฒŒ ์•„๋‹ˆ๋ผ์„œ), ์ผ๋‹จ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜๊ฑฐ๋‚˜ ๊ตฌํ˜„ ์‹คํŒจํ•œ ๋ถ€๋ถ„์€ ํ™•์ธ ๋˜์ง€ ์•Š์•„์„œ ์œ„์™€ ๊ฐ™์€ ๋ฐฉ๋ฒ•์„ ์œ ์ง€ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค. ๊ณ„์† ๋ฆฌํŒฉํ† ๋ง ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋” ๋‚˜์€ ๋ฐฉ๋ฒ•์ด ์žˆ๊ฑฐ๋‚˜ ์ฐพ์œผ๋ฉด ๊ทธ๋•Œ ๋‹ค์‹œ ์ˆ˜์ •ํ•˜๊ธฐ๋กœ.

 

 

- ๋ฉ”์ธ ์Šฌ๋ผ์ด๋“œ ๋ทฐ ๊ตฌํ˜„:

 

import SwiftUI
import Combine

struct TimerSlides: View {
  
  // MARK: - properties
  /// ๋ฌดํ•œ์œผ๋กœ ์ˆœํ™˜ํ•  ๋ฐฐ์—ด
  var colors: [Color] = [.red, .orange, .yellow, .blue, .green]
  /// ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ด๋จธ
  @State private var timer: Timer?
  /// ํ˜„์žฌ ์ธ๋ฑ์Šค ์ €์žฅ
  @State private var currentIndex = 0
  
  // MARK: - body
  var body: some View {
    
    // MARK: - Image Slide
    InfinitePageView(
      selection: $currentIndex,
      before: { $0 == 0 ? colors.count - 1 : $0 - 1 },
      after: { $0 == colors.count - 1 ? 0 : $0 + 1 },
      view: { index in
        
        ZStack {
          Rectangle()
            .fill(colors[index])
            .tag(index)
        } // Z
        .ignoresSafeArea()
        
      }) // InfinitePageView
    .ignoresSafeArea()
    
    // ์ธ๋ฑ์Šค ๋ณ€ํ™”
    .onChange(of: currentIndex, { _, newIndex in
      currentIndex = newIndex
      startTimer()
    })
    
    // MARK: - ์•ก์…˜
    // ์ˆ˜๋™ ๋“œ๋ž˜๊ทธ ์‹œ ํƒ€์ด๋จธ ์กฐ์ • ์•ก์…˜
    .gesture(manageTimerWithIndex())
    
    // MARK: - ์ธ๋””์ผ€์ดํ„ฐ
    .overlay(
      alignment: .bottom,
      content: { imageCustomIndicator() }
    ) // Overlay
    
    // MARK: - LifeCycle (ํƒ€์ด๋จธ ์ž‘๋™ ๊ด€๋ฆฌ)
    .onAppear {
      startTimer()
    }
    .onDisappear {
      stopTimer()
    }
    
  } // body
} // view

 

 

- onAppear์—์„œ ํ•ด๋‹น ๋ทฐ๊ฐ€ ๊ทธ๋ ค์ง€์ž๋งˆ์ž startTimer๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ปฌ๋Ÿฌ ๋ฐฐ์—ด์ด 3์ดˆ๋งˆ๋‹ค ์ž๋™์œผ๋กœ ์ˆœํ™˜ํ•˜๊ฒŒ ํ•œ๋‹ค.

- onChange์™€ gesture๋ฅผ ํ†ตํ•ด ์ƒํ™ฉ์— ๋งž๊ฒŒ ํƒ€์ด๋จธ๊ฐ€ ๋ฉˆ์ท„๋‹ค๊ฐ€ ๋‹ค์‹œ ์ž‘๋™ ๋˜๋„๋ก ํ•œ๋‹ค.

- onDisappear์—์„œ ํ•ด๋‹น ๋ทฐ๊ฐ€ ์‚ฌ๋ผ์งˆ ๋•Œ stopTimer๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํƒ€์ด๋จธ๋ฅผ ํ•ด์ œํ•œ๋‹ค.

 

Indicator ๋ถ€๋ถ„์€ ์ปค์Šคํ…€ํ•ด์„œ ์ ์šฉํ•œ ๊ฑฐ๊ธฐ๋„ ํ•˜๊ณ , ์ด์ „์— ์“ด ๊ธ€๋„ ์žˆ์–ด์„œ ์ธ๋””์ผ€์ดํ„ฐ ์ปค์Šคํ…€์€ ํ•ด๋‹น ๊ธ€์„ ์ฐธ๊ณ ํ•˜๋Š” ๊ฑธ ์ถ”์ฒœํ•œ๋‹ค.

 

https://calliek.tistory.com/45

 

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

ํ”„๋กœ์ ํŠธ ์ง„ํ–‰ ์ค‘ ๋‚ด๊ฐ€ ๋งก์€ ui์—์„œ ์Šฌ๋ผ์ด๋“œ๋ทฐ๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ–ˆ๋Š”๋ฐ,    ํ”ผ๊ทธ๋งˆ์— ์˜ฌ๋ผ์™€ ์žˆ๋Š” ๋””์ž์ธ ๊ฐ€์ด๋“œ๊ฐ€ ์œ„ ์ด๋ฏธ์ง€์™€ ๊ฐ™์•˜๋‹ค. ๋”ฐ๋ผ์„œ SwiftUI์—์„œ Tabview๊ฐ€ ์ œ๊ณตํ•ด์ฃผ๋Š” ์›ํ˜• ์ธ๋””์ผ€์ดํ„ฐ๋ฅผ

calliek.tistory.com

 

์Šค์œ ๋กœ ์ž‘์—…์„ ํ•˜๊ฒŒ ๋œ ์ดˆ๊ธฐ์— ์ง  ์ฝ”๋“œ๋ผ ์ˆ˜์ •ํ•ด์•ผํ•  ๊ฒŒ ์กฐ๊ธˆ ๋ณด์—ฌ์„œ ์กฐ๋งŒ๊ฐ„ ๊ธ€ ์ˆ˜์ •์„ ํ•ด์•ผํ•  ๊ฒƒ ๊ฐ™์ง€๋งŒ,,,^^,,,,

 

 

 

 

 

 

 

 

References

 

https://sheep1sik.tistory.com/144

 

[ SwiftUI ] ๊ด‘๊ณ ๋ฐฐ๋„ˆ ๋งŒ๋“ค๊ธฐ

SwiftUI๋ฅผ ํ†ตํ•ด ๊ด‘๊ณ ๋ฐฐ๋„ˆ๋ฅผ ๋งŒ๋“ค์–ด๋ดค์Šต๋‹ˆ๋‹ค.๋จผ์ € ๊ด‘๊ณ ๋ฐฐ๋„ˆ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ TabView๋ฅผ ์ฑ„ํƒํ•˜์—ฌ ๊ตฌํ˜„์„ ํ–ˆ๊ณ  ๊ตฌํ˜„ ๋ชฉํ‘œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์•˜์Šต๋‹ˆ๋‹ค. 3์ดˆ๊ฐ„๊ฒฉ์œผ๋กœ ํ™”๋ฉด ์ด๋™๋ฌดํ•œ์ ์ธ ๊ด‘๊ณ ๋ฐฐ๋„ˆ ( 1 -> 2 -> 3 -

sheep1sik.tistory.com

 

 

https://stackoverflow.com/questions/65457609/bidirectional-infinite-pageview-in-swiftui

 

Bidirectional infinite PageView in SwiftUI

I'm trying to make a bidirectional TabView (with .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))) whose datasource will change over time. Below is the code that describes what is expected...

stackoverflow.com

 

 

https://stackoverflow.com/questions/64424719/draggestures-onended-never-called-if-canceled-by-scrollview

 

DragGestures onEnded never called if canceled by ScrollView

I have the following problem. I have a View inside a ScrollView that I want to drag. Now, everything works fine, the dragging occurs and the scrolling as well. But once I try to do both, the dragOf...

stackoverflow.com

 

 

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

'๐ŸŽ Dev > ๊ตฌํ˜„' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

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

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

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

  • ์ธ๊ธฐ ๊ธ€

  • ํƒœ๊ทธ

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

  • ์ตœ๊ทผ ๊ธ€

  • hELLOยท Designed By์ •์ƒ์šฐ.v4.10.0
Callie_
[SwiftUI] Infinite Carousel ๊ตฌํ˜„ํ•˜๊ธฐ 1 (feat. Timer)
์ƒ๋‹จ์œผ๋กœ

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