在 UIKit 中将最上面的标签添加到旋转轮的中心

问题描述 投票:0回答:1

我正在用 Swift 语言完成一项作业。我正在尝试实现具有旋转轮视图的功能,并且将多个标签添加为轮子周围的项目。我可以通过向左或向右两个方向滑动手势来旋转旋转视图。我能够实现全部功能。

现在的问题是我希望最上面的标签位于旋转轮箭头的中心。或者换句话说,我想在旋转轮的中心设置任何特定的标签索引。如果我有偶数个项目(如 2 或 4),该功能可以正常工作,但如果项目数为 3 或 5,则面临问题。

我用于在旋转轮中添加箭头的代码是:

var currentRotation: CGFloat = 0.0
var currentIndex = 0.0
var selectedIndex = 0.0

@IBOutlet weak var spinningView: UIView!

private func addLabelsToSpinningView() {
    let labelCount = viewModel.homeDataModel?.data?.result?.count ?? 0
    let angleInterval = CGFloat.pi * 2 / CGFloat(labelCount ) // Divide the circle equally among labels
    let labelWidth: CGFloat = 120
    let labelHeight: CGFloat = 100
    
    let radiusFactor: CGFloat = 0.8 // Adjust this factor to bring labels closer or farther
    for view in self.spinningView.subviews {
        view.removeFromSuperview()
    }

    self.currentIndex = selectedIndex
    self.lastIndex = (self.viewModel.homeDataModel?.data?.result?.count ?? 0) - 1
    self.modeLabels.removeAll()
    for labelIndex in 0..<labelCount {
        let label = UILabel()
        label.text = self.viewModel.homeDataModel?.data?.result?[labelIndex].name
        label.numberOfLines = 0 // Single line of text for horizontal labels
        label.lineBreakMode = .byCharWrapping
        label.textAlignment = .center
        if labelIndex == self.selectedIndex {
            label.font = AppFont.getAppFont(fontName: .manropeExtraBold, fontSize: 20.0)
        } else {
            label.font = AppFont.getAppFont(fontName: .manropeRegular, fontSize: 17.0)
        }
        
        // Calculate the angle for the label
        let labelAngle = CGFloat(labelIndex) * angleInterval + CGFloat.pi / 2  // Start from top radians
        
        // Calculate the position of the label around the spinning view
        let radius = self.spinningView.bounds.width * radiusFactor / 2 // Adjusted radius                
        let labelCenterX = self.spinningView.bounds.midX + radius * cos(labelAngle)
        let labelCenterY = self.spinningView.bounds.midY + radius * sin(labelAngle)
        
        // Create a frame for the label
        let labelFrame = CGRect(x: 0, y: 0, width: labelWidth, height: labelHeight)
        label.frame = labelFrame
        label.center = CGPoint(x: labelCenterX, y: labelCenterY)
        label.clipsToBounds = true
        self.spinningView.addSubview(label)
        self.modeLabels.append(label)
        
        // Rotate the label
        label.transform = CGAffineTransform(rotationAngle: labelAngle + CGFloat.pi / 2) // Offset by pi/2 to make it horizontal
    }
    
    let labelArray: CGFloat = CGFloat((self.viewModel.homeDataModel?.data?.result?.count ?? 0))
    let rotationAngle: CGFloat = ((-CGFloat.pi * 2)/labelArray) * (labelArray/2)
    if self.selectedIndex > 0 && self.selectedIndex <= 2 && self.selectedIndex != 1{
        self.currentRotation = rotationAngle * CGFloat(self.selectedIndex)
    } else if self.selectedIndex > 2  || self.selectedIndex == 1 {
        self.currentRotation = (CGFloat.pi * 2)/labelArray * CGFloat(self.selectedIndex)
    }else {
        self.currentRotation = rotationAngle
    }
    
    debugPrint("rotationAngle on Add labels ------>>>> ", self.currentRotation)
    self.spinningView.transform = CGAffineTransform(rotationAngle: self.currentRotation)
}

这是我目前所取得的成就的图片

我添加了如何向旋转轮添加滑动手势的代码。此外,滑动功能可以在滚轮视图中使用。这种情况是我第一次想要一个默认值作为轮子的起始索引,并且在用户旋转选择的每个索引后,该索引应该位于轮子的顶部中心。

