Я новичок в SwiftUI, поэтому, пожалуйста, оцените меня, мне бы хотелось узнать больше. Моя программа выдает ошибку «Тема 1: Неустранимая ошибка: ObservableObject типа StoryVM не найден. View.environmentObject(_:) для StoryVM может быть отсутствует как предок этой точки зрения». тот же код, использующий фиктивные данные, работает нормально, но я пытаюсь реализовать его с помощью Firebase
struct StoryCellView: View {
@Binding var story: Story
var isSeen: Bool = false
@Environment(\.colorScheme) var scheme
@EnvironmentObject var viewModel: StoryVM
var body: some View {
Text(story.name ?? "")
.font(.system(size: 12))
.fontWeight(.heavy)
.frame(minWidth: 62, maxWidth: 62, minHeight: 62, maxHeight: 62)
.clipShape(Circle())
.padding(2)
.background(scheme == .dark ? .black: .gray, in: Circle())
.padding(3)
.background(
LinearGradient(gradient: Gradient(colors: [.red, .orange, .red, .orange]), startPoint: .top, endPoint: .bottom)
.clipShape(Circle())
.opacity(isSeen ? 0 : 1)
)
.onTapGesture {
withAnimation{
//story.isSeen = true
viewModel.markStoryAsSeen(story)
viewModel.currentStory = story.id ?? ""
viewModel.showStory = true
print("Story tapped: \(story.id ?? "")")
}
}
}
}
struct StoryCircleView: View {
@EnvironmentObject var viewModel: StoryVM
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 12, content: {
ForEach($viewModel.stories) { $story in
StoryCellView(story: $story) //revert back to original when auth is implemented
.environmentObject(viewModel)
}
})
.padding()
.padding(.top, 10)
}
.onAppear{
//viewModel.getStoriesCount()
viewModel.getStories()
}
}
}
модель представления
@MainActor
final class StoryVM: ObservableObject {
@Published var showStory: Bool = false
@Published var currentStory: String = ""
@Published var stories: [Story] = []
private var isSeenStatus: [String: Bool] = [:]
private var lastDocument: DocumentSnapshot? = nil
// func getAllProducts() async throws {
// self.products = try await ProductsManager.shared.getAllProducts()
//
// }
func getStories(){
Task{
do{
let (newStories, lastDocument) = try await StoriesManager.shared.getAllStories(count: 8, lastDocument: lastDocument)
self.stories.append(contentsOf: newStories)
if let lastDocument {
self.lastDocument = lastDocument
}
newStories.forEach { story in
if isSeenStatus[story.id ?? ""] == nil {
isSeenStatus[story.id ?? ""] = false
}
}
print("you ran me!!!")
}catch{
print(error)
}
}
}
//will manage seen status when authentication has been implemented
func markStoryAsSeen(_ story: Story) {
if let storyID = story.id {
isSeenStatus[storyID] = true
}
}
func isStorySeen(_ story: Story) -> Bool {
if let storyID = story.id {
return isSeenStatus[storyID] ?? false
}
return false
}
func getStoriesCount() {
Task {
let count = try await StoriesManager.shared.getAllStoriesCount()
print("all story count \(count)")
}
}
}
struct ContentView: View {
@Environment(\.colorScheme) var colorScheme
@StateObject private var viewModel = StoryVM()
var body: some View {
GeometryReader{
let size = $0.size
let safeArea = $0.safeAreaInsets
NavigationStack{
StoryCircleView()
.overlay(
StoryView(size: size, safeArea: safeArea)
.toolbar(.hidden, for: .tabBar)
)
}
.environmentObject(viewModel)
}
}
}
пожалуйста, что я делаю не так. заранее спасибо
Где вы объявили @StateObject var viewModel = StoryVM()
в родительском представлении и передали ли вы его в подпредставления с помощью .environmentObject(viewModel)
? Обратите внимание: в StoryCircleView
вам следует удалить .environmentObject(viewModel)
или хотя бы переместить его за пределы ScrollView
.
@workingdogsupportUkraine единственное место, которое я объявил (в)StateObject var viewModel = StoryVM(), — это contentView. и перемещение его за пределы прокрутки не помогло. Я попробовал использовать (at)StateObject вместо (at)environmentobject, это устранило ошибку. но затем наложение в режиме просмотра контента перестало работать
@workingdogsupportUkraine есть идеи, почему наложение в представлении контента перестало работать после того, как OP использовал StateObject
Вы путаете данные модели и данные представления. В модельном классе должны быть только истории. Выбор должен иметь вид @state. Таким образом, одну и ту же модель можно использовать в нескольких представлениях.
Пробовали ли вы изменить @EnvironmentObject var viewModel: StoryVM на @stateObject var viewModel = StoryVM()
это исправило ошибку, есть идеи, как заставить работать наложение в представлении контента. из вывода в терминале я вижу, что ontapgesture работает, но наложение не работает. это странно, учитывая, что если я использую @environmentObject, наложение работает
честно говоря, я понятия не имею, почему ваше наложение не работает. возможно, некоторые эксперты увидят ваш вопрос и дадут лучшее представление
У вас должен быть только один источник истины, то есть только один @StateObject private var viewModel = StoryVM()
в вашем ContentView (в том виде, в котором он у вас есть), который вы передаете другим представлениям, несколько таких источников недопустимы. В качестве ответа предоставил свой тестовый код. Можете ли вы также сказать нам, какая строка вашего кода выдает ошибку?
Вот мой базовый тестовый код, который у меня не дает сбоев.
Обратите внимание: id
(например, Story.id) не должен быть Optional
.
Также у вас должен быть только один источник истины, то есть только один @StateObject private var viewModel = StoryVM()
в вашем ContentView
(в том виде, в котором он у вас есть), который вы передаете другим точкам зрения, несколько таких источников недопустимы.
// for testing
struct Story: Identifiable {
let id = UUID() // <--- here, not optional
var name: String
}
struct StoryCellView: View {
@Binding var story: Story
var isSeen: Bool = false
@Environment(\.colorScheme) var scheme
@EnvironmentObject var viewModel: StoryVM
var body: some View {
Text(story.name)
.font(.system(size: 12))
.fontWeight(.heavy)
.frame(minWidth: 62, maxWidth: 62, minHeight: 62, maxHeight: 62)
.clipShape(Circle())
.padding(2)
.background(scheme == .dark ? .black: .gray, in: Circle())
.padding(3)
.background(
LinearGradient(gradient: Gradient(colors: [.red, .orange, .red, .orange]), startPoint: .top, endPoint: .bottom)
.clipShape(Circle())
.opacity(isSeen ? 0 : 1)
)
.onTapGesture {
withAnimation{
//story.isSeen = true
viewModel.markStoryAsSeen(story)
// viewModel.currentStory = story.id ?? ""
viewModel.showStory = true
// print("Story tapped: \(story.id ?? "")")
print("---> in StoryCellView Story tapped: \(story.id)")
}
}
}
}
struct StoryCircleView: View {
@EnvironmentObject var viewModel: StoryVM
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 12, content: {
ForEach($viewModel.stories) { $story in
StoryCellView(story: $story) // <--- here, no .environmentObject
}
})
.padding()
.padding(.top, 10)
}
.onAppear{
viewModel.getStoriesCount()
viewModel.getStories()
print("----> in StoryCircleView viewModel: \(viewModel.stories.count) ")
}
}
}
@MainActor
final class StoryVM: ObservableObject {
@Published var showStory: Bool = false
@Published var currentStory: String = ""
// <--- for testing
@Published var stories: [Story] = [Story(name: "story-1"), Story(name: "story-2")]
private var isSeenStatus: [String: Bool] = [:]
// private var lastDocument: DocumentSnapshot? = nil
func getStories(){ }
//will manage seen status when authentication has been implemented
func markStoryAsSeen(_ story: Story) { }
func isStorySeen(_ story: Story) -> Bool {
return false
}
func getStoriesCount() { }
}
struct ContentView: View {
@Environment(\.colorScheme) var colorScheme
@StateObject private var viewModel = StoryVM()
var body: some View {
GeometryReader {
let size = $0.size
let safeArea = $0.safeAreaInsets
NavigationStack{
StoryCircleView()
.overlay(
StoryView(size: size, safeArea: safeArea)
.toolbar(.hidden, for: .tabBar)
)
}
.environmentObject(viewModel)
}
}
}
// for testing
struct StoryView: View {
@EnvironmentObject var viewModel: StoryVM // <--- here
let size: CGSize
let safeArea: EdgeInsets
var body: some View {
Text("StoryView")
.onAppear {
print("----> in StoryView viewModel: \(viewModel.stories.count) ")
}
}
}
вы гений... вы заслуживаете бутылку охлажденного пива
рад, что это наконец сработало для вас, удачного кодирования.
спасибо @workingdogsupportUkraine