You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 13:04:57