Issue

[Issue] SwiftUI NavigationLink의 상태 관리

Hatchling.dev 2024. 2. 19. 23:20

(편의상 편한 말투로 작성하는 점 이해 부탁드립니다.😅)

(부정확한 정보가 있을 수 있습니다. 지적 환영🤗)

 

안녕하세요! Hatchling입니다.

 

오늘은 개발을 하던 중 만난(원하지 않은..) Navigationlink와 관련된 issue를 정리하면서 NavigationView도 같이 정리해보도록 하겠습니다! (참고로 저는 iOS 16+로 개발 중입니다.)

일단 제 상황은 NavigationStack에서 NavigationLink를 사용해 View를 관리하고 있었고 데이터를 배열로 관리해 각 요소를 SecondView로 보여주는 아주 일반적인 구조를 가지고 있습니다만.. 저는 SecondView안에서 앞 뒤 다른 데이터를 가져와 새로운 View처럼 사용하고 싶었단 말이죠???? 여기서 문제가 생겼습니다. SecondView를 배열의 인덱스로 불러왔고 SecondView 안에서 index를 변경해 다른 데이터를 가져온 뒤에 FirstView에서 다시 NavigationLink로 SecondView를 호출하면 이상한 데이터가 불려오더라구요..?

자세한 설명은 아래에서 자세히 해보겠습니다😢

NavigationView? NavigationStack?

SiwftUI로 개발을 해보신 분이라면 누구나 알만한 NavigationView입니다. SwiftUI에서 화면을 쉽게 push하고 pop할 수 있도록 도와 View의 이동을 위해 사용됩니다.

