커스텀 SwipePopNavigationStack 구현하기

2025. 7. 13. 17:15·iOS/SwiftUI
반응형

👋 들어가기 전

swiftUI를 하면서, 당황스러운 경험을 겪어서 한번 적어보려한다.

 

상황은 다음과 같다.

 

🧑‍💻: 나 너네 네비바 말고 내가 만들어 쓸래 가능?

 

🍎: ㅇㅇ 그럼 우리꺼 숨겨

 

🧑‍💻: ㅇㅋ!

 

🧑‍💻: 어 잠만, 숨기니깐 왜 swipe pop 기능이 안돼?

 

🍎: 우리꺼 안쓴다며

 

🧑‍💻: 아니 네비바만 안쓴다고, swipe pop 기능은 쓸꺼야

 

🍎: 안돼, 그것도 니가 만들어 쓰셈

 

🧑‍💻: ... 😭


🏁 학습할 내용

  • 상황 설명
  • 해결 과정
    • UIKit 힘 빌리기
    • 커스텀 뷰 만들기

기본 형태

toolbar를 숨기지 않을 때, swipe pop이 너무 잘 동작한다.

toolbar(.hidden)

 

다음 함수를 써서, Visibility를 hidden 처리하면, 아무리 swipe을 해도 동작하지 않는다.


🌊 해결과정

SwiftUI는 SwiftUI 자체로 해결을 못하면, 자연럽게 UIKit쪽까지 고려를 해봐야한다.

 

이 문제 역시 마찬가지다.

🧰 UIKit 힘 빌리기

준비물은 다음과 같다.

  • UIViewRepresentable: UIView를 사용하기위해
  • UIPanGestureRecognizer: interactivePopGestureRecognizer에 동작을 가로챌 gesture

BackgroundSwipePopView.swift

PanGesture를 인식하기위해 뒤에 몰래 숨겨 놓는 뷰다.

이게 바로 킥이다.

import SwiftUI
import UIKit

struct BackgroundSwipePopView: UIViewRepresentable {
  @Binding var gesture: UIPanGestureRecognizer
  func makeUIView(context: Context) -> UIView {
    return UIView()
  }

  func updateUIView(_ uiView: UIView, context: Context) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
      if let parentVC = uiView.parentViewController {
        if let navigationController = parentVC.navigationController {
          if let _ = navigationController.view.gestureRecognizers?.first(where: { $0.name == gesture.name }) {
            // Already added
          } else {
            navigationController.addFullSwipeGesture(gesture)
          }
        }
      }
    }
  }
}

fileprivate extension UINavigationController {
  func addFullSwipeGesture(_ gesture: UIPanGestureRecognizer) {
    let key = "targets"
    guard let gestureSelector = interactivePopGestureRecognizer?.value(forKey: key) else { return }

    gesture.setValue(gestureSelector, forKey: key)
    view.addGestureRecognizer(gesture)
  }
}

fileprivate extension UIView {
  //responder chain을 따라 next를 순차적으로 타고 올라가면서 UIViewController를 탐색
  var parentViewController: UIViewController? {
    sequence(first: self) {
      $0.next
    }.first { $0 is UIViewController } as? UIViewController
  }
}

 

func UpdateUIView 

  • SwiftUI에서 이 뷰가 업데이트될 때마다 호출
  • DispatchQueue.main.asyncAfter(deadline: .now() + 1):
    • 살짝 지연시켜서 NavigationController가 완전히 생성된 이후에 실행
  • UIView의 parentViewController를 찾고 , parentViewController의 navigationController를 찾는다.
  • 이후 navigationController에 외부에서 받은 gesture를 등록한다,

func addFullswipeGesture

  • interactivePopGestureRecognizer의 내부 target-action("targets" key)을 가져와서 새로운 제스처에 그대로 복사
  • 이로써 gesture도 같은 방식으로 작동하도록 만듬

📚 커스텀 네비게이션 스택 만들기

