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

SwiftUI矩形填充从下往上动画异常:偏移至中心问题求助

SwiftUI 底部填充动画问题解决

问题描述

使用SwiftUI的颜色视图实现基于数值的内部填充动画,填充高度为totalHeight * ratio。代码功能正常,但动画效果是向中心偏移而非从下往上扩展,多次设置.bottom对齐无效。

原问题代码

SquareOptionView

struct SquareOptionView: View {
    
    let title: String
    @Binding var voteRatio: Double
    let color: Color
    let tapAction: ()->()
    
    var body: some View {
        VStack(spacing: 10.0){
            Text(title)
            Text(
                "\(voteRatio * 100, specifier: "%.2f")%"
            )
        }
        .padding(25.0)
        .frame(maxWidth: .infinity, alignment: .bottom)
        .background{
            color
                .containerRelativeFrame(.vertical, alignment: .bottom) { length, _ in
                    return length * voteRatio
                }
                .animation(.bouncy, value: voteRatio)
                .frame(alignment: .bottom)
        }
        .onTapGesture {
            tapAction()
        }
    }
}

ViewModel与父视图

class SquareOptionsContainerViewModel: ObservableObject{
    
    let firstOptionTitle: String
    let secondOptionTitle: String
    @Published var firstVoteRatio: Double
    @Published var secondVoteRatio: Double
    let firstOptionClickListener: ()->()
    let secondOptionClickListener: ()->()
    
    init(firstOptionTitle: String, secondOptionTitle: String, firstVoteRatio: Double, secondVoteRatio: Double, firstOptionClickListener: @escaping () -> Void, secondOptionClickListener: @escaping () -> Void) {
        self.firstOptionTitle = firstOptionTitle
        self.secondOptionTitle = secondOptionTitle
        self.firstVoteRatio = firstVoteRatio
        self.secondVoteRatio = secondVoteRatio
        self.firstOptionClickListener = firstOptionClickListener
        self.secondOptionClickListener = secondOptionClickListener
    }
}

struct ContentView: View {
    
    @ObservedObject var viewModel: SquareOptionsContainerViewModel

    var body: some View {
        HStack(spacing: 0.0){
            SquareOptionView(title: viewModel.firstOptionTitle, voteRatio: $viewModel.firstVoteRatio, color: .green){
                viewModel.firstVoteRatio = random(min: 0.0, max: 1.0)
            }
            Rectangle()
                .frame(width: 2.0)
                .foregroundColor(
                    .black
                )
            SquareOptionView(title: viewModel.secondOptionTitle, voteRatio: $viewModel.secondVoteRatio, color: .red){
                viewModel.secondVoteRatio = random(min: 0.0, max: 1.0)
            }
        }
        .clipShape(
            RoundedRectangle(cornerSize: .init(width: 10.0, height: 10.0))
        )
        .overlay(
            RoundedRectangle(cornerSize: .init(width: 10.0, height: 10.0))
                .stroke(lineWidth: 2.0)
        )
        .padding(.vertical, 10.0)
    }
    
    func random(min: Double, max: Double) -> Double {
        return random * (max - min) + min
    }
    
    var random: Double {
        return Double(arc4random()) / 0xFFFFFFFF
    }
}

解决方案

问题根源在于containerRelativeFrame的布局逻辑:它生成的视图尺寸变化时,默认以自身中心为基准缩放,而非固定底部对齐。改用GeometryReader获取父容器高度,直接控制颜色视图的高度并固定底部对齐,即可实现从下往上的填充动画。

修改后的SquareOptionView代码

struct SquareOptionView: View {
    
    let title: String
    @Binding var voteRatio: Double
    let color: Color
    let tapAction: ()->()
    
    var body: some View {
        VStack(spacing: 10.0){
            Text(title)
            Text(
                "\(voteRatio * 100, specifier: "%.2f")%"
            )
        }
        .padding(25.0)
        // 让主视图填充整个可用空间,内容底部对齐
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
        .background(
            GeometryReader { geo in
                color
                    // 设置填充高度为父容器高度 * 比例,底部对齐
                    .frame(height: geo.size.height * voteRatio, alignment: .bottom)
                    // 确保颜色视图占满父容器宽度,底部对齐
                    .frame(maxWidth: .infinity, alignment: .bottom)
            }
            // 绑定voteRatio触发动画
            .animation(.bouncy, value: voteRatio)
        )
        .onTapGesture {
            tapAction()
        }
    }
}

效果说明

修改后,颜色视图会以容器底部为起点,随着voteRatio的变化向上扩展高度,动画过程保持底部固定,完全符合从下往上填充的需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 12:17:04