首页 > 解决方案 > SwiftUI List 重绘已呈现工作表的内容

问题描述

.sheetList内容更改时,我遇到了 SwiftUI 的问题。让我详细解释一下情况。

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

语境


我只有 2 意见:

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

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

这里有一个简单的情况示意图:

情况

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

播放列表CreationView

播放列表CreationView

搜索视图

搜索视图

问题

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

空白搜索

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

问题模式

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

在此处输入图像描述

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

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

如何重现问题


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

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

代码


播放列表CreationViewModel

final class PlaylistCreationViewModel: ObservableObject {
    
    @Published var sheetType: SheetType?
    @Published var songs: [String] = []
    
}

extension PlaylistCreationViewModel {
    
    enum SheetType: String, Identifiable {
        
        case search
        
        var id: String {
            rawValue
        }
        
    }
    
}

播放列表CreationView

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")
        }
    }
    
}

搜索视图模型

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)
        
    }
    
}

搜索视图

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)
                    }
                }
            }
        }
    }
    
}

我很确定这是我缺少的一些 SwiftUI 东西。但我无法得到它。我以前没有遇到过这样的情况。我在网上找不到任何东西。

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

标签: iosswiftswiftuiswiftui-list

解决方案


问题

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

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")
    }
}

这个闭包的目标是在Songs数组中添加选定的歌曲,出现在PlaylistCreationViewModel 课堂上。下面是那个类-:

final class PlaylistCreationViewModel: ObservableObject {
    
    @Published var sheetType: SheetType?
    @Published var songs: [String] = []
    
}

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

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")
        }
    }

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

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

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
    }
    
}

解决方案-:

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

工作代码-:

mport SwiftUI

struct PlaylistCreationView: View {
    
    @ObservedObject var viewModel: PlaylistCreationViewModel
    var sheetViewModel: SearchViewModel
    
    init(viewModel:PlaylistCreationViewModel) {
        _viewModel = ObservedObject(wrappedValue: viewModel)
         sheetViewModel = SearchViewModel(addingMode: .playlist(onAddSong: {  addSong in
            viewModel.songs.append(addSong)
        }))
        
    }
    
    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 {
         NavigationView {
            SearchView(model: sheetViewModel)
                .navigationTitle("Search")
        }
    }
    
}

搜索视图-:

import SwiftUI

struct SearchView: View {
    
    @ObservedObject var viewModel: SearchViewModel
    
    init(model:SearchViewModel) {
        _viewModel = ObservedObject(wrappedValue: model)
    }
    
    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)
                    }
                }
            }
        }
    }
    
}

推荐阅读