SwiftUI List 重绘已呈现工作表的内容
Posted
技术标签:
【中文标题】SwiftUI List 重绘已呈现工作表的内容【英文标题】:SwiftUI List redraw the content of a presented sheet 【发布时间】:2021-03-02 12:06:24 【问题描述】:当List
内容发生更改时,我遇到了与呈现.sheet
相关的SwiftUI 问题。让我详细解释一下情况。
我提供了您可以克隆的a sample example on Github,以便您轻松重现该错误。
上下文
我只有 2 个视图:
PlaylistCreationView
SearchView
他们每个人的视图模型分别称为PlaylistCreationViewModel
和SearchViewModel
。
PlaylistCreationView
有一个Add Songs
按钮,女巫将显示SearchView
有一个sheet
。当用户在搜索结果中触摸一首歌曲时,SearchView
发送一个 onAddSong
完成处理程序,该处理程序将通知 PlaylistCreationView
一首歌曲已添加。
这里有一个简单的情景模式:
这里也是一些视图的截图:
播放列表创建视图
搜索视图
问题
当用户触摸SearchView
中的歌曲时,SearchView
的内容会被重绘。我的意思是视图被重新创建。如果第一次再次加载该视图,则会显示该视图。就像这样:
为了简化问题,这里是另一种情况的模式:
这是我在触摸一首歌后观察到的:
如您所见,歌曲已正确添加到播放列表,但 SearchView
现在为空白,因为它已被重新创建。
我无法解释的是,为什么 PlaylistCreationViewModel
中的列表更改会导致重新绘制呈现的 sheet
。
如何重现问题
我提供了您可以克隆的a sample example on Github。重现问题:
-
克隆项目并在任何 iPhone 模拟器上运行它。
触摸“添加歌曲”。
在搜索视图顶部的搜索栏中输入内容。
选择一首歌曲。您的搜索应该已经消失,因为视图已经重建。
代码
播放列表创建视图模型
final class PlaylistCreationViewModel: ObservableObject
@Published var sheetType: SheetType?
@Published var songs: [String] = []
extension PlaylistCreationViewModel
enum SheetType: String, Identifiable
case search
var id: String
rawValue
播放列表创建视图
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 的东西。但我无法得到它。我以前没有遇到过这样的情况。我在网上找不到任何东西。
任何帮助或建议将不胜感激。提前感谢任何愿意花时间阅读和理解问题的人。我希望我已经足够好地描述了这个问题。
【问题讨论】:
您必须使用 100% 纸张吗?我的意思是为什么不使用 CustomView 然后所有问题都解决了,因为工作表有点错误,即使有人在您的代码中发现问题,也可以理解错误并处理它,也许新的拯救不需要Xcode 或 SwiftUI 的更新。我 99% 确定这个问题与您的代码无关,而与工作表有关。 @swiftPunk 感谢您的回复。我不必使用床单。但这是在 ios 应用程序之间导航的标准方式,对用户体验来说是可观的。如果这是一个错误,我会向 Apple 报告。但我想确定这不是我自己的错误。 什么是工作表? sheet 只不过是一个显示 View 的动画,你可以通过一些编码来做到这一点,如果不想要代码,那么你需要处理 apple bugy sheet。我对床单有很多经验,他们只是没有准备好充分使用, @swiftPunk 使用工作表对我的主要优势在于我正在构建的多平台应用程序。利用组件的简单性会很棒。现在我必须找到另一个解决方案并将错误报告给 Apple。感谢您的建议。 【参考方案1】:问题
当您点击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
的类将刷新其 body 。在您的情况下,该类低于-:
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
,因此您将获得完整的 new Object 搜索字符串为空。下面是那个模型类-:
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
解决方案-:
您可以将addingMode
和sheetViewModel
属性移到sheetView
之外,并将它们放在PlaylistCreationView
中,就在body 属性上方。这样,当刷新工作表时,您的对象就不会每次都从头开始实例化。
工作代码-:
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")
SearchView-:
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)
【讨论】:
感谢您的详细解答。是的,它有效。我必须在.sheet()
上添加onDismiss
参数,以在每次SearchView
被解除时实例化一个新的SearchViewModel
。我不希望视图模型属性更改重新加载所有视图主体。再次感谢以上是关于SwiftUI List 重绘已呈现工作表的内容的主要内容,如果未能解决你的问题,请参考以下文章