김경록의 앱 개발 여정

[Swift UIKit] Popover 드롭다운 버튼 화면 전환 버그 수정 과정 본문

Trouble Shooting

[Swift UIKit] Popover 드롭다운 버튼 화면 전환 버그 수정 과정

Kim Roks 2025. 9. 26. 10:25

개요

https://roks-apps.tistory.com/88

이전 포스팅에서 UIPopover + UITableView로 드롭다운 버튼을 만들었다가, 드롭다운이 펼쳐진 상태에서 좌측 엣지 스와이프(인터랙티브 뒤로가기) 를 하면 화면이 얼어붙는 이슈가 생겼다 

- 드롭다운을 연 상태에서 뒤로가기 제스쳐를 할 경우 네비게이션 타이틀만 바뀌고 화면이 멈추는 버그


증상

  • 드롭다운 버튼 탭 → 팝오버로 드롭다운 표시
  • 드롭다운이 열린 상태에서 좌측 엣지 스와이프로 뒤로가기 제스처 시작
  • 네비게이션 바 타이틀은 바뀌지만 뷰는 그대로, 터치도 먹지 않는 상태 발생

원인

원인 추적은 금방 가능했다 아마 화면 전환에서 문제가 생긴듯했다.

presentedViewController(팝오버)가 떠 있는 동안 UINavigationController의 인터랙티브 pop 전환이 시작되면, 두 전환이 동시에 유지되면서 내부 전환 코디네이터가 꼬인다. 결과적으로 화면이 멈춘 것처럼 보이는 상태가 된다는 결론에 도달했다.

 

결국

뒤로가기 제스처가 시작되기 전에 떠 있는 팝오버를 먼저 닫아야 한다.


해결

1) 제스처 시작 직전에 팝오버 닫고, 이번 제스처는 취소

interactivePopGestureRecognizer의 delegate에서 팝오버가 떠 있으면 즉시 dismiss 하고, 이번 제스처는 false로 취소한다. 사용자는 자연스럽게 한 번 더 스와이프하면 정상적으로 뒤로가기 된다.

 
class BaseViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        hookInteractivePopGesture()
    }

    // 일반 전환(pop/push) 중에도 안전하게 정리
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        if isMovingFromParent || navigationController?.transitionCoordinator != nil {
            presentedViewController?.dismiss(animated: false)
        }
    }

    private func hookInteractivePopGesture() {
        guard let nc = navigationController,
              let popGR = nc.interactivePopGestureRecognizer else { return }
        popGR.delegate = self
        popGR.isEnabled = (nc.viewControllers.count > 1)
    }
}

extension BaseViewController: UIGestureRecognizerDelegate {
    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        if gestureRecognizer === navigationController?.interactivePopGestureRecognizer,
           presentedViewController != nil {
            // 팝오버 먼저 닫고, 이번 제스처는 취소
            presentedViewController?.dismiss(animated: false)
            return false
        }
        return true
    }
}

2) 전환 직전 추가 방어

viewWillDisappear(_:)에서 presentedViewController?.dismiss(animated: false)를 한 번 더 호출해 두면, 일반적인 pop/push에서도 잔여 프레젠테이션이 남지 않아 안전하다. (위 코드 포함)


체크리스트

  • 제스처 시작 전에 presentedViewController 있으면 dismiss 후 제스처 취소
  • viewWillDisappear에서도 한 번 더 dismiss로 잔여 프레젠테이션 제거
  • 팝오버의 passthroughViews = []로 바깥 터치 전달 최소화
  • 가능하면 UIButton.menu 등 시스템 풀다운 고려

마무리

 

드롭다운이 먼저 dismiss된 후 화면 전환이 잘 작동

네비게이션 타이틀이 변화하는걸 확인해서 다행히 원인을 빨리 찾고 해결할 수 있었다.
뒤로가기 제스처가 시작되기 전에 항상 떠 있는 팝오버를 닫는 흐름으로 바꾸니, 화면 멈춤 증상은 재현되지 않았다.

드롭다운을 팝오버로 구현해야 하는 상황이라면 위 가드 코드들을 반드시 넣어두는 걸 추천한다.