SwiftUI自定义视图中状态变更时按钮标签不更新问题
问题:SwiftUI动态生成视图中Toggle按钮状态不更新
我正在开发一款SwiftUI应用,通过模板字符串动态生成视图,模板中的占位符会被替换为SwiftUI视图(如Button)。核心结构是主视图RuleInputContentView包含动态构建器RuleInputExpressionBuilder,负责解析模板字符串并插入对应视图。
目前除TOGGLE按钮外,其他功能均正常工作。点击TOGGLE按钮时,预期标签会在“ON”和“OFF”间切换,但实际标签并未更新,@State属性toggleButton的变更没有触发视图重渲染。
相关代码
struct RuleInputContentView: View { @State private var showPopover = false @State private var toggleButton = false var body: some View { RuleInputExpressionBuilder( pattern: String(localized: "The great toggle {{ONOFF}} and {{TOGGLE}} so let's go."), contents: RuleInputExpressionParameter(label: "ONOFF") { Button("Button") { showPopover.toggle() } .buttonStyle(.link) .popover(isPresented: $showPopover, content: { // TODO: view to switch between on and off }) .buttonStyle(.link) }, RuleInputExpressionParameter(label: "TOGGLE") { Button(action: { toggleButton.toggle() }, label: { if toggleButton { Text("ON") } else { Text("OFF") } }) .buttonStyle(.link) } ) } } // MARK: Expression Parameter struct RuleInputExpressionParameter { let label: String let view: AnyView init<Content: View>(label: String, _ content: () -> Content) { self.label = label self.view = AnyView(content()) } } // MARK: Expression Builder struct RuleInputExpressionBuilder: View { enum Component { case text(String) case view(AnyView) } let parameters: [RuleInputExpressionParameter] let pattern: String @State private var components: [Component] = [] init(pattern: String, contents: RuleInputExpressionParameter...) { self.pattern = pattern self.parameters = contents } var body: some View { /// relevant code base: HStack { buildExpression() } .onAppear { renderPattern() } } private func renderPattern() { components.removeAll() let regex = /\{\{([A-Z]+)\}\}/ var lastEndIndex: String.Index = pattern.startIndex for match in pattern.matches(of: regex) { // string before tag var preMatchString = String(pattern[lastEndIndex..<match.0.startIndex]) preMatchString = preMatchString.trimmingCharacters(in: .whitespaces) components.append(.text(preMatchString)) // matched tag var matchString = String(match.output.1) matchString = matchString.trimmingCharacters(in: .whitespaces) // find corresponding view if let view = parameters.first(where: { $0.label == matchString })?.view { components.append(.view(view)) } lastEndIndex = match.0.endIndex } // potential string after last found tag if lastEndIndex < pattern.endIndex { var postMatchString = String(pattern[lastEndIndex...]) postMatchString = postMatchString.trimmingCharacters(in: .whitespaces) components.append(.text(postMatchString)) } } @ViewBuilder private func buildExpression() -> some View { ForEach(Array(components.enumerated()), id: \.offset) { _, component in switch component { case .text(let string): Text(string) case .view(let view): view } } } } #Preview { RuleInputContentView() .frame(width: 300) .padding() }
解决方案
问题根源
RuleInputExpressionParameter初始化时,直接将闭包返回的视图包装成AnyView并存储为常量。当父视图的toggleButton状态变化时,这个提前创建好的AnyView不会重新生成——它没有绑定到状态的更新流中,自然无法响应状态变化。
方案1:存储视图构建闭包(推荐)
修改参数结构,存储视图的构建闭包而非已创建的视图,这样每次状态变化时都能重新计算生成最新视图:
- 更新
RuleInputExpressionParameter:
struct RuleInputExpressionParameter { let label: String let content: () -> AnyView init<Content: View>(label: String, _ content: @escaping () -> Content) { self.label = label self.content = { AnyView(content()) } } }
- 修改
RuleInputExpressionBuilder的Component枚举:
enum Component { case text(String) case content(() -> AnyView) }
- 更新
renderPattern方法中添加视图的逻辑:
// 替换原有的view添加逻辑 if let param = parameters.first(where: { $0.label == matchString }) { components.append(.content(param.content)) }
- 修改
buildExpression方法,调用闭包生成视图:
@ViewBuilder private func buildExpression() -> some View { ForEach(Array(components.enumerated()), id: \.offset) { _, component in switch component { case .text(let string): Text(string) case .content(let content): content() } } }
方案2:监听状态变化(不推荐)
如果不想修改参数结构,可以让RuleInputExpressionBuilder接收父视图的状态绑定,在状态变化时重新调用renderPattern。但这种方式会让构建器和具体状态耦合,灵活性较差,仅适用于简单场景。
内容的提问来源于stack exchange,提问作者Maschina
相关产品推荐
相关产品推荐

