Swift6.3+iOS26:如何为UISegmentedControl选中项添加边框
解决方案
问题根源
iOS 13+ 后UISegmentedControl的内部视图结构发生了变化,直接遍历subviews无法获取到单个分段的视图,导致你遍历到的只有一个容器视图,因此index始终为0。同时直接操作私有视图结构在后续iOS版本中可能出现兼容性问题。
修改后的完整代码
enum DemoAppTab: CaseIterable, CustomStringConvertible { case info case backgrounds case photos case links var description: String { switch self { case .info: return "Info" case .backgrounds: return "Backgrounds" case .photos: return "Photos" case .links: return "Links" } } } // 扩展UISegmentedControl获取分段视图的frame extension UISegmentedControl { func segmentFrame(at index: Int) -> CGRect { guard index >= 0, index < numberOfSegments else { return .zero } // 通过KVC获取私有分段视图数组(注意:私有API存在审核风险,若担心可改用计算方式) let segments = value(forKey: "segments") as? [UIView] return segments?[index].frame ?? .zero } } fileprivate struct CustomTabBar<T: Hashable & Equatable>: UIViewRepresentable { var tint: Color = .gray.opacity(0.15) var items: [T] var titleProvider: (T) -> String @Binding var selectedItem: T func makeUIView(context: Context) -> UISegmentedControl { let titles = items.map { titleProvider($0) } let control = UISegmentedControl(items: titles) let initialIndex = items.firstIndex(of: selectedItem) ?? 0 control.selectedSegmentIndex = initialIndex // 基础样式配置:透明背景、透明选中 tint、移除分隔线 control.backgroundColor = .clear control.selectedSegmentTintColor = .clear control.setDividerImage(UIImage(), forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default) control.apportionsSegmentWidthsByContent = true // 设置文字样式 control.setTitleTextAttributes([ .foregroundColor: UIColor.gray, .font: UIFont.systemFont(ofSize: 14) ], for: .normal) control.setTitleTextAttributes([ .foregroundColor: UIColor.black, .font: UIFont.systemFont(ofSize: 14, weight: .medium) ], for: .selected) control.addTarget(context.coordinator, action: #selector(Coordinator.valueChanged(_:)), for: .valueChanged) DispatchQueue.main.async { applyBorder(to: control) } return control } func updateUIView(_ uiView: UISegmentedControl, context: Context) { let newIndex = items.firstIndex(of: selectedItem) ?? 0 if uiView.selectedSegmentIndex != newIndex { uiView.selectedSegmentIndex = newIndex // 选中项变化时重新绘制边框 applyBorder(to: uiView) } } func makeCoordinator() -> Coordinator { Coordinator(parent: self) } class Coordinator: NSObject { var parent: CustomTabBar<T> init(parent: CustomTabBar<T>) { self.parent = parent } @objc func valueChanged(_ sender: UISegmentedControl) { let newIndex = sender.selectedSegmentIndex if newIndex >= 0 && newIndex < parent.items.count { parent.selectedItem = parent.items[newIndex] } } } private func applyBorder(to control: UISegmentedControl) { // 移除之前的边框视图(避免重复添加) control.subviews.first { $0.tag == 999 }?.removeFromSuperview() let selectedIndex = control.selectedSegmentIndex guard selectedIndex >= 0, selectedIndex < control.numberOfSegments else { return } // 获取选中分段的frame,添加内边距调整边框位置 let segmentFrame = control.segmentFrame(at: selectedIndex).insetBy(dx: 2, dy: 2) let borderView = UIView(frame: segmentFrame) borderView.tag = 999 // 标记用于后续移除 borderView.layer.borderColor = UIColor.gray.cgColor borderView.layer.borderWidth = 1.5 borderView.layer.cornerRadius = 6 borderView.layer.masksToBounds = true borderView.backgroundColor = .clear control.addSubview(borderView) control.bringSubviewToFront(borderView) } } struct CustomSegmentedControlDemo: View { @State private var activeTab: DemoAppTab = .info var body: some View { VStack { Text("Selected: \(activeTab.description)") .padding() CustomTabBar( items: Array(DemoAppTab.allCases), titleProvider: { $0.description }, selectedItem: $activeTab ) .padding(.horizontal) Button("Goto Links") { activeTab = .links } } } } #Preview { CustomSegmentedControlDemo() }
关键修改点
基础样式重置:
- 设置
backgroundColor和selectedSegmentTintColor为透明,移除默认背景和选中填充色 - 调用
setDividerImage移除分段间的分隔线 - 配置正常/选中状态的文字颜色和字体
- 设置
正确获取分段frame:
- 通过扩展添加
segmentFrame(at:)方法,利用KVC获取私有分段视图数组(若担心审核风险,可改用基于内容宽度的计算方式)
- 通过扩展添加
动态添加边框视图:
- 在
applyBorder中先移除旧边框视图,避免重复添加 - 根据选中分段的frame创建自定义边框视图,添加到控件上层并标记tag
- 在
更新时同步边框:
- 在
updateUIView中,当选中项变化时重新调用applyBorder,确保边框跟随选中状态更新
- 在
内容的提问来源于stack exchange,提问作者Codelaby
相关产品推荐
相关产品推荐

