SwiftUI中any类型Binding转换时赋值报错的问题排查
解决SwiftUI中Binding转换的可变表达式错误
问题重现
定义了以下协议与结构体:
public protocol BlockContent_P { var type: Int { get } } public struct Block<BlockContent_P> { public var content: BlockContent_P } public struct MarkdownContent: BlockContent_P { public var rawContent: String = "" public var type: Int { return 0 } }
尝试为Block<any BlockContent_P>类型的属性创建Binding<String>时,出现错误:Cannot assign to immutable expression of type 'String':
@State var block: Block<any BlockContent_P> = Block(content: MarkdownContent()) // 报错的Binding创建代码 Markdown(text: Binding<String>.init(get: { (block.content as? MarkdownContent)?.rawContent ?? "" }, set: { v in (block.content as? MarkdownContent)?.rawContent = v }))
错误原因
不是SwiftUI的Binding不支持可选类型,核心问题在于:
MarkdownContent是值类型(结构体),通过block.content as? MarkdownContent获取到的是原数据的不可变副本,而非原数据的引用,因此无法直接修改副本的属性。@State包装的值类型,必须通过替换整个实例来完成修改,不能直接修改其内部嵌套的值类型属性(尤其是经过类型擦除的any BlockContent_P)。
解决方案
修改Binding的set闭包,通过创建新的MarkdownContent实例和Block实例,替换原有的@State变量:
修正后的完整代码
import SwiftUI protocol BlockContent_P { var type: Int { get } } struct Block<BlockContent_P> { var content: BlockContent_P } struct MarkdownContent: BlockContent_P { var rawContent: String = "" var type: Int { return 0 } } struct ContentView: View { @State var block: Block<any BlockContent_P> = Block(content: MarkdownContent()) var body: some View { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundColor(.accentColor) Text("Hello, world!") // 修正后的Binding创建 Markdown(text: Binding<String>.init(get: { (block.content as? MarkdownContent)?.rawContent ?? "" }, set: { newValue in // 创建新的MarkdownContent实例,更新rawContent if var markdownContent = block.content as? MarkdownContent { markdownContent.rawContent = newValue // 创建新的Block实例,替换原@State变量 block = Block(content: markdownContent) } })) // 测试修改:添加TextField可以实时看到效果 TextField("输入测试", text: Binding<String>.init(get: { (block.content as? MarkdownContent)?.rawContent ?? "" }, set: { newValue in if var markdownContent = block.content as? MarkdownContent { markdownContent.rawContent = newValue block = Block(content: markdownContent) } })) } .padding() } } struct Markdown: View { @Binding var text: String var body: some View { Text(text) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
关键说明
- 对于
@State包装的值类型,必须通过替换整个实例来触发视图更新,直接修改内部嵌套值类型的属性无法生效(甚至会报错)。 - 当使用
any BlockContent_P进行类型擦除后,无法直接修改其内部属性,必须先向下转型为具体类型,创建新实例后再替换原有的Block实例。
内容的提问来源于stack exchange,提问作者Kan Tao
相关产品推荐
相关产品推荐

