Как создать навигационную ссылку внутри другой навигационной ссылки

#ios #swiftui #uikit #swiftui-navigationlink

#iOS #swiftui #uikit #swiftui-navigationlink

Вопрос:

В настоящее время мы с моей командой разрабатываем клиент Mastodon в SwiftUI, и у меня есть простой StatusView, где я отображаю все данные post, которые в настоящее время выглядят следующим образом:

Пример сообщения

Это представление, известное в моем проекте как StatusView , имеет два NavigationLinks : основное, которое перенаправляет пользователя в ветку публикации, и то, которое перенаправляет пользователя в профиль автора публикации при нажатии на изображение профиля публикации.

До этого все работало нормально. Если вы нажмете в любом месте публикации, которое не является кнопками («Нравится», «Увеличить» и «Поделиться») или изображением профиля, откроется тема. Если вы нажмете на изображение профиля, откроется профиль автора.

введите описание изображения здесь

Но если вы нажмете под изображением профиля, приложение выйдет из строя, выдавая мне только следующую ошибку:

 2020-08-23 21:32:39.929392-0400 Hyperspace[830:147862] WF: _WebFilterIsActive returning: NO
2020-08-23 21:32:40.117865-0400 Hyperspace[830:147862] [assertion] Error acquiring assertion: <Error Domain=RBSAssertionErrorDomain Code=2 "Specified target process does not exist" UserInfo={NSLocalizedFailureReason=Specified target process does not exist}>
2020-08-23 21:32:40.117994-0400 Hyperspace[830:147862] [ProcessSuspension] 0x11decfa80 - ProcessAssertion: Failed to acquire RBS Background assertion 'WebProcess Background Assertion' for process with PID 837, error: Error Domain=RBSAssertionErrorDomain Code=2 "Specified target process does not exist" UserInfo={NSLocalizedFailureReason=Specified target process does not exist}
2020-08-23 21:32:40.119401-0400 Hyperspace[830:147862] [assertion] Error acquiring assertion: <Error Domain=RBSAssertionErrorDomain Code=2 "Specified target process does not exist" UserInfo={NSLocalizedFailureReason=Specified target process does not exist}>
2020-08-23 21:32:40.119549-0400 Hyperspace[830:147862] [ProcessSuspension] 0x11decfac0 - ProcessAssertion: Failed to acquire RBS Suspended assertion 'WebProcess Suspended Assertion' for process with PID 837, error: Error Domain=RBSAssertionErrorDomain Code=2 "Specified target process does not exist" UserInfo={NSLocalizedFailureReason=Specified target process does not exist}
Fatal error: UIKitNavigationBridge: multiple active destinations: file SwiftUI, line 0
2020-08-23 21:32:40.292135-0400 Hyperspace[830:147862] Fatal error: UIKitNavigationBridge: multiple active destinations: file SwiftUI, line 0
  

Я предполагаю, что это потому, что при нажатии на нее запускаются ссылки навигации как в потоке, так и в профиле, что приводит к сбою приложения из-за нескольких активных назначений, поскольку оно пытается перейти к потоку и профилю одновременно.

TL; DR

Мое приложение выходит из строя, потому что две NavigationLinks запускаются одновременно, когда они не должны.

Как я могу это исправить?

Заранее спасибо.

Мой код

 /// The status is being displayed in a ``StatusList``, so we should make it smaller and more compact.
private struct CompactStatusView: View {

    /// The ``Status`` data model from where we obtain all the data.
    var status: Status

    /// Used to trigger the navectigationLink to redirect the user to the thread.
    @Binding var goToThread: Bool

    /// Used to redirect the user to a specific profile.
    @Binding var profileViewActive: Bool

    var body: some View {
        ZStack {

            self.content
                .padding(.vertical, 5)
                .contextMenu(
                    ContextMenu(menuItems: {

                        Button(action: {}, label: {
                            Label("Report post", systemImage: "flag")
                        })

                        Button(action: {}, label: {
                            Label("Report (self.status.account.displayName)", systemImage: "flag")
                        })

                        Button(action: {}, label: {
                            Label("Share as Image", systemImage: "square.and.arrow.up")
                        })

                    })
                )

        }
            .buttonStyle(PlainButtonStyle())
            .navigationBarHidden(self.profileViewActive)
    }

