繁体   English   中英

SwiftUI 列表重绘已提交工作表的内容

[英]SwiftUI List redraw the content of a presented sheet

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

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

语境


我只有2个观点:

  • PlaylistCreationView
  • SearchView

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

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

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

情况

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

播放列表CreationView

播放列表CreationView

搜索视图

搜索视图

问题

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

空白搜索

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

问题模式

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

在此处输入图像描述

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

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

如何重现问题


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

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

代码


播放列表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 的东西。 但我无法得到它。 我以前没有遇到过这样的情况。 我在 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
    }
    
}

解决方案-:

您可以将addingModesheetViewModel属性移到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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM