Mac Catalyst下Swift自定义分段控件悬停与显示异常问题
自定义分段控件显示异常及悬停问题修复(Mac Catalyst)
问题现象
在Mac Catalyst平台开发自定义分段控件时,出现以下问题:
- 第二个按钮显示为第三个按钮的文本内容
- 第三个按钮完全不可见
- 鼠标悬停在第二个按钮的预期位置时,仍能正常触发悬停效果
问题根源分析
- 多余的中间视图层导致布局偏移:原代码中
setUpView方法创建了额外的中间视图,再将按钮容器holderView添加进去,且holderView的x坐标设为100,若控件本身宽度不足,会导致右侧按钮被裁剪。 - 硬编码宽度的精度问题:按钮宽度使用
163.3333这类浮点数硬编码,容易因布局精度误差导致按钮重叠或偏移。 - Frame布局的不稳定性:按钮内的label使用frame手动布局,在复杂场景下容易出现位置偏移,导致文本显示错位。
修复方案
1. 移除多余中间视图层
直接将holderView添加到自定义控件本身,避免额外的布局层级带来的偏移问题。
2. 动态计算按钮宽度
根据容器宽度平均分配按钮宽度,避免硬编码浮点数的精度问题。
3. 使用AutoLayout布局label
替换手动frame布局,确保label始终居中显示,避免文本错位。
4. 确保控件宽度足够容纳所有按钮
初始化自定义控件时,设置足够的宽度以显示完整的分段按钮。
修改后的完整代码
class MySegmentedControl: UIView { var state = MySegmentedControl_State.unknown var buttons = [MySegmentedControl_ButtonView]() func setUpView(){ // 移除多余中间层,直接添加holderView到当前控件 let holder = holderView() holder.frame = self.bounds // 让容器填满整个控件 self.addSubview(holder) } func holderView() -> UIView{ let view = UIView(frame: self.bounds) view.layer.borderWidth = 5 view.layer.borderColor = UIColor.gray.cgColor view.layer.cornerRadius = view.frame.height / 2 view.layer.masksToBounds = true view.clipsToBounds = true view.backgroundColor = #colorLiteral(red: 0.5741485357, green: 0.5741624236, blue: 0.574154973, alpha: 1) // 动态计算按钮宽度,避免硬编码精度问题 let buttonWidth = view.bounds.width / 3 // 创建三个按钮 let button_1 = MySegmentedControl_ButtonView(text: "One", frame: CGRect(x: 0, y: 0, width: buttonWidth, height: view.bounds.height), type: .one, delegate: self) let button_2 = MySegmentedControl_ButtonView(text: "Two", frame: CGRect(x: buttonWidth, y: 0, width: buttonWidth, height: view.bounds.height), type: .two, delegate: self) let button_3 = MySegmentedControl_ButtonView(text: "Three", frame: CGRect(x: buttonWidth*2, y: 0, width: buttonWidth, height: view.bounds.height), type: .three, delegate: self) buttons.append(button_1); buttons.append(button_2); buttons.append(button_3) view.addSubview(button_1) view.addSubview(button_2) view.addSubview(button_3) return view } class MySegmentedControl_ButtonView: UIView{ private var selected = false private var hovering = false var text = "" private var type: MySegmentedControl_State private var delegate: MySegmentedControl_ButtonView_Delegate private var label = UILabel() func setUpView(){ layer.cornerRadius = frame.height/2 // 使用AutoLayout布局label,确保居中 let label = UILabel() self.label = label label.tintColor = .white label.text = text label.font = label.font.withSize(15) label.translatesAutoresizingMaskIntoConstraints = false addSubview(label) NSLayoutConstraint.activate([ label.centerXAnchor.constraint(equalTo: self.centerXAnchor), label.centerYAnchor.constraint(equalTo: self.centerYAnchor) ]) // 添加悬停手势 let regionizer = UIHoverGestureRecognizer(target: self, action: #selector(didHover(_:))) addGestureRecognizer(regionizer) // 创建点击按钮,明确设置透明背景避免遮挡label let button = UIButton(frame: bounds, primaryAction: UIAction(handler: { [self] action in selected = true delegate.shouldChangeState(to: type) delegate.shouldDeselectOthers(without: text) backgroundColor = hovering ? #colorLiteral(red: 0.7540688515, green: 0.7540867925, blue: 0.7540771365, alpha: 0.9435433103) : #colorLiteral(red: 0.9961533629, green: 0.9931518435, blue: 1, alpha: 0.8) })) button.backgroundColor = .clear addSubview(button) } func deselect(){ self.selected = false let bgColor = hovering ? #colorLiteral(red: 0.7540688515, green: 0.7540867925, blue: 0.7540771365, alpha: 0.15) : #colorLiteral(red: 0.2605174184, green: 0.2605243921, blue: 0.260515637, alpha: 0.15) backgroundColor = bgColor UIView.animate(withDuration: 0.2) {[self]in label.frame.origin.y = 10 label.font = label.font.withSize(12) } } @objc func didHover(_ recognizer: UIHoverGestureRecognizer){ switch recognizer.state{ case .began, .changed: hovering = true let bgColor = selected ? #colorLiteral(red: 0.7540688515, green: 0.7540867925, blue: 0.7540771365, alpha: 0.15) : #colorLiteral(red: 0.2605174184, green: 0.2605243921, blue: 0.260515637, alpha: 0.15) backgroundColor = bgColor UIView.animate(withDuration: 0.2) {[self]in label.frame.origin.y = 10 label.font = label.font.withSize(12) } case .ended: hovering = false let bgColor = selected ? #colorLiteral(red: 0.9961533629, green: 0.9931518435, blue: 1, alpha: 0.2) : .clear backgroundColor = bgColor UIView.animate(withDuration: 0.2) {[self]in label.center.y = center.y label.font = label.font.withSize(15) } default:break } } init(text: String, frame: CGRect, type: MySegmentedControl_State, delegate: MySegmentedControl_ButtonView_Delegate){ self.type = type self.delegate = delegate super.init(frame: frame) self.text = text setUpView() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } } extension MySegmentedControl: MySegmentedControl_ButtonView_Delegate{ func shouldDeselectOthers(without: String) { buttons.forEach { button in if button.text != without { button.deselect() } } } func shouldChangeState(to state: MySegmentedControl_State) { self.state = state } } protocol MySegmentedControl_ButtonView_Delegate{ func shouldDeselectOthers(without: String) func shouldChangeState(to state: MySegmentedControl_State) } enum MySegmentedControl_State: String{ case unknown = "Non specific case avalaible" case one = "One selected" case two = "Two selected" case three = "Three selected" }
使用示例
初始化控件时确保宽度足够容纳所有按钮:
let segmentedControl = MySegmentedControl(frame: CGRect(x: 50, y: 100, width: 490, height: 70)) segmentedControl.setUpView() view.addSubview(segmentedControl)
内容的提问来源于stack exchange,提问作者Michaelll
相关产品推荐
相关产品推荐

