SwiftUI中实现常规文本转粗体动画的更优雅方案探讨
实现SwiftUI文本字重平滑过渡(支持自动换行)
问题背景
UI设计要求按钮按下后文本切换为粗体,但当前用两层Text叠加切换透明度的方案动画过渡不够平滑(比如字符错位),且需要支持文本自动换行,因此逐字符处理的方案不可行。希望找到借助TextKit实现浮点字重平滑过渡的方案,允许使用UIKit/Core Animation技术。
当前实现代码:
Text("Brat") .fontWeight(.bold) .foregroundStyle(Color.black.opacity(isSelected ? 1 : 0)) .overlay(alignment: .center) { Text("Brat") .fontWeight(.regular) .opacity(isSelected ? 0 : 1) } .padding() .id(model.title)
最优实现思路:封装UIKit UILabel做字重插值动画
核心思路是利用UIKit的UILabel(基于TextKit)支持动态字体调整的特性,通过插值浮点型字重值生成过渡字体,配合UIKit的动画系统实现平滑过渡,同时原生支持文本换行。
具体实现步骤
- 封装SwiftUI可复用组件:用
UIViewRepresentable将UILabel包装成SwiftUI视图,绑定一个浮点型的字重控制值(比如0对应常规字重,1对应粗体)。 - 配置UILabel支持换行:设置
numberOfLines = 0让文本自动换行。 - 字重插值与动画:在字重值变化时,通过
UIFontDescriptor动态生成对应字重的字体,用UIKit的动画API实现字体的平滑切换。
代码示例
import SwiftUI struct WeightAnimatableText: UIViewRepresentable { let text: String @Binding var weightProgress: Double // 0 = 常规字重, 1 = 粗体 let baseFont: UIFont func makeUIView(context: Context) -> UILabel { let label = UILabel() label.numberOfLines = 0 label.text = text label.textAlignment = .center // 可根据需求调整对齐方式 return label } func updateUIView(_ uiView: UILabel, context: Context) { // 字重映射:UIFont.Weight的rawValue范围大致是-1(ultraLight)到1(black),这里取0到0.7对应regular到bold let targetWeight = UIFont.Weight(rawValue: weightProgress * 0.7) let fontDescriptor = baseFont.fontDescriptor.addingAttributes([ .traits: [UIFontDescriptor.TraitKey.weight: targetWeight] ]) let animatedFont = UIFont(descriptor: fontDescriptor, size: baseFont.pointSize) // 平滑动画过渡,时长可自定义 UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseInOut) { uiView.font = animatedFont } } } // 使用示例 struct DemoView: View { @State private var isSelected = false var body: some View { WeightAnimatableText( text: "Brat\n这是一段支持换行的测试文本", weightProgress: .constant(isSelected ? 1 : 0), baseFont: UIFont.systemFont(ofSize: 18) ) .padding() .onTapGesture { isSelected.toggle() } } }
方案优势
- 原生支持换行:依赖UILabel的TextKit布局,自动处理文本换行,无需自定义布局逻辑。
- 平滑无错位:直接过渡字体本身,避免了两层文本叠加导致的字符偏移问题。
- 灵活控制:通过浮点值可精确控制字重过渡的进度,支持任意中间状态的动画。
注意事项
- 不同字体的字重
rawValue范围可能有差异,比如部分自定义字体的粗体对应值可能不是0.7,需要根据实际使用的字体调整插值乘数,确保过渡到预期的粗体效果。 - 如果需要更精细的动画控制,可以改用Core Animation的显式动画(比如
CABasicAnimation),直接对UILabel的font属性做动画。
内容的提问来源于stack exchange,提问作者CalebK
相关产品推荐
相关产品推荐

