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