I am trying to recreate the account followers flow seen in many social media apps in SwiftUI.
Steps 3 and 4 can go on forever (another example below):
MyProfile -> Followers (my followers list) -> FollowerView -> Followers (their followers list) -> FollowerView -> Followers (their followers list) -> FollowerView and so on...
However with the implementation below when run, the XCode console prints:
A navigationDestination for “myApp.SomeProfile” was declared earlier on the stack. Only the destination declared closest to the root view of the stack will be used.
I have an understanding as to why this is yet am unsure how to fix this issue. I am also if the type used as the NavigationLink
value is suitable since it is Int
. Would it be better to replace it with a more custom type?
Any help would be greatly appreciated.
// Enum with custom options
enum ViewOptions: Hashable {
case followers(Int)
@ViewBuilder func view(_ path: Binding<NavigationPath>, id: Int) -> some View {
FollowersList(path: path, id: id)
}
}
// Root view
struct MyProfileView: View {
@State private var path: NavigationPath = .init()
var body: some View {
NavigationStack(path: $path) {
VStack {
Text(myProfile.username)
Button("See followers") {
path.append(ViewOptions.followers(myProfile.id))
}
.navigationDestination(for: ViewOptions.self) { option in
option.view($path, id: myProfile.id)
}
}
}
}
}
struct FollowersList: View {
@Binding var path: NavigationPath
var id: Int
var body: some View {
List(getFollowers(for: id), id:\.id) { follower in
NavigationLink(follower.username, value: follower)
}
.navigationDestination(for: SomeProfile.self) { profile in
switch profile.isMe {
case true: Text("This is your profile")
case false: SomeProfileView(path: $path, profile: profile)
}
}
}
}
struct SomeProfileView: View {
@Binding var path: NavigationPath
var profile: SomeProfile
var body: some View {
VStack {
Text(profile.username)
Button("See followers") {
path.append(ViewOptions.followers(profile.id))
}
}
}
}
// ----- Types & functions -----
// Example type for my profile
struct MyProfile: Identifiable, Hashable {
var id: Int
var username: String
}
// Example type for profiles reached via navigation link
// (can be my profile but with reduced functionality e.g. no follow button)
struct SomeProfile: Identifiable, Hashable {
var id: Int
var username: String
let isMe: Bool
}
// example myProfile (IRL would be stored in a database)
let myProfile = MyProfile(id: 0, username: "my profile")
// example users (IRL would be stored in a database)
let meVisited = SomeProfile(id: 0, username: "my profile reached from followers list", isMe: true)
let bob = SomeProfile(id: 1, username: "Bob", isMe: false)
let alex = SomeProfile(id: 2, username: "Alex", isMe: false)
// example user followers (IRL would be stored in a database)
let dict: [Int : [SomeProfile]] = [
0 : [bob, alex],
1 : [alex, meVisited],
2 : [alex, meVisited],
]
// example function to get followers of a user (IRL would be a network request)
func getFollowers(for id: Int) -> [SomeProfile] {
return dict[id]!
}
You are repeatedly adding an identical .navigationDestination(...)
modifier by showing views that contain it.
Move all navigationDestination
log somewhere that is not repeating like (but not necessarily) in the NavigationStack
level.
Like this:
NavigationStack(path: $path) {
VStack {
Text(myProfile.username)
Button("See followers") {
path.append(ViewOptions.followers(myProfile.id))
}
.navigationDestination(for: ViewOptions.self) { option in
option.view($path, id: myProfile.id)
}
.navigationDestination(for: SomeProfile.self) { profile in
switch profile.isMe {
case true: Text("This is your profile")
case false: SomeProfileView(path: $path, profile: profile)
}
}
}
}
So .navigationDestination(for: SomeProfile.self)
will not be created again and again.
Don't forget to remove it from the FollowersList