如何在SwiftUI中用.matchedGeometryEffect实现选项填充填空题空白?
SwiftUI 实现选项按钮动画填充填空题空白
核心实现思路
通过**命名空间(Namespace)**关联选项按钮与填空题空白占位视图,配合状态变量控制动画触发逻辑,利用.matchedGeometryEffect实现按钮到空白处的平滑过渡。
步骤1:定义ViewModel与数据结构
基于你的JSON数据结构,先实现数据模型与状态管理的ViewModel:
import SwiftUI // 对应JSON的题目数据模型 struct Question: Codable { let text: String // 格式示例:"This is a _____ question" let options: [String] } class FillBlankViewModel: ObservableObject { @Published var question: Question @Published var selectedOption: String? // 记录选中的选项 @Published var isFilled = false // 标记空白是否已填充 init(question: Question) { self.question = question } // 处理选项点击,触发动画 func selectOption(_ option: String) { withAnimation { selectedOption = option isFilled = true } } // 重置填充状态(可选功能) func reset() { withAnimation { isFilled = false selectedOption = nil } } }
步骤2:主视图实现动画逻辑
在主视图中创建命名空间,关联空白占位与选项按钮,通过状态控制视图显示:
struct FillBlankView: View { @StateObject private var viewModel: FillBlankViewModel @Namespace private var animationNamespace // 拆分题目文本为空白前后的内容 private let questionPrefix: String private let questionSuffix: String init(question: Question) { _viewModel = StateObject(wrappedValue: FillBlankViewModel(question: question)) // 按空白标记"_____"拆分文本 let components = question.text.components(separatedBy: "_____") questionPrefix = components.first ?? "" questionSuffix = components.last ?? "" } var body: some View { VStack(spacing: 30) { // 填空题区域 HStack(spacing: 4) { Text(questionPrefix) // 空白占位/填充后的内容 if viewModel.isFilled, let selected = viewModel.selectedOption { Text(selected) .padding(.horizontal, 8) .padding(.vertical, 4) .background(Color.blue.opacity(0.2)) .cornerRadius(4) .matchedGeometryEffect(id: "fillBlank", in: animationNamespace) } else { // 空白占位视图,与选项按钮用同一id关联 Rectangle() .frame(height: 24) .foregroundColor(.gray.opacity(0.2)) .matchedGeometryEffect(id: "fillBlank", in: animationNamespace) } Text(questionSuffix) } .font(.title) // 选项按钮区域 HStack(spacing: 12) { ForEach(viewModel.question.options, id: \.self) { option in Button(action: { viewModel.selectOption(option) }) { Text(option) .padding(.horizontal, 16) .padding(.vertical, 8) .background(Color.blue) .foregroundColor(.white) .cornerRadius(8) // 选中后隐藏按钮,避免重复显示 .opacity(viewModel.isFilled && viewModel.selectedOption == option ? 0 : 1) .matchedGeometryEffect(id: "fillBlank", in: animationNamespace) } } } // 重置按钮(可选) Button("重置") { viewModel.reset() } .foregroundColor(.red) } .padding() } }
关键细节说明
- 命名空间作用:
@Namespace是动画核心,确保空白占位视图与选项按钮被识别为关联对象,让SwiftUI能计算两者的位置差生成过渡动画。 - 视图匹配规则:两个关联视图必须使用完全相同的
id和命名空间,动画才会生效。 - 状态控制:通过
isFilled和selectedOption控制按钮隐藏与填充文本显示,withAnimation包裹状态更新保证动画平滑。 - 文本拆分:假设题目用
_____作为空白标记,拆分后拼接前缀、填充内容、后缀,保证题目格式完整。
测试示例
用以下代码快速验证功能:
struct ContentView: View { let testQuestion = Question( text: "This is a _____ question", options: ["choice1", "choice2", "choice3", "example"] ) var body: some View { FillBlankView(question: testQuestion) } }
内容的提问来源于stack exchange,提问作者mzm0227
相关产品推荐
相关产品推荐