위에서 만든 뷰를 뷔에 숨기는 컨테이너 뷰가 필요하다.

import SwiftUI
import UIKit

public struct SwipePopNavigationStack<Content: View>: View {
  private let content: () -> Content

  @State private var gesture: UIPanGestureRecognizer = {
    let gesture = UIPanGestureRecognizer()
    gesture.name = UUID().uuidString
    gesture.isEnabled = true
    return gesture
  }()

  public init(@ViewBuilder content: @escaping () -> Content) {
    self.content = content
  }

  public var body: some View {
    NavigationStack {
      content()
        .background {
          BackgroundSwipePopView(gesture: $gesture)
        }
    }
  }
}

 


✅ 결과


😂 쉬운 방법을 나중에 찾음

다음 코드를 사용하면, back버튼을 지워도 시스템에서 지원하는 swipe pop을 사용할 수 있다.

extension UINavigationController: @retroactive UIGestureRecognizerDelegate {
    override open func viewDidLoad() {
        super.viewDidLoad()
        interactivePopGestureRecognizer?.delegate = self
    }

    public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        return viewControllers.count > 1
    }
}

출처

https://youtu.be/4ceKPSTlL4I?si=iLTvC8WQrHUD32xC

반응형

'iOS > SwiftUI' 카테고리의 다른 글

스유에서 lineHeight과 letterSpacing 적용하기  (2) 2025.07.22
.contentShape  (0) 2025.07.19
커스텀 뷰를 만들 때 고민점  (0) 2025.07.05
@Observable 매크로  (0) 2025.07.03
Custom Carousel 만들기  (0) 2025.03.16
'iOS/SwiftUI' 카테고리의 다른 글
  • 스유에서 lineHeight과 letterSpacing 적용하기
  • .contentShape
  • 커스텀 뷰를 만들 때 고민점
  • @Observable 매크로
Hamp
Hamp
남들에게 보여주기 부끄러운 잡다한 글을 적어 나가는 자칭 기술 블로그입니다.
  • Hamp
    Hamp의 분리수거함
    Hamp
  • 전체
    오늘
    어제
    • 분류 전체보기 (304)
      • CS (30)
        • 객체지향 (2)
        • Network (7)
        • OS (6)
        • 자료구조 (1)
        • LiveStreaming (3)
        • 이미지 (1)
        • 잡다한 질문 정리 (0)
        • Hardware (2)
        • 이론 (6)
        • 컴퓨터 그래픽스 (0)
      • Firebase (3)
      • Programing Langauge (37)
        • swift (32)
        • python (4)
        • Kotlin (1)
      • iOS (132)
        • UIKit (37)
        • Combine (1)
        • SwiftUI (32)
        • Framework (7)
        • Swift Concurrency (22)
        • Tuist (6)
        • Setting (11)
        • Modularization (1)
        • Instruments (6)
      • PS (59)
        • 프로그래머스 (24)
        • 백준 (13)
        • LeetCode (19)
        • 알고리즘 (3)
      • Git (18)
        • 명령어 (4)
        • 이론 (2)
        • hooks (1)
        • config (2)
        • action (7)
      • Shell Script (2)
      • Linux (6)
        • 명령어 (5)
      • Spring (13)
        • 어노테이션 (1)
        • 튜토리얼 (11)
      • CI-CD (4)
      • Android (0)
        • Jetpack Compose (0)
  • 블로그 메뉴

    • 홈
    • 태그
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    투포인터
    IOS
    property
    UIKit
    Swift
    SwiftUI
    CS
    protocol
    dispatch
    백준
    Spring
    dfs
    boostcamp
    lifecycle
    dp
    프로그래머스
    Tuist
    AVFoundation
    GIT
    concurrency
  • 최근 댓글

  • 최근 글

  • 반응형
  • hELLO· Designed By정상우.v4.10.0
Hamp
커스텀 SwipePopNavigationStack 구현하기
상단으로

티스토리툴바