SwiftUI 检测视图何时不可见(有点视图会消失)并停止发布者

Posted

技术标签:

【中文标题】SwiftUI 检测视图何时不可见(有点视图会消失)并停止发布者【英文标题】:SwiftUI detect when the view is not visible (kinda View will disappear) and stop publishers 【发布时间】:2021-08-10 22:07:52 【问题描述】:

我正在使用 SwiftUI 和 Firestore 开发应用程序。

在主页上,我显示了 Firestore 中的项目列表,这些项目在后端有任何更新时都会正确更新。 现在在顶部加号按钮上,我正在导航到另一个屏幕以添加新项目。 添加新项目时,主页上的列表会更新,即再次将 AddNewItemView 推送到堆栈中。

在这种情况下,我想检测列表屏幕现在是否不可见并停止列出事件。

import SwiftUI

struct HomeView: View 
    
    private let useCase: AuthLogoutProvider = AuthService.shared
    @ObservedObject var viewModel: HomeViewModel
    
    init(_ viewModel: HomeViewModel) 
        self.viewModel = viewModel
    
    
    private var addTeam: some View 
        return HStack 
            Text("Add Team +")
                .font(.system(size: 20))
                .fontWeight(.regular)
                .foregroundColor(Color.secondary)
                .padding(.vertical)
        
        .frame(width: 150, height: 54)
        .overlay(
            RoundedRectangle(cornerRadius: 10)
                .stroke(Color.secondary, lineWidth: 1)
        )
    
    
    var body: some View 
        NavigationView 
            VStack 
                HStack 
                    NavigationLink(destination: AddTeamInputView(), label: 
                        addTeam
                    )
                    Spacer()
                
                .padding()
                
                List(viewModel.teamsInfoModels, id: \.id)  teamInfoModel in
                    VStack 
                        NavigationLink(destination: TeamDetailView(teamInfoModel), label: 
                            EmptyView()
                        )
                        TeamInfoCell(teamInfoModel)
                    
                
                Spacer()
            
            .navigationTitle( Text("Countries"))
            .navigationBarItems(
                trailing: Button(action: 
                    useCase.signOut()
                    self.viewModel.logoutPerformed()
                ) 
                    Text("Log out")
                        .font(.title2)
                        .fontWeight(.bold)
                        .foregroundColor(.secondary)
                
            )
        
        .onAppear 
            print("HomeView onAppear")
        
        .onDisappear 
            print("HomeView onDisappear")
        
    

这是我的主页视图,其中包含团队列表和添加团队按钮

点击添加新按钮导航到另一个屏幕以在 Firestore 上添加团队

添加团队输入视图

struct AddTeamInputView: View 
    
    @ObservedObject private var viewModel: AddTeamInputViewModel
    
    init(_ viewModel: AddTeamInputViewModel = AddTeamInputViewModel()) 
        self.viewModel = viewModel
        print("AddTeamInputView init called")
    
    
    var body: some View 
        VStack 
            
            HStack 
                TextField("Enter Team name", text: $viewModel.teamName)
                    .font(.title2)
                    .foregroundColor(.secondary)
                    .frame(width: UIScreen.main.bounds.width - 40)
            
            .padding(10)
            .overlay(
                RoundedRectangle(cornerRadius: 10)
                    .stroke(Color.secondary, lineWidth: 1)
            )
            
            if viewModel.move 
                NavigationLink(destination: CountrySelectionView(viewModel: CountrySelectionViewModel(viewModel.createdTeam())), isActive: $viewModel.move) 
                    EmptyView()
                
            
            
            AppButton(action: 
                
                self.viewModel.addTeam()
                
            , title: "Save", width: 200)
            .padding(.vertical, 40)
        
        .navigationTitle("Name Your Team!")
    

添加团队视图模型

class AddTeamInputViewModel: ObservableObject 
    
    init() 
        print("AddTeamInputViewModel init")
    
    
    @Published var teamName: String = ""
    
    @Published var move: Bool = false
    private var team: Team? = nil
        
    func addTeam() 
        TeamDataProvider.shared.createTeam(teamName)  (error, teamId) in
            if let teamId = teamId 
                if let team = TeamDataProvider.shared.team(forId: teamId)  
                    
                    DispatchQueue.main.async 
                        self.team = team
                        print("Move to select country for team == \(self.team?.teamName ?? "") == \(self.move)")
                        self.move.toggle()
                        print("Move Value after toggle == \(self.move)")
                    
                    
                 else 
                    print("Team issue")
                
             else 
                print("Team issue")
            
        
    
    
    func createdTeam() -> Team 
        return team!
    
    
    deinit 
        print("AddTeamInputViewModel deinit called")
    

【问题讨论】:

您没有显示任何代码。假设,您有一些模型来确定将呈现 什么,表示 ListView 的数据为空或 nil。因此,您可能会在“真相之源”(模型)中找到答案。如果没有,请重新考虑您的设计。 如果您的 ListView 被模态视图覆盖,您的模型或 ViewModel 应该知道这一点。如果您的视图被导航堆栈上的另一个视图覆盖,您的模型 (ViewModel) 应该知道这一点。只有少数情况下模型不知道部分覆盖 ListView 的模态视图。在这种情况下,您不应该停止观察事件。 @CouchDeveloper 你能检查给定的代码并提出一些建议吗 我假设您会在 HomeView 中看到团队的更新,因为在 AddTeamInputView 中添加了一个新团队并将其提交给数据提供者。然后从您的 ViewModel 获取团队的 onChange 通知。恕我直言,这就是它应该如何工作的!但是,您可以将 AddTeamInputView 更改为模式视图(工作表),并在那里收集新的团队信息,然后在关闭时,让您的 HomeViewModel 处理“添加新团队”操作。因此,您可能只需要 一个 ViewModel,但需要一个更完整的底层 DataProvider (CRUD) API 和一个为团队提供属性的简单 InputView。 我理解你的意见,但我们对这个流程很严格,因为在添加团队之后还有另一个流程,比如邀请成员加入。这里的流程与您在 HomeView 上观察更改然后在 AddTeamView 上添加新团队的流程相同,该团队为 Homeview 创建另一个事件,然后 HomeView 意外推送 AddTeamView 【参考方案1】:

