bpi*_*ano 6 ios swift swiftui swiftui-list
.sheet我在使用 SwiftUI 时遇到了内容更改时出现的问题List。我来详细说明一下情况。
我在 Github 上提供了一个示例,您可以克隆该示例,以便轻松重现该错误。
我只有两种观点:
PlaylistCreationViewSearchView他们每个人都有各自的视图模型,称为PlaylistCreationViewModel和SearchViewModel。
有PlaylistCreationView一个Add Songs按钮女巫将呈现SearchView有一个sheet。当用户触摸搜索结果中的歌曲时,系统SearchView会发送一个onAddSong完成处理程序,通知PlaylistCreationView歌曲已添加。
这是情况的简单示意图:
这里还有一些视图的屏幕截图:
当用户触摸 中的歌曲时SearchView,SearchView内容会重新绘制。我的意思是视图被重新创建。如果该视图是第一次加载,则会出现该视图。像这样:
为了简化问题,这是情况的另一种模式:
以下是我在触摸一首歌后可以观察到的情况:
如您所见,歌曲已正确添加到播放列表中,但SearchView现在为空白,因为它已被重新创建。
我无法解释的是,为什么更改列表中的列表会PlaylistCreationViewModel导致重新绘制所呈现的sheet.
我在 Github 上提供了一个示例,您可以克隆它。要重现该问题:
final class PlaylistCreationViewModel: ObservableObject {
@Published var sheetType: SheetType?
@Published var songs: [String] = []
}
extension PlaylistCreationViewModel {
enum SheetType: String, Identifiable {
case search
var id: String {
rawValue
}
}
}
Run Code Online (Sandbox Code Playgroud)
struct PlaylistCreationView: View {
@ObservedObject var viewModel: PlaylistCreationViewModel
var body: some View {
NavigationView {
List {
ForEach(viewModel.songs, id: \.self) { song in
Text(song)
}
Button("Add Song") {
viewModel.sheetType = .search
}
.foregroundColor(.accentColor)
}
.navigationTitle("Playlist")
}
.sheet(item: $viewModel.sheetType) { _ in
sheetView
}
}
private var sheetView: some View {
let addingMode: SearchViewModel.AddingMode = .playlist { addedSong in
// This line leads SwiftUI to rebuild the SearchView
viewModel.songs.append(addedSong)
}
let sheetViewModel: SearchViewModel = SearchViewModel(addingMode: addingMode)
return NavigationView {
SearchView(viewModel: sheetViewModel)
.navigationTitle("Search")
}
}
}
Run Code Online (Sandbox Code Playgroud)
final class SearchViewModel: ObservableObject {
@Published var search: String = ""
let songs: [String] = ["Within", "One more time", "Veridis quo"]
let addingMode: AddingMode
init(addingMode: AddingMode) {
self.addingMode = addingMode
}
}
extension SearchViewModel {
enum AddingMode {
case playlist(onAddSong: (_ song: String) -> Void)
}
}
Run Code Online (Sandbox Code Playgroud)
struct SearchView: View {
@ObservedObject var viewModel: SearchViewModel
var body: some View {
VStack {
TextField("Search", text: $viewModel.search)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
List(viewModel.songs, id: \.self) { song in
Button(song) {
switch viewModel.addingMode {
case .playlist(onAddSong: let onAddSong):
onAddSong(song)
}
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
我很确定我缺少一些 SwiftUI 的东西。但我无法得到它。我以前没有遇到过这样的情况。我在网上找不到任何东西。
任何帮助或建议将不胜感激。预先感谢任何花时间阅读和理解该问题的人。我希望我已经足够好地描述了这个问题。
问题
\n当您点击其中的歌曲时,SearchView您正在调用一个closure块,该块是在Sheet视图内定义的。下面的代码-:
private var sheetView: some View {\n let addingMode: SearchViewModel.AddingMode = .playlist { addedSong in\n // This line leads SwiftUI to rebuild the SearchView\n viewModel.songs.append(addedSong)\n }\n let sheetViewModel: SearchViewModel = SearchViewModel(addingMode: addingMode)\n return NavigationView {\n SearchView(viewModel: sheetViewModel)\n .navigationTitle("Search")\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n此闭包的目标是将选定的歌曲添加到Songs数组中,并在课堂上呈现PlaylistCreationViewModel 。下面是该类:
final class PlaylistCreationViewModel: ObservableObject {\n \n @Published var sheetType: SheetType?\n @Published var songs: [String] = []\n \n}\nRun Code Online (Sandbox Code Playgroud)\n正如您所看到的,这个类是ObservableObject,并且您的songs数组是@Published属性,只要您向该数组添加歌曲,任何使用该模型并标记为 的类@ObservedObject都会刷新其主体。在您的情况下,该类如下:
struct PlaylistCreationView: View {\n \n @ObservedObject var viewModel: PlaylistCreationViewModel\n \n var body: some View {\n NavigationView {\n List {\n ForEach(viewModel.songs, id: \\.self) { song in\n Text(song)\n }\n Button("Add Song") {\n viewModel.sheetType = .search\n }\n .foregroundColor(.accentColor)\n }\n .navigationTitle("Playlist")\n }\n .sheet(item: $viewModel.sheetType) { _ in\n sheetView\n }\n }\n \n private var sheetView: some View {\n let addingMode: SearchViewModel.AddingMode = .playlist { addedSong in\n // This line leads SwiftUI to rebuild the SearchView\n viewModel.songs.append(addedSong)\n }\n let sheetViewModel: SearchViewModel = SearchViewModel(addingMode: addingMode)\n return NavigationView {\n SearchView(viewModel: sheetViewModel)\n .navigationTitle("Search")\n }\n }\nRun Code Online (Sandbox Code Playgroud)\n现在,当添加歌曲后刷新此视图时,它也会重新加载sheetView视图,为什么?因为您的工作表正在读取其状态,viewModel.sheetType 其值仍设置为.search
现在,当调用工作表时,它还会SearchViewModel再次创建对象,并将其交给SearchView,因此您将获得带有空搜索字符串的完整新对象。以下是该模型类:
final class SearchViewModel: ObservableObject {\n \n @Published var search: String = ""\n let songs: [String] = ["Within", "One more time", "Veridis quo"]\n let addingMode: AddingMode\n \n init(addingMode: AddingMode) {\n self.addingMode = addingMode\n }\n \n}\nRun Code Online (Sandbox Code Playgroud)\n解决方案-:
\n您可以将addingMode和sheetViewModel属性移到 之外sheetView,并将它们放在 bodyPlaylistCreationView属性上方。这样,当刷新工作表时,您的对象将不会每次都从头开始实例化。
工作代码-:
\nmport SwiftUI\n\nstruct PlaylistCreationView: View {\n \n @ObservedObject var viewModel: PlaylistCreationViewModel\n var sheetViewModel: SearchViewModel\n \n init(viewModel:PlaylistCreationViewModel) {\n _viewModel = ObservedObject(wrappedValue: viewModel)\n sheetViewModel = SearchViewModel(addingMode: .playlist(onAddSong: { addSong in\n viewModel.songs.append(addSong)\n }))\n \n }\n \n var body: some View {\n NavigationView {\n List {\n ForEach(viewModel.songs, id: \\.self) { song in\n Text(song)\n }\n Button("Add Song") {\n viewModel.sheetType = .search\n }\n .foregroundColor(.accentColor)\n }\n .navigationTitle("Playlist")\n }\n .sheet(item: $viewModel.sheetType) { _ in\n sheetView\n }\n }\n \n private var sheetView: some View {\n NavigationView {\n SearchView(model: sheetViewModel)\n .navigationTitle("Search")\n }\n }\n \n}\nRun Code Online (Sandbox Code Playgroud)\n搜索视图-:
\nimport SwiftUI\n\nstruct SearchView: View {\n \n @ObservedObject var viewModel: SearchViewModel\n \n init(model:SearchViewModel) {\n _viewModel = ObservedObject(wrappedValue: model)\n }\n \n var body: some View {\n VStack {\n TextField("Search", text: $viewModel.search)\n .textFieldStyle(RoundedBorderTextFieldStyle())\n .padding()\n List(viewModel.songs, id: \\.self) { song in\n Button(song) {\n switch viewModel.addingMode {\n case .playlist(onAddSong: let onAddSong):\n onAddSong(song)\n }\n }\n }\n }\n }\n \n}\nRun Code Online (Sandbox Code Playgroud)\n