SwiftUI: Fatal error: No ObservableObject of type * found. A View.environmentObject(_:) for * may be missing as an ancestor of this view.

Hello,

The app displays a list of posts (Post), and for each post, a list of tags (Tag). The Post detail view shows a list of tags for a given post. The Tag detail view shows the post name and the tag name. Both Post and Tag are ObservableObject, and both are passed to the environment when a NavigationLink is tapped.

On the master List with all the posts, you tap on a post. The post is passed in the environment. It's working because the detail view for the selected post is correctly displayed, and you can choose to display a tag from the tags in the displayed post.

But when you tap on a tag to view the details, the app crashes: the post object is not in the environment. No ObservableObject of type Post found. A View.environmentObject(_:) for Post may be missing as an ancestor of this view.

Why? The post object in the PostView, so it should be also available in the TagView because the TagView can only be displayed from the PostView.

Thanks Axel

import SwiftUI

class Store: ObservableObject {
    @Published var posts: [Post] = [
        Post(name: "Post 1", tags: [.init(name: "Tag 1"), .init(name: "Tag 2")]),
        Post(name: "Post 2", tags: [.init(name: "Tag 1"), .init(name: "Tag 2")])
    ]
}

class Post: ObservableObject, Identifiable, Hashable {
    @Published var name: String = ""
    @Published var tags: [Tag] = []

    var id: String { name }

    init(name: String, tags: [Tag]) {
        self.name = name
        self.tags = tags
    }

    static func == (lhs: Post, rhs: Post) -> Bool {
        return lhs.id == rhs.id
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

class Tag: ObservableObject, Identifiable, Hashable {
    @Published var name: String = ""
    var id: String { name }

    init(name: String) {
        self.name = name
    }

    static func == (lhs: Tag, rhs: Tag) -> Bool {
        return lhs.id == rhs.id
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

struct PassEnvironmentObject: View {

    @StateObject private var store: Store = .init()

    var body: some View {
        NavigationStack {
            List {
                ForEach(store.posts) { post in
                    NavigationLink(post.name, value: post)
                }
            }
            .navigationDestination(for: Post.self) { post in
                PostView()
                    .environmentObject(post)
           }
            .navigationDestination(for: Tag.self) { tag in
                TagView()
                    .environmentObject(tag)
            }
        }
    }
}

struct PostView: View {

    @EnvironmentObject private var post: Post

    var body: some View {
        List {
            ForEach(post.tags) { tag in
                NavigationLink(tag.name, value: tag)
            }
        }
    }
}

struct TagView: View {

    @EnvironmentObject private var post: Post
    @EnvironmentObject private var tag: Tag

    var body: some View {
        VStack {
            Text(post.name)
            Text(tag.name)
        }
    }
}

struct PassEnvironmentObject_Previews: PreviewProvider {
    static var previews: some View {
        PassEnvironmentObject()
    }
}

Replies

Your TagView requires BOTH a post and a tag but you are only passing a tag environment object...

  • The TagView is only accessed from the PostView, where the post exists. Pushing a View from the PostView should keep the post object already in the environment by definition.

  • The TagView can only be accessed from a PostView where the post is in the environment (because it's the purpose of the PostView to display the Post and details about it). So why isn't the Post obiect available from the Environment in the context of the TagView if I push a View from this PostView?

Add a Comment

Navigation destinations inherit their environment from the stack in which they appear, not from the context in which they are presented.

So the navigation environment of a previous view on the stack doesn’t affect the subsequent view.

I think you could move your .navigationDestination(for: Tag.self) into PostView, then propagate both the tag and the post into the TagView from there.