你应该检查他们的行为,但我认为你正在寻找的是一个 .onAppear 和 .onDisappear 视图修饰符,一旦视图出现并相应地消失,它们就会收到回调 有关更多信息,您可以在以下链接中阅读:

onAppear onDisappear

【讨论】:

onDisappear 仅在从堆栈中删除视图时调用。这里 ListView 没有从堆栈中删除,但另一个视图被推到它的顶部 只有将这些修饰符放在 navigationView 上才是正确的,尝试将这些修饰符放在视图内的某个项目上,例如 List 或 ForEach 实际上内部视图也没有按预期工作。因为每当状态更新时,那些出现和消失的块不会在实际视图上被调用,会出现并消失【参考方案2】:

这不是一个答案,而是一个示例,到目前为止,最小实现没有问题:

import SwiftUI

struct InputView: View 
    @State var name: String
    let submitOnDisappear: Bool
    let onSubmit: (String) -> Void

    init(name: String, onSubmit: @escaping (String) -> Void, submitOnDisappear: Bool = false) 
        self.name = name
        self.submitOnDisappear = submitOnDisappear
        self.onSubmit = onSubmit
    
    var body: some View 
        Form 
            TextField("Name", text: $name)
                .onSubmit 
                    if submitOnDisappear == false 
                        onSubmit(name)
                    
                
        
        .onDisappear 
            if submitOnDisappear 
                onSubmit(name)
            
        
    


struct DetailView: View 
    let detail: String

    var body: some View 
        Text(detail)
    


struct ListView: View 
    let items: [String]
    let addNewItem: (String) -> Void

    enum Sheet: Int, Identifiable 
        case new
        var id: Int  self.rawValue 
    

    @State private var showSheet: Sheet? = nil

    var body: some View 
        VStack 
            Button(action:  showSheet = .new  ) 
                Text("New item via Sheet")
            
            NavigationLink(
                destination: InputView(name: "", onSubmit: addNewItem, submitOnDisappear: true),
                label:  Text("New item via push") 
            )

            List(items, id: \.self)  item in
                NavigationLink(destination: DetailView(detail: item),
                               label:  Text(item)  )
            
        
        .sheet(item: $showSheet)  sheet in
            switch sheet 
            case .new:
                InputView(name: "",
                          onSubmit:  value in
                    showSheet = nil
                    addNewItem(value)
                )
            
        
    


class ViewModel: ObservableObject 
    struct ViewState 
        var items: [String] = []
    

    @Published private(set) var viewState = ViewState()

    init() 
        self.viewState.items = ["John"]
    

    func add(item: String) 
        self.viewState.items.append(item)
    



struct ContentView: View 
    @StateObject var viewModel = ViewModel()

    var body: some View 
        ListView(items: viewModel.viewState.items,
                 addNewItem: viewModel.add(item:))
            .listStyle(.plain)
            .navigationTitle("Items")
    


import PlaygroundSupport
PlaygroundPage.current.setLiveView(
    NavigationView 
        ContentView()
    
    .navigationViewStyle(.stack)
)

通过视图推送向导航堆栈添加新项目不适合我。它看起来和行为都很尴尬。从用户体验的角度来看,它应该是一个模态视图。

【讨论】:

感谢您的努力,但如果您能在 git 上提供帮助,我们将无法更改流程,我们将不胜感激【参考方案3】:

伙计们,我已经通过将 ObservableObject 更改为 StateObject 解决了这个问题,尽管仍然不知道为什么会发生这种情况。 我只是为了依赖注入而这样做

struct AddTeamInputView: View 
    
    @StateObject private var viewModel = AddTeamInputViewModel()
    
    init(_ viewModel) 
        print("AddTeamInputView init called")
    
    
    var body: some View 
        VStack 
            
            HStack 
                TextField("Enter Team name", text: $viewModel.teamName)
                    .font(.title2)
                    .foregroundColor(.secondary)
                    .frame(width: UIScreen.main.bounds.width - 40)
            
            .padding(10)
            .overlay(
                RoundedRectangle(cornerRadius: 10)
                    .stroke(Color.secondary, lineWidth: 1)
            )
            
            if viewModel.move 
                NavigationLink(destination: CountrySelectionView(viewModel: CountrySelectionViewModel(viewModel.createdTeam())), isActive: $viewModel.move) 
                    EmptyView()
                
            
            
            AppButton(action: 
                
                self.viewModel.addTeam()
                
            , title: "Save", width: 200)
            .padding(.vertical, 40)
        
        .navigationTitle("Name Your Team!")
    

【讨论】:

以上是关于SwiftUI 检测视图何时不可见(有点视图会消失)并停止发布者的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI - ScrollView 的宽度为 0,我的内容不可见

如何检测 SwiftUI 视图何时被拖过

如何检测 iPhone MPMoviePlayer 控件何时出现/消失?

SwiftUI Picker 视图在 TabView 中消失

SwiftUI 检测 contextMenu 何时打开

SwiftUI:根据属性值设置视图可见性?