我从从 web 加载数据的 viewModel 加载我的数据。问题:我想设置一些预览示例数据以在预览窗口中包含内容。目前我的预览包含一个空列表,因为我不提供数据。
我怎样才能做到这一点?
struct MovieListView: View {
@ObservedObject var viewModel = MovieViewModel()
var body: some View {
List{
ForEach(viewModel.movies) { movie in
MovieRow(movie: movie)
.listRowInsets(EdgeInsets())
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
MovieListView()
}
}
class MovieViewModel: ObservableObject{
private let provider = NetworkManager()
@Published var movies = [Movie]()
init() {
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)}
}
}
Run Code Online (Sandbox Code Playgroud)
这是可能的方法(基于视图模型成员的依赖注入而不是紧密耦合)
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
// create Movie to be previewed inline, say from bundled data
MovieListView(viewModel: MovieViewModel(provider: nil, movies: [Movie(...)]))
}
}
class MovieViewModel: ObservableObject {
private var provider: NetworkManager?
@Published var movies: [Movie]
// same as before by default, but allows to modify if/when needed explicitly
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)
}
}
}
Run Code Online (Sandbox Code Playgroud)
这个问题是@StateObject
在 WWDC 2020 上提出的。我相信现在您会想要使用@StateObject
而不是@ObservedObject
因为否则您的视图模型可能会被重新初始化多次(在这种情况下会导致多次网络调用)。
我想做与 OP 完全相同的事情,但是使用@StateObject
. 这是我的解决方案,不依赖于任何构建配置。
struct MovieListView: View {
@StateObject var viewModel = MovieViewModel()
var body: some View {
MovieListViewInternal(viewModel: viewModel)
}
}
private struct MovieListViewInternal<ViewModel: MovieViewModelable>: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
List {
ForEach(viewModel.movies) { movie in
MovieRow(movie: movie)
}
}
.onAppear {
viewModel.fetchMovieRatings()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
MovieListViewInternal(viewModel: PreviewMovieViewModel())
}
}
Run Code Online (Sandbox Code Playgroud)
视图模型协议和实现:
protocol MovieViewModelable: ObservableObject {
var movies: [Movie] { get }
func fetchMovieRatings()
// Define vars or funcs for anything else your view accesses in your view model
}
class MovieViewModel: MovieViewModelable {
@Published var movies = [Movie]()
init() {
loadNewMovies()
}
private func loadNewMovies() {
// do the network call
}
func fetchMovieRatings() {
// do the network call
}
}
class PreviewMovieViewModel: MovieViewModelable {
@Published var movies = [fakeMovie1, fakeMovie2]
func fetchMovieRankings() {} // do nothing while in a Preview
}
Run Code Online (Sandbox Code Playgroud)
这样,您的外部接口MovieListView
完全相同,但对于预览,您可以使用内部视图定义并覆盖视图模型类型。
除了上面的答案之外,如果您想保持运输代码库干净,我发现扩展在预处理器标志中捕获的类以添加方便的 init 是可行的。
#if DEBUG
extension MovieViewModel{
convenience init(forPreview: Bool = true) {
self.init()
//Hard code your mock data for the preview here
self.movies = [Movie(...)]
}
}
#endif
Run Code Online (Sandbox Code Playgroud)
然后也使用预处理器标志修改您的 SwiftUI 结构:
struct MovieListView: View {
#if DEBUG
let viewModel: MovieViewModel
init(viewModel: MovieViewModel = MovieViewModel()){
self.viewModel = viewModel
}
#else
@StateObject var viewModel = MovieViewModel()
#endif
var body: some View {
List{
ForEach(viewModel.movies) { movie in
MovieRow(movie: movie)
.listRowInsets(EdgeInsets())
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
MovieListView(viewModel: MovieViewModel(forPreview: true)
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
2042 次 |
最近记录: |