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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 13:32:31