我正在用 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)
}
}
}
如果需要更多详细信息,请告诉我。
首先,有几个提示:
viewModel.homeDataModel
或 AppFont.getAppFont
之类的东西......当我们必须猜测事情时,这会让构建和运行代码变得困难那么,回答你的第一个问题...
当您“构建”
spinningView
时,请始终添加具有零旋转变换的标签,从顶部(12点钟位置)开始。
然后,如果您想开始,请在顶部说“标签 2”,在您添加并定位(并旋转)各个标签之后,在视图上设置旋转变换。
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
}
}
}
示例代码!!!它几乎没有错误检查(对于数组边界之类的东西),并且我尝试保留大部分代码变量/属性 - 尽管我没有花任何时间尝试优化任何东西。