今天给大家讲一个iOS抽奖的转盘实现,其实,现在这种需求,一般都是由H5来实现,也可能原生的体验会更好一些,反正项目要求,我们非(ji)常(bu)开(qing)心(yuan)地用iOS原生来实现。先去借别人几张图片,开搞。
图层分布:
1–>
rotateView
2–>AwardView
通过图层示例我们可以清楚地看到,每一个奖项对应一个AwardView,然后设置view的transform
就可以实现旋转
let awardView = AwardView(frame: frame)
awardView.layer.anchorPoint = CGPoint(x: 0.5, y: 1)
awardView.center = CGPoint(x: rotateView.bounds.width / 2,
y: rotateView.bounds.width / 2)
awardView.transform = CGAffineTransform(rotationAngle: rotationAngle)
为了实现奖项的高度可配,我们决定购将一个奖项的数据模型(某些抽奖转盘并不是如上图一般均匀等分),所以我们对每个item都设置了开始角度和结束角度
// 展示的奖项模型
struct Lottery: Codable {
let id: Int // 奖品Id
let begin: Float // 开始角度
let end: Float // 结束角度
let title: String // 奖品名称
let image: String // 奖品图片
}
let lotteries = [
Reward.Lottery(id: 1, begin: -22.5, end: 22.5, title: "1234567", image: "qiandao_0000_000"),
Reward.Lottery(id: 2, begin: 22.5, end: 67.5, title: "2345", image: "qiandao_0001_001"),
Reward.Lottery(id: 3, begin: 67.5, end: 112.5, title: "3456", image: "qiandao_0000_000"),
Reward.Lottery(id: 4, begin: 112.5, end: 157.5, title: "4", image: "qiandao_0004_02"),
Reward.Lottery(id: 5, begin: 157.5, end: 201.5, title: "567", image: "qiandao_0000_000"),
Reward.Lottery(id: 6, begin: 201.5, end: 247.5, title: "678", image: "qiandao_0003_01"),
Reward.Lottery(id: 7, begin: 247.5, end: 292.5, title: "789", image: "qiandao_0002_003"),
Reward.Lottery(id: 8, begin: 292.5, end: 337.5, title: "8", image: "qiandao_0000_000")
]
AwardView
视图 private func reloadData() {
layoutIfNeeded()
// 移除子视图
for view in awardsView.subviews {
view.removeFromSuperview()
}
// 添加 awardView 视图
for lottery in lotteries {
let ratio = CGFloat(lottery.end - lottery.begin)/360.0
let angle = (ratio > 0.5 ? 0.5 : ratio) * .pi
let frame = CGRect(x: 0,
y: 0,
width: rotateView.bounds.width / 2 * sin(angle),
height: rotateView.bounds.height / 2)
let awardView = AwardView(frame: frame)
// 设置锚点
awardView.layer.anchorPoint = CGPoint(x: 0.5, y: 1);
awardView.center = CGPoint(x: rotateView.bounds.width / 2,
y: rotateView.bounds.width / 2)
// 填充信息
awardView.set(baseAngle: 270 * .pi / 180, radius: 75)
awardView.set(title: lottery.title, image: lottery.image)
// 设置旋转角度
let rotationAngle = CGFloat((lottery.begin + lottery.end) / 360.0 * .pi )
awardView.transform = CGAffineTransform(rotationAngle: rotationAngle)
awardsView.addSubview(awardView)
}
}
旋转动画很简单,主要使用的是 iOS的核心动画 CABasicAnimation
/// 开始旋转动画
///
/// - Parameters:
/// - num: 转动圈数
/// - awardId: 所中奖品Id
private func startAnimation(num: Int, awardId: Int) {
guard
!isAnimating,
let lottery = lotteries.first(where: { $0.id == awardId }) else {
return
}
let rotationAngle = -CGFloat((lottery.begin + lottery.end) / 360.0 * .pi )
self.rotationAngle = rotationAngle
let animation = CABasicAnimation(keyPath: "transform.rotation.z")
animation.toValue = rotationAngle + 360 * .pi / 180.0 * CGFloat(num)
animation.duration = CFTimeInterval(num) + 0.5
animation.isCumulative = false
animation.delegate = self
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
rotateView.layer.add(animation, forKey: "rotationAnimation")
}
什么是 CABasicAnimation 戳这里
做完以后我们发现一个比较尴尬的事情,因为转盘是个圆形,当奖项描述过长的时候就会很尴尬,如图
所以我们还要做另外一件事,将文字设置为弧形
使用苹果提供的CoreText框架绘制出弧形效果文字,CoreText是由苹果官方提供的文本引擎,它提供了多种控制文字布局的方式,通过使用CoreText框架可以控制文字的位置、颜色、尺寸等。苹果官方提供了一段可以绘制弧形文字的代码,使用起来非常方便,只不过是macOS程序,不适用于Swift,感兴趣的可以了解一下CoreTextArcCocoa
思路:
根据圆弧半径计算出每个字符 character
的位置和旋转角度,通过UIGraphicsGetCurrentContext
绘制到当前View上,具体看我的GitHub,我会在以后文章中详细解释
实现效果如下:
Demo链接: https://github.com/cuixuerui/RewardViewDemo
https://developer.apple.com/library/mac/samplecode/CoreTextArcCocoa/Introduction/Intro.html
http://stackoverflow.com/questions/3841642/curve-text-on-existing-circle/7114184