UIKit旋转轮盘指定标签居中异常及按钮控制需求
问题解决与需求实现方案
核心问题:奇数标签对齐异常修复
原代码中初始旋转角度的条件判断逻辑混乱,导致奇数个标签时选中项无法正确对齐顶部中心。统一计算逻辑,让选中索引的标签通过旋转刚好定位到顶部:
初始旋转角度修正
替换addLabelsToSpinningView中复杂的currentRotation计算逻辑,改为:
let labelCount = CGFloat(viewModel.homeDataModel?.data?.result?.count ?? 0) guard labelCount > 0 else { return } // 计算每个标签的间隔角度 let anglePerItem = CGFloat.pi * 2 / labelCount // 初始旋转角度:让选中索引的标签转到顶部(顶部角度为 -CGFloat.pi/2,因为默认0是右侧,逆时针转π/2到顶部) self.currentRotation = -anglePerItem * CGFloat(selectedIndex) - CGFloat.pi/2
这样不管标签数是奇数还是偶数,选中索引的标签都会准确对齐顶部中心。
同时,标签的角度计算也需要调整,确保标签本身的方向正确:
// 标签的基础角度:从顶部开始排列,每个标签间隔anglePerItem let labelAngle = -CGFloat(labelIndex) * anglePerItem - CGFloat.pi/2 // 标签旋转:让文字保持水平,需要抵消自身的角度 label.transform = CGAffineTransform(rotationAngle: labelAngle + CGFloat.pi/2)
额外需求实现
1. 初始加载默认选中索引居中
上面的初始旋转角度修正已经实现了这一点,确保初始时选中索引的标签位于顶部中心位置。
2. 禁用项自动回退与跳过处理
首先,假设后端返回的数据模型包含isEnabled字段标识是否可用,然后:
- 新增
lastValidIndex变量跟踪上一个有效选中项 - 修改滑动处理逻辑,检查当前选中项是否禁用,若是则回退到
lastValidIndex,并且滑动时自动跳过禁用项
修改handleSwipe方法:
@objc func handleSwipe(_ gesture: UISwipeGestureRecognizer) { guard let dataList = viewModel.homeDataModel?.data?.result, !dataList.isEmpty else { return } let labelCount = CGFloat(dataList.count) let anglePerItem = CGFloat.pi * 2 / labelCount let isLeftSwipe = gesture.direction == .left // 计算目标索引 var targetIndex = Int(currentIndex) if isLeftSwipe { targetIndex = (targetIndex + 1) % dataList.count } else { targetIndex = (targetIndex - 1 + dataList.count) % dataList.count } // 查找有效目标索引(跳过禁用项) var validTargetIndex = targetIndex var loopCount = 0 while !dataList[validTargetIndex].isEnabled && loopCount < dataList.count { validTargetIndex = isLeftSwipe ? (validTargetIndex + 1) % dataList.count : (validTargetIndex - 1 + dataList.count) % dataList.count loopCount += 1 } // 更新旋转角度与索引 let indexDelta = validTargetIndex - Int(currentIndex) currentRotation += (isLeftSwipe ? anglePerItem : -anglePerItem) * CGFloat(indexDelta) currentIndex = Double(validTargetIndex) // 验证当前项是否有效 if dataList[Int(currentIndex)].isEnabled { lastValidIndex = Int(currentIndex) } else { // 回退到上一个有效项 currentIndex = Double(lastValidIndex) currentRotation = -anglePerItem * CGFloat(lastValidIndex) - CGFloat.pi/2 } UIView.animate(withDuration: 0.5) { self.spinningView.transform = CGAffineTransform(rotationAngle: self.currentRotation) self.dashedImageView.transform = CGAffineTransform(rotationAngle: self.currentRotation) self.updateMoodLabels(index: Int(self.currentIndex)) } }
同时,在addLabelsToSpinningView初始化时设置lastValidIndex:
// 初始化时找到第一个有效索引作为初始lastValidIndex if let dataList = viewModel.homeDataModel?.data?.result { if dataList[selectedIndex].isEnabled { lastValidIndex = selectedIndex } else { for (index, item) in dataList.enumerated() { if item.isEnabled { lastValidIndex = index currentIndex = Double(index) selectedIndex = index break } } } }
3. 左右按钮控制轮盘切换
补充左按钮的点击事件,直接调用滑动逻辑即可:
@IBAction func didTapLeftButton(_ sender: Any) { let gesture = UISwipeGestureRecognizer() gesture.direction = .left handleSwipe(gesture) } @IBAction func didTapRightButton(_ sender: Any) { let gesture = UISwipeGestureRecognizer() gesture.direction = .right handleSwipe(gesture) }
完整修改后关键代码片段
变量定义
var currentRotation: CGFloat = 0.0 var currentIndex = 0.0 var selectedIndex = 0.0 var lastValidIndex = 0 // 新增:跟踪上一个有效选中项 var firstIndex: Int { 0 } var lastIndex: Int { (viewModel.homeDataModel?.data?.result?.count ?? 0) - 1 } var modeLabels: [UILabel] = [] @IBOutlet weak var spinningView: UIView! @IBOutlet weak var dashedImageView: UIImageView!
addLabelsToSpinningView 修正
private func addLabelsToSpinningView() { guard let dataList = viewModel.homeDataModel?.data?.result, !dataList.isEmpty else { return } let labelCount = dataList.count let anglePerItem = CGFloat.pi * 2 / CGFloat(labelCount) let labelWidth: CGFloat = 120 let labelHeight: CGFloat = 100 let radiusFactor: CGFloat = 0.8 // 清空子视图 spinningView.subviews.forEach { $0.removeFromSuperview() } modeLabels.removeAll() currentIndex = Double(selectedIndex) // 初始化lastValidIndex if dataList[selectedIndex].isEnabled { lastValidIndex = selectedIndex } else { // 查找第一个有效项 for (index, item) in dataList.enumerated() { if item.isEnabled { lastValidIndex = index currentIndex = Double(index) selectedIndex = index break } } } for labelIndex in 0..<labelCount { let label = UILabel() label.text = dataList[labelIndex].name label.numberOfLines = 0 label.lineBreakMode = .byCharWrapping label.textAlignment = .center label.font = labelIndex == Int(currentIndex) ? AppFont.getAppFont(fontName: .manropeExtraBold, fontSize: 20.0) : AppFont.getAppFont(fontName: .manropeRegular, fontSize: 17.0) // 计算标签位置角度:从顶部开始排列 let labelAngle = -CGFloat(labelIndex) * anglePerItem - CGFloat.pi/2 let radius = spinningView.bounds.width * radiusFactor / 2 let labelCenterX = spinningView.bounds.midX + radius * cos(labelAngle) let labelCenterY = spinningView.bounds.midY + radius * sin(labelAngle) label.frame = CGRect(x: 0, y: 0, width: labelWidth, height: labelHeight) label.center = CGPoint(x: labelCenterX, y: labelCenterY) label.clipsToBounds = true spinningView.addSubview(label) modeLabels.append(label) // 旋转标签保持水平 label.transform = CGAffineTransform(rotationAngle: labelAngle + CGFloat.pi/2) } // 计算初始旋转角度,确保选中项在顶部 currentRotation = -anglePerItem * CGFloat(currentIndex) - CGFloat.pi/2 spinningView.transform = CGAffineTransform(rotationAngle: currentRotation) }
内容的提问来源于stack exchange,提问作者Gurmeet kaur
相关产品推荐
相关产品推荐

