Как разместить NavigationLink внутри другой NavigationLink

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

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

Это представление, известное в моем проекте как 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

My app crashes because two NavigationLinks are triggered at the same time when they shouldn't.

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

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

Мой код

/// 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()
                    }
                )

person iAlex11    schedule 24.08.2020    source источник


Ответы (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()
        }
    }
}
person Asperi    schedule 24.08.2020
comment
Спасибо! Какое простое решение, я не могу быть глупее ... ???? - person iAlex11; 25.08.2020