하지만 WWDC22에서 NavigationView가 deprecated 되고 NavigationStack/NavigationSplitView라는 것들이 나왔지만 iOS 16+ 부터 사용할 수 있습니다. (최소 타겟이 그 아래라면 NavigationView에 더 익숙하실수도 있겠네요.

 

둘의 차이부터 알아볼까요?

NavigationView { 
    // Code
}
.navigationViewStyle(.stack)
NavigationStack { 
    // Code
}

위의 두 코드는 같은 동작을 하는 코드입니다. NavigationView에서는 Style을 지정해줘야 했지만 NavigationStack은 이름 값을 하는 아이입니다. ㅎ Stack이라는 이름을 붙은 만큼 View를 Stack 구조로 쌓아 LIFO(Last In First Out) 방식으로 View를 관리합니다. (root view는 삭제할 수 없습니다!)

그리고 NavigationView에서는 보통 NavigationLink와 세트로 사용했지만 NavigationStack에서는 NavigationLink와 .navigationDestination을 함께 사용하게 됩니다. (무조건은 아닙니다..)

그럼 NavigationStack에서 NavigationLink를 사용하는 방법을 알아보도록 하겠습니다!

 

NavigationLink? .navigationDestination?

NavigationView에서는 NavigationLink만을 사용해 여러 방식으로 개발할 수 있었지만 iOS 16+부터는 NavigationStack이 나오면서 기존에 사용하던 NavigationLink들이 deprecated 되어버렸습니다. 물론 대책없이 없애버린 건 아니고 NavigationLink와 .navigationDestination를 같이 사용하라는 Apple의 뜻이죠..

모두 아시겠지만 혹시 모르는 분들을 위해 설명하면 NavigationLink는 실제로 View의 이동을 도와주는 아이입니다. NavigationLink에 이동할 View를 넣고 해당 NavigationLink를 누르면 해당 View로 이동하는 방식이죠!!

더보기

NavigationView + NavigationLink

NavigationView {
    List(numbers.indices) { index in
        NavigationLink(destination: {
            // 이동할 View
        }, label: {
            Text("\(numbers[index])")
        })
    }
}

그럼 .navigationDestination은 뭐하는 아이냐! DistinationView를 NavigationLink에서 제시된 data와 연결시켜주는 역할을 담당합니다.

더보기

NavigationStack + NavigationLink + .navigationDestination

NavigationStack {
    List(numbers.indices) { index in
        NavigationLink("\(numbers[index])", value: numbers[index])
    }
    .navigationDestination(for: Int.self, destination: { number in
        // 이동할 View
    })
}

주의 : NavigationLink의 value에 해당하는 값과 navigationDestination의 for에 해당하는 타입과 다르면 NavigationLink는 동작하지 않음

 

자!! 기본적인 SwiftUI의 Navigation에 대해 알아보았으니 제게 생긴 문제를 자세히 볼까요?

위에서 설명한 것처럼 NavigationLink로 이동한 View 안에서 index를 변경시키면 NavigationLink에서 이상한 index를 뱉어냅니다..

여러분들은 SwiftUI에서 중요한 포인트가 어떤 점이라고 생각하시나요? 선언형? 상태관리? 여러가지가 떠오르죠. 제게 생긴 문제는 바로 상태 관리와 관련된 문제였습니다..

SwiftUI에서 NavigationLink의 대상이 되는 상태를 정확하게 매핑하는 것은 매우 중요하죠. 이걸 제가 임의로 변경해버렸으니 문제가 생기는 것은 당연(???)한 것...

NavigationLink를 통해 이동한 후에 index를 변경해야 한다면 해당 선택한 index를 같이 변경시켜줘야 합니다.

그럼 index를 어떻게 관리해줘야 할까요? 바로 SwiftUI의 얼굴! 상태 관리 변수로 관리해주면 된답니다. @State property를 사용하면 사용자가 선택한 index를 쉽게 관리할 수 있겠네요!

NavigationLink(destination:tag:selection:label:)을 활용하면 selection에 selectedIndex를 바인딩해 연결시켜주면 사용자가 선택한 index와 tag 값이 일치할 때 destination을 실행합니다. 아래는 예제.

아래처럼 작성하면 SecondView에서 index를 변경시켜도 NavigationLink에서 tag와 selection을 활용해 정확한 View를 호출할 수 있습니다.

NavigationStack {
    List(numbers.indices) { index in
        NavigationLink(
            destination: Text("이동할 View"),
            tag: index,
            selection: $selectedIndex,
            label: { Text("\(numbers[index])") }
        )
    }
}

 

문제가 해결 되었습니다!!!!! 라고 할 뻔 🥲🥲🥲

 

 

 

여기까지 읽으면서 혹시 이상한 점은 없으셨나요??

 

맞아요. NavigationStack에서는 NavigationLink와 .navigationDestination은 함께 사용한다고 했는데 위 코드에서는 NavigationLink만 쓰였죠? 사실 .navigationDestination을 꼭 써야하는 건 아닙니다.

NavigationView는 deprecated 되면서 NavigationLink는 살아있죠. 하지만 전부 살아있는 건 아닙니다. 네.. NavigationLink(destination:tag:selection:label:)를 포함한 여러 NavigationLink가 deprecated되었고 deprecated된 기능들을 .navigationDestination을 통해 동작하도록 변경되었습니다.

 

그럼 찐 마지막으로 위 코드를 .navigationDestination를 활용해 다시 작성해보도록 하겠슴다

NavigationStack {
    List(numbers.indices) { index in
        NavigationLink(value: index, label: {
            Text("\(numbers[index])")
        })
    }
    .navigationDestination(for: Int.self) { index in
        // 이동할 View
    }
}

이처럼 작성을 하면 비로소 문제가 해결되었네요!!! 😭😭😭

 

사실 그 동안 NavigationView로 불러온 View에서 데이터를 변경할 일이 없어서 상태 관리의 중요성을 잠깐 잊어버린 것 같습니다ㅠㅠㅠ 그래도 이번 기회에 다시 한 번 깨달은 것 같아 기분은 좋네요 🤣

혹시나 같은 문제를 겪고 계신 분들에게 조금이나마 도움이 되었으면 하는 마음으로 오늘은 이만 글을 줄이겠습니다.

 

아직 포스팅 할 Issue가 많으니 다음에 또 만나요 😭

'Issue' 카테고리의 다른 글

[ISSUE] XcodeCloud Archive Error  (1) 2024.04.18
[Issue] Xcode Cloud Custom Build Scripts  (0) 2024.02.27