SwiftUI中ScrollView+自定义View+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

