I am trying to recreate the account followers flow seen in many social media apps in SwiftUI.
- You press a button on your profile to see a list of your followers
- You can click on any one of your followers to see their account
- You can press a button on their profile to see a list of their followers
- You can click on any one of their followers to see their account
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]!
}
.navigationDestination
removed fromSomeProfileView
so there is only one. Also, what do you mean by the id an argument in the case for the enum? Have I not already done that?.navigationDestination
inside a loop.