You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI导航列表按钮重复弹出Sheet问题排查与解决咨询

问题

在SwiftUI代码中,从ContentView里点击列表项中的按钮会多次弹出Sheet,关闭Sheet时还会再次触发;但直接预览RowView时点击按钮仅弹出一次。需要实现ContentView中点击按钮仅弹出一次Sheet,避免重复调用接口导致失败。

功能简化描述:一个包含列表项的NavigationStack,每个列表项都有可弹出Sheet的按钮,Sheet弹出时会执行任务调用接口获取数据展示。

点击按钮并关闭Sheet后,控制台输出如下:

Button tapped
Loading
Sheet appeared
Loading
Sheet appeared
Loading
Sheet appeared

尝试过用状态变量记录Sheet是否已打开,仅在首次点击时设置绑定变量为true,但无效;参考类似问题考虑过将Sheet上移,但获取行特定文本存在难度(接触Swift仅2周)。

解决方法

问题根源

  1. RowView被放在NavigationLink的label中,SwiftUI会因列表复用、状态刷新等原因多次创建RowView实例,每个实例都有独立的showingPopover状态,导致点击时多个状态被触发,弹出多个Sheet。
  2. 按钮同时设置了action和onTapGesture,一次点击会触发两次事件处理,加剧重复弹出问题。

修改方案

  1. 移除按钮重复的点击事件处理,仅保留Button自带的action闭包。
  2. 将Sheet的状态管理上移到ContentView,通过绑定传递状态和选中的行文本,确保整个页面只有一个Sheet状态源。

修改后的完整代码

import SwiftUI

struct ContentView: View {
    @State private var showingSheet = false
    @State private var selectedText: String?
    
    var body: some View {
        NavigationStack {
            List {
                NavigationLink {
                    Text("Hello")
                } label: {
                    RowView(textToShow: "Text 1", showingSheet: $showingSheet, selectedText: $selectedText)
                }
                NavigationLink {
                    Text("Hello")
                } label: {
                    RowView(textToShow: "Text 2", showingSheet: $showingSheet, selectedText: $selectedText)
                }
            }
            .sheet(isPresented: $showingSheet) {
                if let text = selectedText {
                    PopupView(textToShow: text)
                        .onAppear {
                            print("Sheet appeared")
                        }
                }
            }
        }
    }
}

struct RowView: View {
    var textToShow: String
    @Binding var showingSheet: Bool
    @Binding var selectedText: String?
    
    var body: some View {
        Text("Hello")
        
        Button(action: {
            print("Button pressed")
            selectedText = textToShow
            showingSheet = true
        }) {
            Image(systemName:"doc.text")
                .imageScale(.large)
        }
    }
}

struct PopupView: View {
    var textToShow: String
    @State private var loading = true
    
    var body: some View {
        VStack {
            if loading {
                HStack {
                    ProgressView()
                    Text("Loading...")
                }
            } else {
                Text(textToShow)
            }
        }
        .onAppear {
            print("Loading")
            loading = false
        }
    }
}

#Preview {
    ContentView()
}

#Preview {
    RowView(textToShow: "Text 1", showingSheet: .constant(false), selectedText: .constant(nil))
}

修改说明

  • 移除onTapGesture:按钮的action闭包已足够处理点击事件,避免一次点击触发多次状态变更。
  • 上移Sheet到ContentView:整个页面仅维护一个showingSheet状态变量,彻底避免多实例导致的重复弹出问题。
  • 绑定传递选中文本:点击按钮时将当前行的文本赋值给selectedText,Sheet根据该值展示对应内容,解决行文本传递的问题。

内容的提问来源于stack exchange,提问作者learnandgrow

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 00:07:36