SwiftUI 列表重绘所呈现工作表的内容

bpi*_*ano 6 ios swift swiftui swiftui-list

.sheet我在使用 SwiftUI 时遇到了内容更改时出现的问题List。我来详细说明一下情况。

在 Github 上提供了一个示例,您可以克隆该示例,以便轻松重现该错误。

语境


我只有两种观点:

  • PlaylistCreationView
  • SearchView

他们每个人都有各自的视图模型,称为PlaylistCreationViewModelSearchViewModel

PlaylistCreationView一个Add Songs按钮女巫将呈现SearchView有一个sheet。当用户触摸搜索结果中的歌曲时,系统SearchView会发送一个onAddSong完成处理程序,通知PlaylistCreationView歌曲已添加。

这是情况的简单示意图:

情况

这里还有一些视图的屏幕截图:

播放列表创建视图

播放列表创建视图

搜索视图

搜索视图

问题

当用户触摸 中的歌曲时SearchViewSearchView内容会重新绘制。我的意思是视图被重新创建。如果该视图是第一次加载,则会出现该视图。像这样:

空白搜索

为了简化问题,这是情况的另一种模式:

问题图式

以下是我在触摸一首歌后可以观察到的情况:

在此输入图像描述

如您所见,歌曲已正确添加到播放列表中,但SearchView现在为空白,因为它已被重新创建。

我无法解释的是,为什么更改列表中的列表会PlaylistCreationViewModel导致重新绘制所呈现的sheet.

如何重现问题


在 Github 上提供了一个示例,您可以克隆它。要重现该问题:

  1. 克隆该项目并在任何 iPhone 模拟器上运行它。
  2. 触摸“添加歌曲”。
  3. 在搜索视图顶部的搜索栏中输入内容。
  4. 选择一首歌曲。您的搜索应该已经消失,因为视图已被重建。

代码


播放列表创建视图模型

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 的东西。但我无法得到它。我以前没有遇到过这样的情况。我在网上找不到任何东西。

任何帮助或建议将不胜感激。预先感谢任何花时间阅读和理解该问题的人。我希望我已经足够好地描述了这个问题。

Tus*_*rma 3

问题

\n

当您点击其中的歌曲时,SearchView您正在调用一个closure块,该块是在Sheet视图内定义的。下面的代码-:

\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}\n
Run Code Online (Sandbox Code Playgroud)\n

此闭包的目标是将选定的歌曲添加到Songs数组中,并在课堂上呈现PlaylistCreationViewModel 。下面是该类:

\n
final class PlaylistCreationViewModel: ObservableObject {\n    \n    @Published var sheetType: SheetType?\n    @Published var songs: [String] = []\n    \n}\n
Run Code Online (Sandbox Code Playgroud)\n

正如您所看到的,这个类是ObservableObject,并且您的songs数组是@Published属性,只要您向该数组添加歌曲,任何使用该模型并标记为 的类@ObservedObject都会刷新其主体。在您的情况下,该类如下:

\n
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    }\n
Run Code Online (Sandbox Code Playgroud)\n

现在,当添加歌曲后刷新此视图时,它也会重新加载sheetView视图,为什么?因为您的工作表正在读取其状态,viewModel.sheetType 其值仍设置为.search

\n

现在,当调用工作表时,它还会SearchViewModel再次创建对象,并将其交给SearchView,因此您将获得带有空搜索字符串的完整新对象。以下是该模型类:

\n
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}\n
Run Code Online (Sandbox Code Playgroud)\n

解决方案-:

\n

您可以将addingModesheetViewModel属性移到 之外sheetView,并将它们放在 bodyPlaylistCreationView属性上方。这样,当刷新工作表时,您的对象将不会每次都从头开始实例化。

\n

工作代码-:

\n
mport 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}\n
Run Code Online (Sandbox Code Playgroud)\n

搜索视图-:

\n
import 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}\n
Run Code Online (Sandbox Code Playgroud)\n