另一种情况是,如果后端未启用顶部选定的功能,滚轮将再次移动最后选定的项目。例如,用户选择第 4 个项目,最后选择的项目是 3。但是第 4 个项目未启用,所以现在滚轮将旋转到最后选择的项目 3,如果用户再次旋转到相同的方向,它应该移动到下一个将是第 5 项的项目。

设置旋转轮的代码并为其添加滑动手势:

private func addWheelView() {
    spinningView.layer.cornerRadius = spinningView.frame.size.height / 2
    let swipeLeftGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(_:)))
    swipeLeftGesture.direction = .left
    swipeGestureview.addGestureRecognizer(swipeLeftGesture)
    let swipeRightGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(_:)))
    swipeRightGesture.direction = .right
    swipeGestureview.addGestureRecognizer(swipeRightGesture)
    addLabelsToSpinningView()
}

旋转轮子代码:

@objc func handleSwipe(_ gesture: UISwipeGestureRecognizer) {
    let labelArray: CGFloat = CGFloat(viewModel.homeDataModel?.data?.result?.count ?? 0)
    let rotationAngle: CGFloat = gesture.direction == .left ? (-CGFloat.pi * 2)/labelArray : (CGFloat.pi * 2)/labelArray
   
        currentRotation += rotationAngle debugPrint("rotationAngle", rotationAngle)
    
    if gesture.direction == .left && currentIndex != lastIndex {
        currentIndex += 1
    }else if gesture.direction == .right && currentIndex != firstIndex {
        currentIndex -= 1
    }else if (gesture.direction == .left ||  gesture.direction == .right) && currentIndex == lastIndex {
        currentIndex = firstIndex
    }else if (gesture.direction == .left ||  gesture.direction == .right) && currentIndex == firstIndex {
        currentIndex = lastIndex
    }
    print("SELECTED INDEX ----->>> \(viewModel.selectedIndex)")
    print("Current INDEX ----->>> \(currentIndex)")
   
    UIView.animate(withDuration: 0.5) {
        self.spinningView.transform = CGAffineTransform(rotationAngle: self.currentRotation)
        self.dashedImageView.transform = CGAffineTransform(rotationAngle: self.currentRotation)
        self.updateMoodLabels(index: self.currentIndex)
    }
}
    
func updateMoodLabels(index: Int) {
    for i in 0..<modeLabels.count {
            if index == i {
                modeLabels[i].font = AppFont.getAppFont(fontName: .manropeExtraBold, fontSize: 20.0)
            } else {
                modeLabels[i].font = AppFont.getAppFont(fontName: .manropeRegular, fontSize: 17.0)
            }
    }
}

如果需要更多详细信息,请告诉我。

ios swift uikit rotation trigonometry
1个回答
0
投票

首先,有几个提示:

  • 这里发生了很多事情。最好询问单个任务,而不是多个任务。
  • 不要使用
    viewModel.homeDataModel
    AppFont.getAppFont
    之类的东西......当我们必须猜测事情时,这会让构建和运行代码变得困难
  • 将代码精简为最小、可重现且可运行的代码。您应该发布一个完整的示例视图控制器,我们可以复制/粘贴/运行。

那么,回答你的第一个问题...

当您“构建”

spinningView
时,请始终添加具有零旋转变换的标签,从顶部(12点钟位置)开始。

然后,如果您想开始,请在顶部说“标签 2”,在您添加并定位(并旋转)各个标签之后,在视图上设置旋转变换。


对于您关于“禁用”项目的问题,请考虑以下逻辑:

    判断Next索引是否启用
    • 如果启用
      • 旋转到下一个索引
    • 如果未启用
      • 如果第一次尝试
        • 旋转到下一个索引,然后
        • “反弹”到当前索引
        • 否则
          • 逐步查找下一个启用的索引
          • 旋转到该索引
    • 然后,根据需要更新当前/选定的索引属性

所以,这里有一些示例代码,您可以复制/粘贴/运行,无需进行任何编辑:

