【问题标题】:SwiftUI Keeping data up to date across views and view modelsSwiftUI 跨视图和视图模型保持数据最新
【发布时间】:2021-07-15 03:41:44
【问题描述】:

我来自react-native 背景,我无法理解 SwiftUI 中 MVVM 的特定部分:拥有相同实体的多个视图。

我正在构建一个带有帖子的简单社交媒体应用。这些帖子可以在Homepage 和任何用户的Profile 页面上查看。

这就是视图模型的样子

struct HomepagePosts: ObservableObject {
  @Published var posts = [Post]()

  ... rest of logic
}

struct ProfilePosts: ObservableObject {
  @Published var posts = [Post]()

  ... rest of logic
}

我的问题如下。如果用户刚刚发布了帖子,则该帖子将显示在主页和他的个人资料中。如果用户决定更新帖子怎么办?帖子将如何在所有地方更新?

在 React 中,这是通过规范化状态来完成的。不是保留单独的 posts 数组,而是保留 2 个 postIds 数组和一个字典,其中 postId 作为键,整个 Post 对象作为值。这样一来,更新帖子就会在一个地方更新它,并且每个视图都会直接更新。

你如何在 SwiftUI 中做到这一点?

【问题讨论】:

  • 什么是帖子类型?
  • @mahan simple struct idcontent 作为属性。
  • 我认为这里的问题是您有 2 个 ObservableObject 并且它们彼此不相关。最好只有一个“真相”来源并在任何地方使用它。例如:“struct PostsModel:ObservableObject”。这样你就避免了必须不断同步不同 ObservableObject 中的两个 Post 数组的复杂性。
  • @workingdog 是的,我想要一个事实来源。但是,如果我有 PostsModel 结构,我在哪里保存主页和个人资料的帖子数组? - 另外,这两个对象确实相互关联。它们是不同的“帖子提要”,可以包含相同的帖子
  • 将事物的状态(在本例中为帖子)保存在一个 ObservableObject 中(只有一个,不是每个 View 一个)。然后任何依赖于帖子的视图(例如主页和个人资料)都将引用该对象(作为 ObservedObject 或可能更干净的 EnvironmentObject)。对帖子的任何更改都会导致这些视图自动刷新(忘记 MVVM 和 Viper)。如果需要,可以将其他属性添加到模型中或制作单独的模型。最后, Post 本身可以是一个结构体,但持有它的模型不能,因为ObservableObjects 必须是带有引用的类。你所拥有的无法编译。

标签: ios swift swiftui


【解决方案1】:

这里有一些代码来说明上面的注释。我相信你想用一个模型对象来构造它。

struct Post: Identifiable {
  let id: String
  let text: String
}

class Model: ObservableObject {
  @Published var posts: [Post] = []
}

@main
struct TestApp: App {
  // @StateObject var model = Model()
  var body: some Scene {
    WindowGroup {
      Homepage()
       // .environmentObject(model)
    }
  }
}

struct Homepage: View {
  // @EnvironmentObject var model: Model
  @StateObject var model = Model() // holds array of homepage posts
  @State private var newPost: String = ""
  @State private var showProfile = false
  var body: some View {
    VStack {
      TextField("new post", text: $newPost) { _ in
      } onCommit: {
        model.posts.append(Post(id: UUID().uuidString, text: newPost))
        newPost = ""
      }
      Divider()
      ScrollView {
        ForEach(model.posts) { post in
          Text(post.text).padding()
        }
      }
      Divider()
      Button("Profile") {
        showProfile = true
      }
      .sheet(isPresented: $showProfile) {
        Profile()
      }
    }
    .padding()
  }
}

struct Profile: View {
  // @EnvironmentObject var model: Model
  @StateObject var model = Model() // holds array of profile posts
  var body: some View {
    Text("You have \(model.posts.count) posts")
  }
}

