Reputation: 2040
My team and I are currently developing a Mastodon client in SwiftUI and I've got a simple StatusView where I display all the post data, which currently looks like this:
This view, known on my project as StatusView
, has two NavigationLinks
: The main one, that redirects the user to the post's thread, and one that redirects the user to the post's author's profile when the post's profile picture is tapped.
Until here, everything works fine. If you tap anywhere on the post that aren't the buttons (like, boost, and share) or the profile picture, it opens the thread. If you tap on the profile picture, it opens the author's profile.
But if you tap below the profile picture, the app crashes, only giving me the following error:
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
I guess this is because when you tap there, both the thread's and the profile's navigation links are triggered, causing the app to crash because there are multiple active destinations, as it's trying to go to the thread and to the profile at the same time.
How can I fix this?
Thanks in Advance.
/// 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()
}
}
}
I guess the important parts here are:
The profile image:
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)
)
The two last modifiers
.onTapGesture {
self.goToThread.toggle()
}
.background(
NavigationLink(
destination: ThreadView(
mainStatus: self.status
),
isActive: self.$goToThread
) {
EmptyView()
}
)
Upvotes: 2
Views: 997
Reputation: 257711
Here is a demo of possible solution on some replicated scenario (because provided code is not testable as-is). The idea is to reuse one NavigationLink but with different destinations depending on activation place.
Tested with 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()
}
}
}
Upvotes: 2