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

SwiftUI:NavigationLink标签内系统图标点击全动画问题排查与解决

问题分析与解决方法

问题原因

你用了一个全局的@State private var tapped变量控制动画状态,List里的每一行收藏图标都绑定了这个变量。只要点击任意一个图标触发tapped值变化,所有行的图标都会响应这个状态变更,所以全部跟着动了。

解决方法

给每个列表项单独维护动画状态,不要用全局变量,以下是两种常用实现方式:

方式一:用字典存储每个项目的动画状态

把全局的tapped改成字典,键为动物名称,值对应该行是否触发动画的状态:

class Favourites: ObservableObject {
    var favAnimals: Set<String> = []
    
    func contains(_ animal: String) -> Bool {
        favAnimals.contains(animal)
    }
    
    func add(_ animal: String) {
        objectWillChange.send()
        favAnimals.insert(animal)
    }
    
    func remove(_ animal: String) {
        objectWillChange.send()
        favAnimals.remove(animal)
    }
    
}

struct ContentView: View {
    let animals = ["Dog", "Cat", "Horse", "Sheep"]
    
    @StateObject var favourites = Favourites()
    // 用字典存储每个动物对应的动画状态
    @State private var tappedStates: [String: Bool] = [:]
    
    var body: some View {
        NavigationView {
            List(animals, id: \.self) { animal in 
                NavigationLink { 
                    Text("Animal Detail View")
                } label: { 
                    HStack {
                        Text(animal)
                        Spacer()
                        Image(systemName: favourites.contains(animal) ? "heart.fill" : "heart")
                            .foregroundColor(favourites.contains(animal) ? .red : .gray)
                            .onTapGesture {
                                // 更新当前动物的动画状态
                                tappedStates[animal] = true
                                if favourites.contains(animal) {
                                    favourites.remove(animal)
                                } else {
                                    favourites.add(animal)
                                }
                                DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
                                    tappedStates[animal] = false
                                }
                            }
                            .scaleEffect(tappedStates[animal, default: false] ? 1.2 : 1)
                            .animation(.spring(response: 0.4, dampingFraction: 0.6), value: tappedStates[animal])
                        
                    }
                }
            }
            .navigationTitle("Animals")
        }
    }
}

方式二:拆分子View,每个子View维护自己的状态

把列表项抽成单独的AnimalRow子视图,每个子视图内部维护专属的tapped状态,实现状态隔离:

class Favourites: ObservableObject {
    var favAnimals: Set<String> = []
    
    func contains(_ animal: String) -> Bool {
        favAnimals.contains(animal)
    }
    
    func add(_ animal: String) {
        objectWillChange.send()
        favAnimals.insert(animal)
    }
    
    func remove(_ animal: String) {
        objectWillChange.send()
        favAnimals.remove(animal)
    }
    
}

struct ContentView: View {
    let animals = ["Dog", "Cat", "Horse", "Sheep"]
    
    @StateObject var favourites = Favourites()
    
    var body: some View {
        NavigationView {
            List(animals, id: \.self) { animal in 
                NavigationLink { 
                    Text("Animal Detail View")
                } label: {
                    AnimalRow(animal: animal, favourites: favourites)
                }
            }
            .navigationTitle("Animals")
        }
    }
}

// 子视图单独维护动画状态
struct AnimalRow: View {
    let animal: String
    @ObservedObject var favourites: Favourites
    @State private var tapped = false
    
    var body: some View {
        HStack {
            Text(animal)
            Spacer()
            Image(systemName: favourites.contains(animal) ? "heart.fill" : "heart")
                .foregroundColor(favourites.contains(animal) ? .red : .gray)
                .onTapGesture {
                    tapped.toggle()
                    if favourites.contains(animal) {
                        favourites.remove(animal)
                    } else {
                        favourites.add(animal)
                    }
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
                        tapped = false
                    }
                }
                .scaleEffect(tapped ? 1.2 : 1)
                .animation(.spring(response: 0.4, dampingFraction: 0.6), value: tapped)
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 07:40:25