SwiftUI List重绘呈现的内容sheet
SwiftUI List redraw the content of a presented sheet
当 List
内容发生变化时,我在使用 SwiftUI 时遇到问题,显示 .sheet
。让我详细说明一下情况。
我提供了 a sample example on Github 你可以克隆,这样你就可以轻松地重现错误。
上下文
我只有 2 次观看:
PlaylistCreationView
SearchView
他们每个人都有各自的视图模型 PlaylistCreationViewModel
和 SearchViewModel
。
PlaylistCreationView
有一个 Add Songs
按钮 将显示 SearchView
有一个 sheet
。当用户触摸搜索结果中的歌曲时,SearchView
发送一个 onAddSong
完成处理程序,通知 PlaylistCreationView
一首歌曲已添加。
这里是一个简单的情况示意图:
这里还有一些观点的截图:
PlaylistCreationView
搜索视图
问题
当用户触摸 SearchView
中的歌曲时,SearchView
内容将被重绘。我的意思是重新创建视图。如果视图是第一次再次加载,则会出现该视图。就像这样:
为了简化问题,这里是另一种情况的模式:
这是我在触摸一首歌后可以观察到的:
如您所见,歌曲已正确添加到播放列表中,但SearchView
现在是空白的,因为它已被重新创建。
我无法解释的是,为什么 PlaylistCreationViewModel
中的列表更改导致重新绘制呈现的 sheet
。
如何重现问题
我提供了 a sample example on Github 你可以克隆。要重现问题:
- 克隆项目并在任何 iPhone 模拟器上 运行 它。
- 触摸“添加歌曲”。
- 在搜索视图顶部的搜索栏中键入内容。
- Select一首歌。您的搜索应该已经消失,因为视图已经重建。
代码
PlaylistCreationViewModel
final class PlaylistCreationViewModel: ObservableObject {
@Published var sheetType: SheetType?
@Published var songs: [String] = []
}
extension PlaylistCreationViewModel {
enum SheetType: String, Identifiable {
case search
var id: String {
rawValue
}
}
}
PlaylistCreationView
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")
}
}
}
SearchViewModel
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 的东西。但我无法得到它。我以前没有遇到过这样的情况。我在网上找不到任何东西。
如有任何帮助或建议,我们将不胜感激。提前感谢任何愿意花时间阅读和理解问题的人。我希望我已经充分描述了问题。
问题
当您点击 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
class 中。下面是 class-:
final class PlaylistCreationViewModel: ObservableObject {
@Published var sheetType: SheetType?
@Published var songs: [String] = []
}
如您所见,class 是 ObservableObject
,而您的 songs
数组是 @Published
属性,只要您将歌曲添加到这个数组,任何使用这个模型并标有 @ObservedObject
的 class 都会刷新它的 body 。在你的情况下 class 如下:
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
视图,为什么?因为您的 sheet 正在从 viewModel.sheetType
读取其状态,其值仍设置为 .search
现在,当 sheet 被调用时,它还会再次创建 SearchViewModel
个对象,并将其交给 SearchView
,所以你得到了完整的 new 对象 与空搜索字符串。下面是模型 class-:
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 属性。这样,当 sheet 刷新时,您的对象不会每次都从头开始实例化。
工作代码-:
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)
}
}
}
}
}
}
当 List
内容发生变化时,我在使用 SwiftUI 时遇到问题,显示 .sheet
。让我详细说明一下情况。
我提供了 a sample example on Github 你可以克隆,这样你就可以轻松地重现错误。
上下文
我只有 2 次观看:
PlaylistCreationView
SearchView
他们每个人都有各自的视图模型 PlaylistCreationViewModel
和 SearchViewModel
。
PlaylistCreationView
有一个 Add Songs
按钮 将显示 SearchView
有一个 sheet
。当用户触摸搜索结果中的歌曲时,SearchView
发送一个 onAddSong
完成处理程序,通知 PlaylistCreationView
一首歌曲已添加。
这里是一个简单的情况示意图:
这里还有一些观点的截图:
PlaylistCreationView
搜索视图
问题
当用户触摸 SearchView
中的歌曲时,SearchView
内容将被重绘。我的意思是重新创建视图。如果视图是第一次再次加载,则会出现该视图。就像这样:
为了简化问题,这里是另一种情况的模式:
这是我在触摸一首歌后可以观察到的:
如您所见,歌曲已正确添加到播放列表中,但SearchView
现在是空白的,因为它已被重新创建。
我无法解释的是,为什么 PlaylistCreationViewModel
中的列表更改导致重新绘制呈现的 sheet
。
如何重现问题
我提供了 a sample example on Github 你可以克隆。要重现问题:
- 克隆项目并在任何 iPhone 模拟器上 运行 它。
- 触摸“添加歌曲”。
- 在搜索视图顶部的搜索栏中键入内容。
- Select一首歌。您的搜索应该已经消失,因为视图已经重建。
代码
PlaylistCreationViewModel
final class PlaylistCreationViewModel: ObservableObject {
@Published var sheetType: SheetType?
@Published var songs: [String] = []
}
extension PlaylistCreationViewModel {
enum SheetType: String, Identifiable {
case search
var id: String {
rawValue
}
}
}
PlaylistCreationView
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")
}
}
}
SearchViewModel
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 的东西。但我无法得到它。我以前没有遇到过这样的情况。我在网上找不到任何东西。
如有任何帮助或建议,我们将不胜感激。提前感谢任何愿意花时间阅读和理解问题的人。我希望我已经充分描述了问题。
问题
当您点击 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
class 中。下面是 class-:
final class PlaylistCreationViewModel: ObservableObject {
@Published var sheetType: SheetType?
@Published var songs: [String] = []
}
如您所见,class 是 ObservableObject
,而您的 songs
数组是 @Published
属性,只要您将歌曲添加到这个数组,任何使用这个模型并标有 @ObservedObject
的 class 都会刷新它的 body 。在你的情况下 class 如下:
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
视图,为什么?因为您的 sheet 正在从 viewModel.sheetType
读取其状态,其值仍设置为 .search
现在,当 sheet 被调用时,它还会再次创建 SearchViewModel
个对象,并将其交给 SearchView
,所以你得到了完整的 new 对象 与空搜索字符串。下面是模型 class-:
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 属性。这样,当 sheet 刷新时,您的对象不会每次都从头开始实例化。
工作代码-:
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)
}
}
}
}
}
}