struct MoodStruct { var name: String = "" var enabled: Bool = true } class ExampleViewController: UIViewController { var currentRotation: CGFloat = 0.0 var currentIndex = 0 var selectedIndex = 0 var firstIndex = 0 var lastIndex: Int = 0 // track attempt to spin to disabled mood var firstAttempt: Bool = true let spinningView = UIView() let dashedImageView = UIView() let swipeGestureview = UIView() let sampleData: [String] = [ "Happy", "Sad", "Angry", "Silly", "Lonely", "Excited", "Thrilled", "Meh", ] var moodData: [MoodStruct] = [] var moodLabels: [UILabel] = [] override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemYellow spinningView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(spinningView) dashedImageView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(dashedImageView) swipeGestureview.translatesAutoresizingMaskIntoConstraints = false view.addSubview(swipeGestureview) let g = view.safeAreaLayoutGuide NSLayoutConstraint.activate([ spinningView.centerYAnchor.constraint(equalTo: g.centerYAnchor), spinningView.centerXAnchor.constraint(equalTo: g.centerXAnchor), spinningView.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.9), spinningView.heightAnchor.constraint(equalTo: spinningView.widthAnchor), dashedImageView.centerYAnchor.constraint(equalTo: spinningView.centerYAnchor), dashedImageView.centerXAnchor.constraint(equalTo: spinningView.centerXAnchor), dashedImageView.widthAnchor.constraint(equalTo: spinningView.widthAnchor), dashedImageView.heightAnchor.constraint(equalTo: spinningView.heightAnchor), swipeGestureview.centerYAnchor.constraint(equalTo: spinningView.centerYAnchor), swipeGestureview.centerXAnchor.constraint(equalTo: spinningView.centerXAnchor), swipeGestureview.widthAnchor.constraint(equalTo: spinningView.widthAnchor), swipeGestureview.heightAnchor.constraint(equalTo: spinningView.heightAnchor), ]) spinningView.backgroundColor = UIColor(white: 0.95, alpha: 1.0) // don't set this greater than the "moods" array count let numMoods = 7 moodData = [] for i in 0..<numMoods { let aMood = MoodStruct(name: "\(sampleData[i]) \(i)", enabled: true) moodData.append(aMood) } // let's start with mood 2 selected selectedIndex = 2 // for a test, let's set moods 4 & 5 to disabled moodData[4].enabled = false moodData[5].enabled = false swipeGestureview.isUserInteractionEnabled = true } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) let dashedLayer = CAShapeLayer() dashedLayer.strokeColor = UIColor.systemBlue.cgColor dashedLayer.fillColor = UIColor.clear.cgColor dashedLayer.lineWidth = 2 dashedLayer.lineDashPattern = [8, 8] dashedLayer.path = UIBezierPath(ovalIn: dashedImageView.bounds.insetBy(dx: 75.0, dy: 75.0)).cgPath dashedImageView.layer.addSublayer(dashedLayer) addWheelView() } private func addLabelsToSpinningView() { let labelCount = moodData.count let angleInterval = CGFloat.pi * 2 / CGFloat(labelCount ) // Divide the circle equally among labels let labelWidth: CGFloat = 120 let labelHeight: CGFloat = 100 let radiusFactor: CGFloat = 0.8 // Adjust this factor to bring labels closer or farther for view in self.spinningView.subviews { view.removeFromSuperview() } self.lastIndex = moodData.count - 1 // (self.viewModel.homeDataModel?.data?.result?.count ?? 0) - 1 self.moodLabels.removeAll() for labelIndex in 0..<labelCount { let label = UILabel() label.text = moodData[labelIndex].name label.numberOfLines = 0 // Single line of text for horizontal labels label.lineBreakMode = .byCharWrapping label.textAlignment = .center // Calculate the angle for the label // Start from top radians let labelAngle = CGFloat(labelIndex) * angleInterval - CGFloat.pi / 2 // Calculate the position of the label around the spinning view let radius = self.spinningView.bounds.width * radiusFactor / 2 // Adjusted radius let labelCenterX = self.spinningView.bounds.midX + radius * cos(labelAngle) let labelCenterY = self.spinningView.bounds.midY + radius * sin(labelAngle) // Create a frame for the label let labelFrame = CGRect(x: 0, y: 0, width: labelWidth, height: labelHeight) label.frame = labelFrame label.center = CGPoint(x: labelCenterX, y: labelCenterY) label.clipsToBounds = true self.spinningView.addSubview(label) self.moodLabels.append(label) // Rotate the label label.transform = CGAffineTransform(rotationAngle: labelAngle + CGFloat.pi / 2) // Offset by pi/2 to make it horizontal } // rotate so selectedIndex is at top self.currentIndex = self.selectedIndex self.currentRotation = -(CGFloat(self.selectedIndex) * angleInterval) self.spinningView.transform = CGAffineTransform(rotationAngle: self.currentRotation) self.dashedImageView.transform = CGAffineTransform(rotationAngle: self.currentRotation) debugPrint("rotationAngle on Add labels ------>>>> ", self.currentRotation) } private func addWheelView() { spinningView.layer.cornerRadius = spinningView.frame.size.height / 2 let swipeLeftGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(_:))) swipeLeftGesture.direction = .left swipeGestureview.addGestureRecognizer(swipeLeftGesture) let swipeRightGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(_:))) swipeRightGesture.direction = .right swipeGestureview.addGestureRecognizer(swipeRightGesture) addLabelsToSpinningView() // update the fonts of the mood labels updateMoodLabels(index: self.selectedIndex) } @objc func handleSwipe(_ gesture: UISwipeGestureRecognizer) { let labelCount: CGFloat = CGFloat(moodData.count) var angleInterval = CGFloat.pi * 2 / labelCount // Divide the circle equally among labels var toRot: CGFloat = 0 var bounceBack: Bool = false var toIndex: Int = 0 var numSlots: CGFloat = 0 // determine if the Next index is enabled or not // if not // if firstAttempt // rotate to the Next index, and then // "bounce back" to currentIndex // else // step through to find the next enabled index // rotate to the That index // // update selected / current index properties if gesture.direction == .left { angleInterval *= -1.0 toIndex = currentIndex + 1 if !moodData[toIndex % moodData.count].enabled { if firstAttempt { bounceBack = true firstAttempt = false } else { while !moodData[toIndex % moodData.count].enabled { toIndex += 1 } } } numSlots = CGFloat(toIndex - self.currentIndex) toRot = self.currentRotation + (numSlots * angleInterval) } else { toIndex = currentIndex - 1 if !moodData[abs(toIndex % moodData.count)].enabled { if firstAttempt { bounceBack = true firstAttempt = false } else { while !moodData[abs(toIndex % moodData.count)].enabled { toIndex -= 1 } } } numSlots = CGFloat(toIndex - self.currentIndex) toRot = self.currentRotation - (numSlots * angleInterval) } UIView.animate(withDuration: 0.5 * abs(numSlots), animations: { self.spinningView.transform = CGAffineTransform(rotationAngle: toRot) self.dashedImageView.transform = CGAffineTransform(rotationAngle: toRot) if toIndex < 0 { self.updateMoodLabels(index: self.moodData.count + toIndex) } else { self.updateMoodLabels(index: toIndex % self.moodData.count) } }, completion: { _ in if bounceBack { UIView.animate(withDuration: 0.5, animations: { self.spinningView.transform = CGAffineTransform(rotationAngle: self.currentRotation) self.dashedImageView.transform = CGAffineTransform(rotationAngle: self.currentRotation) self.updateMoodLabels(index: self.currentIndex) }) } else { self.firstAttempt = true self.currentRotation = toRot if toIndex < 0 { toIndex = self.moodData.count + toIndex } self.currentIndex = toIndex % self.moodData.count self.updateMoodLabels(index: self.currentIndex) } }) } func updateMoodLabels(index: Int) { for i in 0..<moodLabels.count { if index == i && moodData[i].enabled { moodLabels[i].font = .systemFont(ofSize: 20.0, weight: .bold) } else { moodLabels[i].font = .systemFont(ofSize: 19.0, weight: .regular) } moodLabels[i].textColor = moodData[i].enabled ? .black : .red } } }


请记住:这只是

示例代码!!!它几乎没有错误检查(对于数组边界之类的东西),并且我尝试保留大部分代码变量/属性 - 尽管我没有花任何时间尝试优化任何东西。

© www.soinside.com 2019 - 2024. All rights reserved.