这更多的是一个架构问题。
我正在使用 Rick 和 Morty API 开发一个个人项目,遵循此设计,使用 MVVM 模式使用 Swift 和 SwiftUI 进行构建。
我为每个选项卡视图都有单独的视图模型 - 角色、位置、剧集,负责独立获取角色、位置和剧集。
现在,当您单击某个角色时,它会将您带到角色详细信息视图,该视图还显示该角色曾参与过的剧集,我的困惑是我是否继续为这些创建 ViewModel?或者有更好的方法吗?
工作流程将是这样的:
CharacterView [taps character] —> CharacterDetailView [sees episode characters in, tap an episode] —> EpisodeDetailView
,这样的情况一直持续下去。
解决这个问题更好的方法是什么?或者为每个视图创建一个独立的视图模型就足够了吗?这不会导致项目中的文件集合很大吗?或者做这样的post工作会起作用吗?
我尝试为每个创建一个视图模型。这是获取角色出现的剧集的视图模型。
class CharacterEpisodeViewModel: ObservableObject {
@Published var charEpisodes: [CharacterEpisode] = []
@Published var error: Error?
init(character: Character) {
loadData(character: character)
}
func loadData(character: Character) {
Task(priority: .low) {
do {
try await fetchCharacterEpisode(from: character.episode) { episodes in
self.charEpisodes = episodes
print("Received episodes: \(episodes)")
}
} catch {
print("Error fetching episodes: \(error)")
}
}
}
func fetchCharacterEpisode(from urls: [String], completionHandler: ([CharacterEpisode]) -> Void) async throws {
var fetchedEpisodes: [CharacterEpisode] = []
var seenEpisodes: Set<Int> = []
for url in urls {
let episodeID = Int(url.components(separatedBy: "/").last ?? "") ?? 0
if seenEpisodes.contains(episodeID) {
continue
}
seenEpisodes.insert(episodeID)
// Fetch episode data from API
let episode = try await fetchEpisodeData(from: url)
fetchedEpisodes.append(episode)
print("Received episode: \(episode)")
}
completionHandler(fetchedEpisodes)
}
func fetchEpisodeData(from url: String) async throws -> CharacterEpisode {
guard let url = URL(string: url) else {
throw APIResponseError.invalidURL
}
let (data, response) = try await URLSession.shared.data(from: url)
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
throw APIResponseError.serverError
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let episode = try decoder.decode(CharacterEpisode.self, from: data)
print("Decoded episode: \(episode)")
return episode
}
}
在 SwiftUI 中,View 结构已经是视图模型,因此最好使用它们而不是您自己的对象,否则您将遇到 View 结构已经设计用来解决的一致性问题。
要异步加载视图数据,它是 .task(id:) ,您可以从那里调用异步函数(通常它们在作为环境键的 Controller 结构中定义,因此可以模拟它以进行测试)。
将结果存储在@State中并使用计算变量将其转换为子视图。