首页 > 解决方案 > SwiftUI:使用数据库连接实现评级视图

问题描述

在我的应用程序中,我希望用户给电影评分。电影是从 REST 接口加载的,评级应该存储在本地。问题是我不知道如何保持用户的新评级。

我可以从我的数据库中加载评级并更改 MovieRow 中的值,但我不知道如何获取回调以保存更改后的评级。

这是我所拥有的:

struct RatingView: View {

    @Binding var rating: Int

    var maximumRating = 5
    var offImage: Image?
    var onImage = Image(systemName: "star.fill")
    var offColor = Color.gray
    var onColor = Color.yellow

    var body: some View {
        HStack {

            ForEach(1..<maximumRating + 1) { number in
                self.image(for: number)
                    .foregroundColor(number > self.rating ? self.offColor : self.onColor)
                    .onTapGesture {
                        self.rating = number
                    }
            }
        }
    }

    func image(for number: Int) -> Image {
        if number > rating {
            return offImage ?? onImage
        } else {
            return onImage
        }
    }
}

struct MovieRow: SwiftUI.View {

    let movie: Movie
    @State var ownRating: Int

    var body: some SwiftUI.View {
        ZStack(alignment: Alignment(horizontal: .leading, vertical: .bottom)) {
            ZStack(alignment: Alignment(horizontal: .trailing, vertical: .top
            )) {
                KFImage(movie.fullPosterURL)
                    .cancelOnDisappear(true)
                    .resizable()
                    .frame(height: 250)
                RatingView(rating: $ownRating)
                    .padding([.top, .trailing], 20.0)
            }
            Text(movie.title)
                .font(.largeTitle)
                .padding(.all)
                .background(Color(.darkGray)
                    .opacity(0.5))
                .foregroundColor(.white)
        }
    }
}

struct MovieListView: View {

    @ObservedObject var viewModel = MovieViewModel()

    var body: some View {
        List{
            ForEach(viewModel.movies) { movie in
                MovieRow(movie: movie, ownRating: self.viewModel.ratingForMovieId(id: movie.id))
                    .listRowInsets(EdgeInsets())
            }
        }
    }
}

class MovieViewModel: ObservableObject{

    private let provider: NetworkManager?
    private let ratingModel = RatingModel()

    @Published var movies = [Movie]()

    init(provider: NetworkManager? = NetworkManager(), movies: [Movie] = []) {
        self.provider = provider
        self.movies = movies

        loadNewMovies()
    }

    func loadNewMovies(){
         provider?.getNewMovies(page: 1) {[weak self] movies in
                   print("\(movies.count) new movies loaded")
                   self?.movies.removeAll()
            self?.movies.append(contentsOf: movies)}
    }

    func ratingForMovieId(id: Int)->Int{
        return ratingModel.ratingForMovieId(movieId: id)
    }
}

class RatingModel{

    let realm = try! Realm()

    func ratingForMovieId(movieId: Int)->Int{

        let result = realm.objects(MovieRating.self).filter("movieId = %@", movieId)

        guard let movieRating = result.first else{
            return 0
        }
        return movieRating.rating
    }

    func updateRating(movieId: Int, rating: Int){

        try! realm.write {
            realm.create(MovieRating.self, value: ["movieId": movieId, "rating": rating], update: .modified)
        }
    }

}

标签: iosswiftdatabaserestswiftui

解决方案


要对此给出具体答案,您必须提供有关数据库/ViewModel/REST-Interface 的更多信息

有些事情可能会解决您的@State var ownRating: Int问题@Binding var ownRating: IntState旨在仅在视图中使用并应声明privateBinding将传递与ViewModel

何时onTapGesture激活参考您ViewModel并从那里使用 REST 接口预先建立的 save()/update()。

如果您的保存/更新方法需要对象/id 保持不变,您可能必须将整个Movie对象或id变量传递给。RatingView

评级更改时刷新的 UI 取决于发生某事的“ping”或“通知”。SwiftUI 及其教程在这方面严重依赖 CoreData。

我对 Realm 不熟悉,但这是我可以收集到的。RealmNotificationTokens这将为可观察对象中的 SwiftUI 更新提供所需的通知。

获取 ListView 的对象 -如何在 SwiftUI 中使用 Realm

入门指南 - https://realm.io/docs/swift/latest/

带有 UITableView 查询代码的教程应该是您所需要的 - https://academy.realm.io/posts/meetup-jp-simard-mastering-realm-notifications/

observed使用Realm 对象对 ListView 进行故障排除-将 Realm 与 SwiftUI 一起使用时索引越界

创建领域数据模型并保存 - https://learnappmaking.com/realm-database-swift-getting-started/

您应该使用标准 CRUD 方法创建一个 ObservableObject。

Movie来自 UI 的对象 ->在 Realm 中 ->使用来自 Realm 的新查询ViewModel.create(newMovie: Movie)通知/保存@Published var movies

ViewModel init()Retrieve [Movie]from Realm-> setup NotifiationTokento observeand @Published var movieswith the new query from Realm

从 UI 更新 Movie.rating -> Realm 中的 ViewModel.update(updatedMovie)->@Published var movies使用来自 Realm 的新查询通知/更新

删除的工作方式与其他方式相同。

关于如何持久化有很多不同的方法,这当然不是最有效的,因为它每次都会检索整个查询,但它应该让你开始。NotificationToken 似乎支持更新到正确的字母NotificationToken.observer

专门针对您的代码。当用户onTapGesture使用您应该引用的新评级viewModel.update(updatedMovie: updatedMovie)时,当视图moviesNotificationToken.


推荐阅读