SwiftUI实现无图片自定义分段控件的最优方案与动画实现方法
实现方案选择&动画实现教程
方案选型结论
优先选你已经在尝试的ScrollView嵌套HStack加Button的方案,理由如下:
- 系统原生的Segmented Control(SwiftUI中Picker的segmented样式)定制化上限极低,要修改底部选中边框、自定义间距、支持多选项横向滚动都需要桥接UIKit做大量重写,维护成本远高于自定义实现
- 自定义HStack方案灵活度拉满,不管是后续调整文字样式、间距、选中动效还是加更多交互都很方便
底部选中边框动画实现
SwiftUI可以非常简单实现你要的选中条跟随动画,核心用matchedGeometryEffect修饰符做共享元素过渡即可,完整可运行的实现代码参考如下:
import SwiftUI struct CustomSegmentedControl: View { // 所有分段选项文本 let segments: [String] // 当前选中索引 @Binding var selectedIndex: Int // 共享动画的命名空间 @Namespace private var animationNamespace var body: some View { ScrollView(.horizontal, showsIndicators: false) { HStack(alignment: .center, spacing: 24) { ForEach(0..<segments.count, id: \.self) { index in Button(action: { // 加动画触发选中条过渡 withAnimation(.easeInOut(duration: 0.25)) { selectedIndex = index } // 选中后自动滚动到对应选项可见区域 UIScrollView.appearance().scrollRectToVisible( CGRect(x: CGFloat(index) * 80, y: 0, width: 80, height: 80), animated: true ) }) { VStack(spacing: 12) { Text(segments[index]) .fontWeight(selectedIndex == index ? .semibold : .regular) .foregroundColor(selectedIndex == index ? .black : .gray) // 选中状态的底部边框 if selectedIndex == index { Rectangle() .fill(.blue) .frame(height: 2) // 绑定id,触发共享动画 .matchedGeometryEffect(id: "selectedIndicator", in: animationNamespace) } else { // 未选中状态的底部占位透明条,保证高度一致 Rectangle() .fill(.clear) .frame(height: 2) } } .padding(.horizontal, 8) } } } .frame(height: 80, alignment: .leading) .padding(.horizontal, 16) } } } // 调用示例 struct ContentView: View { @State private var selected = 0 let testSegments = ["推荐", "关注", "热榜", "科技", "数码", "游戏", "影视", "生活"] var body: some View { CustomSegmentedControl(segments: testSegments, selectedIndex: $selected) } }
优化点说明
- 代码里的间距、颜色、动画时长都可以根据你的设计需求自行调整
- 如果不需要横向滚动,直接去掉外层的ScrollView即可,选中动画不受影响
- 不需要桥接任何UIKit组件,纯SwiftUI实现兼容iOS 14及以上系统
内容的提问来源于stack exchange,提问作者LumoColor27
相关产品推荐
相关产品推荐

