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

SwiftUI中Toggle需点击两次才生效问题排查求助

问题:SwiftUI中Toggle首次点击无响应,需两次切换状态

我在SwiftUI中实现了一个展示书籍的List,每本Book模型包含isFinished属性。用户点击书籍可进入详情页,在详情页通过Toggle切换书籍的isFinished状态。目前遇到的问题是:首次点击Toggle无法改变状态,需点击两次才能完成状态切换。

原始完整代码

struct Book: Hashable, Identifiable {
    let id = UUID()
    let name: String
    var isFinished: Bool = true
}

struct BookCellView: View {
    
    let book: Book
    
    var body: some View {
        HStack {
            Text(book.name)
            Text(book.isFinished ? "Finished": "Not finished")
        }
    }
}

struct BookDetailView: View {
    
    @Binding var book: Book
    @State private var isOn: Bool = false
    
    var body: some View {
        VStack {
            Text(book.name)
            Toggle(isOn: $book.isFinished) {
                Text("Finished")
            }.fixedSize()
        }
    }
}

struct ContentView: View {
    
    @State private var books = [Book(name: "Harry Potter"), Book(name: "Peek Performance"), Book(name: "Ready Player One")]
    
    func bookBinding(id: UUID) -> Binding<Book> {
        
        let index = books.firstIndex(where: { $0.id == id })!
        
        return Binding {
            return books[index]
        } set: {
            books[index] = $0
        }

    }
    
    var body: some View {
        NavigationStack {
            List(books) { book in
                
                NavigationLink(value: book) {
                    BookCellView(book: book)
                }
            }.navigationDestination(for: Book.self) { book in
                BookDetailView(book: bookBinding(id: book.id))
            }
        }
    }
}

更新后的代码

struct ContentView: View {
    
    @State private var books = [Book(name: "Harry Potter"), Book(name: "Peek Performance"), Book(name: "Ready Player One")]
    
    /*
    func bookBinding(id: UUID) -> Binding<Book> {
        
        let index = books.firstIndex(where: { $0.id == id })!
        
        return Binding {
            return books[index]
        } set: {
            books[index] = $0
        }

    } */
    
    var body: some View {
        NavigationStack {
            List(books) { book in
                
                NavigationLink(value: book.id) {
                    BookCellView(book: book)
                }
            }.navigationDestination(for: UUID.self) { id in
                let index = books.firstIndex(where: { $0.id == id })!
                BookDetailView(book: $books[index])
            }
        }
    }
}

问题原因与修复说明

问题根源

原始代码的核心问题在于值类型的传递方式:

  • Book是struct值类型,NavigationLink(value: book)传递的是当前book的副本,而非对原数组元素的引用。
  • 虽然通过bookBinding方法基于原数组索引创建了Binding,但详情页视图初始化时依赖的是传递过来的book副本,导致SwiftUI的状态追踪出现延迟:第一次点击Toggle修改状态后,原数组元素已更新,但详情页视图未及时感知,需要第二次点击才能触发视图刷新完成同步。

修复逻辑

更新后的代码通过两个关键调整解决了问题:

  1. 传递book.id而非book本身,彻底避免值类型副本传递带来的视图同步问题。
  2. 在navigationDestination中直接通过ID定位原数组索引,使用$books[index]创建直接绑定到原@State数组元素的Binding,确保状态修改能立即同步并触发视图更新。

另外,BookDetailView中声明的@State private var isOn: Bool = false是未使用的冗余代码,可安全删除。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 01:30:47