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

SwiftUI中Scroll/Grid视图下窗口调整触发Sheet模态框反复消失问题

macOS SwiftUI Sheet 模态框反复消失重现问题分析

问题描述

我开发了一款macOS应用,使用ScrollView嵌套LazyVGrid展示单词列表,点击单词会弹出Sheet模态框。点击顶部单词时,调整窗口大小模态框可保持正常;但滚动到列表中部点击单词,调整窗口大小时模态框会反复消失并重现,控制台会打印onDismiss。

问题代码

import SwiftUI

struct SheetIssueView: View {
    let collection: [String] = [
        "apple", "banana", "orange", "grape", "pear", "mango", "cherry", "kiwi", "watermelon", "pineapple",
        "strawberry", "blueberry", "raspberry", "blackberry", "apricot", "peach", "plum", "lemon", "lime", "coconut",
        "pomegranate", "melon", "dragonfruit", "guava", "papaya", "fig", "kiwifruit", "passionfruit", "avocado", "date",
        "grapefruit", "cantaloupe", "honeydew", "mangosteen", "durian", "breadfruit", "ackee", "starfruit", "quince",
        "lychee", "gooseberry", "elderberry", "mulberry", "boysenberry", "cranberry", "lingonberry", "loganberry",
        "persimmon", "tamarind", "kiwano", "cucumber", "tomato", "bell pepper", "zucchini", "eggplant", "pumpkin",
        "squash", "butternut", "acorn", "carrot", "beet", "radish", "turnip", "sweet potato", "potato", "yam", "onion",
        "garlic", "ginger", "celery", "cabbage", "lettuce", "spinach", "kale", "broccoli", "cauliflower", "asparagus",
        "brussels sprouts", "corn", "peas", "green beans", "artichoke", "leek", "shallot", "okra", "bamboo shoots",
        "water chestnuts", "bean sprouts", "jicama", "cassava", "lotus root", "rhubarb", "cactus", "fennel", "salsify",
        "plantain", "yucca", "cattail", "lotus seed", "watercress", "sorrel", "dandelion greens", "parsnip", "sweet corn",
        "gourds", "water caltrop", "chayote", "taro", "burdock root", "arrowhead", "sunchokes"
    ]
    
    private class WordManager: ObservableObject {
        @Published var selectedWord: String?
    }
    
    @StateObject private var wordManager = WordManager()
    
    var body: some View {
        ScrollView {
            LazyVGrid(columns: [GridItem(.adaptive(minimum: 300, maximum: 300), spacing: 0)]) {
                ForEach(collection, id: \.id) { word in
                    Text(word)
                        .frame(width: 300, height: 300)
                        .onTapGesture {
                            self.wordManager.selectedWord = word
                        }
                        .sheet(item: $wordManager.selectedWord,
                            onDismiss: {
                                print("onDismiss")
                            },
                            content: { word in
                                WordDetailView(word: word)
                                    .frame(width: 500, height: 500)
                            })
                }
            }
        }
    }
}

struct WordDetailView: View {
    var word: String
    
    var body: some View {
        Text(word)
    }
}

extension String: Identifiable {
    public var id: String { return self }
}

环境:Xcode 14.3.1,macOS 13.4.1

触发原因

核心问题是Sheet被绑定到了LazyVGrid的单个列表项View上,结合LazyVGrid的特性导致:

  • LazyVGrid会按需创建/销毁列表项View:不在可视区域的项会被销毁,窗口大小调整时,网格布局重新计算,即使原项在可视区域,也可能触发View的销毁重建。
  • 当点击中部单词弹出Sheet后,调整窗口大小会触发原持有Sheet的列表项View被销毁,此时Sheet会随View生命周期结束而关闭,触发onDismiss。
  • 布局完成后,该列表项View被重新创建,由于selectedWord仍有值,新View会再次弹出Sheet,形成"关闭-弹出"的循环。
  • 顶部单词无此问题是因为窗口调整时,顶部列表项View始终处于可视区域且不会被销毁,Sheet的生命周期不受影响。

修复方案

将Sheet从列表项中移出,附加到不会被LazyVGrid布局影响的父View(如ScrollView或根View)上,确保Sheet的生命周期独立于列表项:

修改后的核心代码片段:

var body: some View {
    ScrollView {
        LazyVGrid(columns: [GridItem(.adaptive(minimum: 300, maximum: 300), spacing: 0)]) {
            ForEach(collection, id: \.id) { word in
                Text(word)
                    .frame(width: 300, height: 300)
                    .onTapGesture {
                        self.wordManager.selectedWord = word
                    }
            }
        }
    }
    // 将Sheet移至ScrollView外侧,绑定到根View
    .sheet(item: $wordManager.selectedWord,
           onDismiss: {
               print("onDismiss")
           },
           content: { word in
               WordDetailView(word: word)
                   .frame(width: 500, height: 500)
           })
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 09:22:15