【讨论】:

  • 这很好,但它没有考虑homepagePostsprofilePosts 可以在其中有不同的帖子,只有几个帖子重叠的情况。这将如何适应?
  • 在这种情况下,您确实需要两个不同的对象,尽管它们都是 Model 类型。我已经注释掉并添加了更改。 @mahan 是对的,如果你想让它们同步,为什么要让它们不同呢?
  • 所以这是我想要的理想状态,但我的最后一个问题是,如果在主页和个人资料中都找到了Post 1,并且用户从他们的个人资料中对其进行了编辑,那么该编辑是否会反映在主页自动?因为这就是我想要的
  • 是的,因为它们都引用相同的单一数据源(如果您使用第一个版本)。当数据的状态发生变化时,SwiftUI 将处理刷新视图。
  • 但是如果我使用第一个版本,我将失去拥有 2 个相互独立的 Posts 数组的能力,不是吗?在版本 2 中,我可以拥有单独的数组,但似乎我无法在两个数组中同步一篇文章。有没有办法两者兼得?
【解决方案2】:

struct 无法确认ObservableObject 协议。只有class 可以这样做。因此,HomepagePostsProfilePosts 必须是 class 类型。


您不必为帖子创建两个不同的类。为视图之间共享的数据使用单个 ObservableObject


struct Post: Identifiable {
    let id: UUID = UUID()
    let content: String
}

class PostModel: ObservableObject {
    @Published var posts: [Post] = []
}

PostModel 的单个实例传递给HomepagePostsProfilePosts


struct ContentView: View {
    @ObservedObject var postModel: PostModel = PostModel()
    
    var body: some View {
        TabView {
            ProfilePostsView(postModel: postModel)
                .tabItem {
                    Label("Profile", systemImage: "person")
                }
            
            HomepageView(postModel: postModel)
                .tabItem {
                    Label("Home", systemImage: "house")
                }
        }
    }
}


所有代码都准备好测试了。

struct Post: Identifiable {
    let id: UUID = UUID()
    let content: String
}

class PostModel: ObservableObject {
    @Published var posts: [Post] = []
}


struct ContentView: View {
    @ObservedObject var postModel: PostModel = PostModel()
    
    var body: some View {
        TabView {
            ProfilePostsView(postModel: postModel)
                .tabItem {
                    Label("Profile", systemImage: "person")
                }
            
            HomepageView(postModel: postModel)
                .tabItem {
                    Label("Home", systemImage: "house")
                }
        }
    }
}



struct ProfilePostsView: View {
    @ObservedObject var postModel: PostModel

    var body: some View {
        NavigationView {
            VStack {
                List(postModel.posts) { post in
                    Text(post.content)
                }
            }
        }
    }
}


struct HomepageView: View {
    @ObservedObject var postModel: PostModel
    
    var body: some View {
        NavigationView {
            VStack {
                Button(action: {
                    self.postModel.posts.append(Post(content: "Hello \(Date())"))
                }, label: {
                    Text("Add new Posts")
                })
                List(postModel.posts) { post in
                    Text( post.content)
                }
            }
        }
    }
}


在上面的代码中,如果您在HomepageView 中添加新帖子,相同的帖子会自动添加到第二个选项卡(ProfilePostsView)中。如果您这样做,它们将被更新。


postModel 也可以是HomepagePostsProfilePosts 的属性。

更多信息:


https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app

注意:这种方法类似于 Cenk Bilgen

【讨论】:

  • 这看起来很棒,非常感谢。我认为一个问题在这里有一点影响。 homepageprofile 帖子不必总是相同。它们可以有重叠的职位,但必须是独立的。您如何构建它以同步恰好在两者中的帖子?
  • 如果帖子重叠,这显然不是一个好方法。建议您更新您的问题。有人可能会回答。 @DariusMandres
  • 嗯,是的,我正在构建一个社交媒体应用程序,其中有许多不同分页状态的帖子提要,但任何时候都可以在任何提要中找到一个帖子,并且需要同步.我认为这是一个非常正常的用例,但我找不到一个合适的解决方案来解决这个问题
猜你喜欢
  • 2020-02-03
  • 2021-06-01
  • 2014-04-10
  • 2011-06-21
  • 2020-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多