SwiftUI NavigationStack의 navigationDestination가 예상보다 자주 호출됨
몇 달전, 개인 앱의 NavigationView를 NavigationStack으로 전환하였습니다. 부끄러운 이야기이지만, 한참이 지나서, 네비게이션의 깊이가 2 이상이 되면, 해당 페이지에 버그가 있는 것을 인지하였습니다. 주로 저는 제 앱의 기능을 깊이 1에서 이용하고 있었기 때문에 해당 버그를 잘 모르고 있었던 것입니다.
버그는 2가지가 있었는데, 첫번째는 항목을 길게 탭하여 나오는 컨텍스트 메뉴가 표시되지 않고, 새로 고침되는 현상이었고, 두번째는 당장 사용에는 크게 지장이 없지만, 전체 화면으로 다른 뷰 컨트롤러를 푸시하게 되는 경우, 그 뷰 컨트롤러를 닫을 때, 화면이 갱신되는 현상이었습니다.
두가지 모두 View의 값 변화를 유도하는 행동이 아니었습니다. 따라서 뷰는 갱신되지 않아야 할 것이라고 생각했습니다. 그렇기 때문에 의도치 않게 내가 값을 변경하는 부분이 있나 찾아보았지만 아니었습니다. 해당 동작을 할 때, 콜 스택을 확인해본 결과, 해당 동작에서 뷰를 새로 만드는 곳의 최초 위치가 navigationDestination 모디파이어의 클로저 안임을 확인하였습니다.
테스트 프로젝트를 하나 생성한 후, 앱과 동일하게 내비게이션 스택을 구성한 후 똑같은 동작을 해보니, 테스트 앱도 동일한 증상을 가진 것을 확인하였습니다. 이 경우, 페이지가 리프레시 되는 것 같은 현상은 없었습니다. 페이지가 리프레시되는 현상의 원인은 뷰를 만들 때, 뷰 모델을 새로 만들고, 제 앱에서 파일 목록을 받을 때 이 값이 비동기적으로 업데이트되기 때문이었습니다. 부모 뷰에서 가지고 있는 내용을 공유하기 위해서 environmentObject로 모델을 주입받은 것이 원인입니다.
해당 문제를 해결하기 위해서는 아래와 같은 방법을 생각해볼 수 있을 것입니다.
- 가장 이상적인 방법은 뷰 모델을 environmentObject로 공유하지 않습니다. 공유해야 하는 데이터는 model에만 위치해야하고, 뷰 모델은 stateObject로 만든다음 업데이트합니다.
- 여의치 않은 경우 뷰 모델을 navigationDestination이 만드는 뷰보다 더 높은 곳에서 생성하여 캐싱된 상태로 이용합니다. 뷰 모델의 내용은 일치하므로, 이상동작이 발생하지 않습니다.
새롭게 도입된 NavigationStack은 좀 더 좋은 인터페이스와 일관성을 제공해주지만, NavigationLink로 전달하는 value와 실제 만들게 되는 뷰가 변동 없이 일치하는 것이 전제되어 있지 때문에, 이러한 예상치 못한 동작에도 정상작동하도록 고려하고 만들어합니다.
덧: 해당 문제점은 iPad 환경에만 발생하고, iPhone에서는 정상적이었기에 판단에 장애가 있었던 것 같습니다.