[英]SwiftUI presentationMode: Check if view is presented by sheet
[英]SwiftUI List redraw the content of a presented sheet
当List
内容更改时,我遇到了 SwiftUI 的问题,并显示了.sheet
。 让我详细解释一下情况。
我在 Github 上提供了一个示例示例,您可以克隆该示例,以便轻松重现该错误。
我只有2个观点:
PlaylistCreationView
SearchView
他们每个人都有各自的视图模型,称为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
}
}
}
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 的东西。 但我无法得到它。 我以前没有遇到过这样的情况。 我在 web 上找不到任何东西。
任何帮助或建议将不胜感激。 提前感谢任何愿意花时间阅读和理解问题的人。 我希望我已经足够好地描述了这个问题。
问题
当您在SearchView
中点击 Songs 时,您正在调用一个在Sheet
视图中定义的closure
包块。 下面的代码-:
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 中。 下面是那个类-:
final class PlaylistCreationViewModel: ObservableObject {
@Published var sheetType: SheetType?
@Published var songs: [String] = []
}
如您所见,此 class 是ObservableObject
,并且您的songs
数组是@Published
属性,只要您将歌曲添加到此数组,任何使用此 Z20F35E630DAF44DBFA4C3F68F5399DZ 的@ObservedObject
将刷新其主体。 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
视图,为什么? 因为您的工作表正在从viewModel.sheetType
读取其 state ,其值仍设置为.search
现在,当工作表被调用时,它还会再次创建SearchViewModel
对象,并将其提供给SearchView
,因此您将获得带有空搜索字符串的完整新 Object 。 下面是model类-:
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属性上方。 这样,当刷新工作表时,您的 object 就不会每次都从头开始实例化。
工作代码-:
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)
}
}
}
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.