    var content: some View {
        HStack(alignment: .top, spacing: 12) {

            URLImage(URL(string: self.status.account.avatarStatic)!,
                placeholder: { _ in
                    Image("amodrono")
                        .resizable()
                        .scaledToFit()
                        .clipShape(Circle())
                        .frame(width: 50, height: 50)
                        .redacted(reason: .placeholder)
                },
                content: {
                    $0.image
                        .resizable()
                        .scaledToFit()
                        .clipShape(Circle())
                        .frame(width: 50, height: 50)
                }
            )
                .onTapGesture {
                    self.profileViewActive.toggle()
                }
                .background(
                    NavigationLink(
                        destination: ProfileView(
                            accountInfo: ProfileViewModel(
                                accountID: self.status.account.id
                            ),
                            isParent: false
                        ),
                        isActive: self.$profileViewActive
                    ) {
                        Text("")
                    }
                        .frame(width: 0, height: 0)
                )

            VStack(alignment: .leading, spacing: 2) {
                HStack(alignment: .firstTextBaseline) {

                    if !self.status.account.displayName.isEmpty {
                        Text("(self.status.account.displayName)")
                            .font(.headline)
                            .lineLimit(1)
                    }

                    Text("@(self.status.account.acct)")
                        .foregroundColor(.secondary)
                        .lineLimit(1)

                    Text((self.status.createdAt.getDate()!.getInterval())")
                        .foregroundColor(.secondary)
                        .lineLimit(1)
                }

                StatusViewContent(
                    isMain: false,
                    content: self.status.content,
                    card: self.status.card,
                    attachments: self.status.mediaAttachments,
                    goToProfile: self.$profileViewActive
                )

                StatusActionButtons(
                    isMain: false,
                    repliesCount: self.status.repliesCount,
                    reblogsCount: self.status.reblogsCount,
                    favouritesCount: self.status.favouritesCount,
                    statusUrl: self.status.uri
                )

            }
                .onTapGesture {
                    self.goToThread.toggle()
                }
                .background(
                    NavigationLink(
                        destination: ThreadView(
                            mainStatus: self.status
                        ),
                        isActive: self.$goToThread
                    ) {
                        EmptyView()
                    }
                )

            Spacer()
        }
    }

}
  

Я думаю, что важными частями здесь являются:

Изображение профиля:

 URLImage(URL(string: self.status.account.avatarStatic)!,
                placeholder: { _ in
                    Image("amodrono")
                        .resizable()
                        .scaledToFit()
                        .clipShape(Circle())
                        .frame(width: 50, height: 50)
                        .redacted(reason: .placeholder)
                },
                content: {
                    $0.image
                        .resizable()
                        .scaledToFit()
                        .clipShape(Circle())
                        .frame(width: 50, height: 50)
                }
            )
                .onTapGesture {
                    self.profileViewActive.toggle()
                }
                .background(
                    NavigationLink(
                        destination: ProfileView(
                            accountInfo: ProfileViewModel(
                                accountID: self.status.account.id
                            ),
                            isParent: false
                        ),
                        isActive: self.$profileViewActive
                    ) {
                        Text("")
                    }
                        .frame(width: 0, height: 0)
                )
  

Два последних модификатора

 .onTapGesture {
                    self.goToThread.toggle()
                }
                .background(
                    NavigationLink(
                        destination: ThreadView(
                            mainStatus: self.status
                        ),
                        isActive: self.$goToThread
                    ) {
                        EmptyView()
                    }
                )
  

Ответ №1:

Вот демонстрация возможного решения для некоторого реплицируемого сценария (поскольку предоставленный код не тестируется как есть). Идея состоит в том, чтобы повторно использовать одну NavigationLink, но с разными назначениями в зависимости от места активации.

Протестировано с Xcode 12 / iOS 14

 struct DemoRowView: View {
    @State private var isProfile = false
    @State private var isActive = false
    var body: some View {
        HStack {
            Image(systemName: "person")
                .scaledToFit()
                .onTapGesture {
                    self.isProfile = true
                    self.isActive = true
                }
            Text("Thread description")
                .onTapGesture {
                    self.isProfile = false
                    self.isActive = true
                }
                .background(
                    NavigationLink(destination: self.destination(), isActive: $isActive) { EmptyView() }
                )
        }
        .buttonStyle(PlainButtonStyle())
    }

    @ViewBuilder
    private func destination() -> some View {
        if isProfile {
            ProfileView()
        } else {
            ThreadView()
        }
    }
}