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

SwiftUI中ScrollView+自定义View+ContextMenu动画故障修复咨询

Fixing SwiftUI Scroll Animation Glitch with TabView, NavigationView, and ContextMenu

Hey there, let's tackle that annoying view disappearance glitch you're seeing when scrolling your CardView list. Combining TabView, NavigationView, ScrollView, and ContextMenu can sometimes trigger weird rendering conflicts in SwiftUI—here are several targeted fixes you can try:

1. Add Unique Identifiers to Your CardViews

SwiftUI relies on view identity to efficiently update and reuse views. When looping over CardViews without a unique id, it might incorrectly reuse or re-render views during scrolling, causing the flicker.

Update your CardView by adding a unique id modifier at the top level:

struct CardVew: View {
    // ... existing properties ...
    
    var body: some View {
        ZStack(alignment: .leading) {
            // ... existing ZStack content ...
        }
        .padding([.leading, .trailing], 12)
        .id("\(title)-\(name)-\(done)") // Add unique ID here
        .sheet(isPresented: $details) {
            // ... sheet content ...
        }
        // ... rest of modifiers ...
    }
    
    // ... computed properties ...
}

Make sure the id combines values that are unique to each card—this helps SwiftUI track each view individually during scroll operations.

2. Restructure TabView and NavigationView Hierarchy

Nesting a single NavigationView inside a TabView can cause layout priority conflicts, especially when scrolling. Instead, wrap each tab's content in its own NavigationStack (or NavigationView for iOS <16):

Before (problematic structure):

TabView {
    NavigationView {
        ScrollView {
            ForEach(yourDataArray) { item in
                CardView(...)
            }
        }
    }
    .tabItem { /* ... */ }
    
    // Other tabs...
}

After (fixed structure):

TabView {
    TabItem {
        Label("Cards", systemImage: "square.stack")
    } content: {
        NavigationStack { // Use NavigationView for iOS <16
            ScrollView {
                ForEach(yourDataArray) { item in
                    CardView(...)
                }
            }
            .navigationTitle("Your Cards")
        }
    }
    
    // Repeat for other tabs, each with its own NavigationStack/View
}

This isolates navigation logic to each tab, reducing cross-view interference during scrolling.

3. Adjust ContextMenu Placement

Attaching a ContextMenu to the entire CardView can sometimes trigger temporary view removal during scroll gestures. Try moving the ContextMenu to a specific subview (like the background RoundedRectangle) instead:

struct CardVew: View {
    // ... existing properties ...
    
    var body: some View {
        ZStack(alignment: .leading) {
            RoundedRectangle(cornerRadius: 15)
                .foregroundColor(Color("cardGray"))
                .opacity(0.24)
                .contextMenu { // Move ContextMenu here
                    Button("Edit") { /* ... */ }
                    Button("Delete") { /* ... */ }
                }
            // ... rest of ZStack content ...
        }
        // ... rest of modifiers ...
    }
}

This limits the ContextMenu's trigger area to the background, reducing the chance of scroll-related rendering conflicts.

4. Fix UIImpactFeedbackGenerator Initialization

Creating a UIImpactFeedbackGenerator directly in the View struct (instead of using SwiftUI's state management) can conflict with view lifecycle events. Update it to use @State and delay initialization:

struct CardVew: View {
    // ... existing properties ...
    @State private var tapVibration: UIImpactFeedbackGenerator? // Change to optional @State
    
    var body: some View {
        ZStack(alignment: .leading) {
            // ... existing content ...
        }
        // ... rest of modifiers ...
        .onAppear {
            DispatchQueue.main.async { // Delay initialization
                tapVibration = UIImpactFeedbackGenerator(style: .light)
                tapVibration?.prepare()
            }
        }
        .onTapGesture {
            tapVibration?.impactOccurred()
            details = true
        }
    }
}

This ensures the feedback generator is initialized after the view has fully appeared, avoiding unnecessary layout triggers.

5. Remove Fixed Width Constraints

Fixed width frames (like .frame(width: 244) in your titleBlock) can force SwiftUI to recalculate layouts during scrolling, leading to flicker. Replace these with flexible width constraints:

private var titleBlock: some View {
    HStack(alignment: .top) {
        Text(title)
            .font(.system(size: 22, weight: .bold))
            .fixedSize(horizontal: false, vertical: true)
            .frame(maxWidth: .infinity, alignment: .leading) // Replace fixed width with maxWidth
            .padding(.bottom, 4)
        if done {
            Spacer()
            Image("done")
                .opacity(0.8)
        }
    }
}

Do the same for the descriptionBlock's fixed width—let the parent view handle sizing based on available space.

Try these fixes one by one (starting with the unique ID and hierarchy restructure, as those are the most common culprits) and see which resolves your glitch.


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 15:57:30