Swift - iOS大转盘抽奖实现原理

今天给大家讲一个iOS抽奖的转盘实现,其实,现在这种需求,一般都是由H5来实现,也可能原生的体验会更好一些,反正项目要求,我们非(ji)常(bu)开(qing)心(yuan)地用iOS原生来实现。先去借别人几张图片,开搞。

UI实现原理

效果展示

我们来看一下效果图:
Swift - iOS大转盘抽奖实现原理_第1张图片

图层分布:

Swift - iOS大转盘抽奖实现原理_第2张图片

Swift - iOS大转盘抽奖实现原理_第3张图片

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)

Lottery模型

为了实现奖项的高度可配,我们决定购将一个奖项的数据模型(某些抽奖转盘并不是如上图一般均匀等分),所以我们对每个item都设置了开始角度和结束角度

	// 展示的奖项模型
    struct Lottery: Codable {
        let id: 	Int         // 奖品Id
        let begin: 	Float   	// 开始角度
        let end: 	Float      	// 结束角度
        let title: 	String   	// 奖品名称
        let image: 	String   	// 奖品图片
    }

添加视图

  1. 请求网络数据
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")
]
  1. 添加视图
    假设我们拿到8条奖项数据,根据数据添加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 戳这里

弧形文字

做完以后我们发现一个比较尴尬的事情,因为转盘是个圆形,当奖项描述过长的时候就会很尴尬,如图
Swift - iOS大转盘抽奖实现原理_第4张图片

所以我们还要做另外一件事,将文字设置为弧形

使用苹果提供的CoreText框架绘制出弧形效果文字,CoreText是由苹果官方提供的文本引擎,它提供了多种控制文字布局的方式,通过使用CoreText框架可以控制文字的位置、颜色、尺寸等。苹果官方提供了一段可以绘制弧形文字的代码,使用起来非常方便,只不过是macOS程序,不适用于Swift,感兴趣的可以了解一下CoreTextArcCocoa
思路:
根据圆弧半径计算出每个字符 character 的位置和旋转角度,通过UIGraphicsGetCurrentContext 绘制到当前View上,具体看我的GitHub,我会在以后文章中详细解释
实现效果如下:
Swift - iOS大转盘抽奖实现原理_第5张图片
Demo链接: https://github.com/cuixuerui/RewardViewDemo

Reference

https://developer.apple.com/library/mac/samplecode/CoreTextArcCocoa/Introduction/Intro.html
http://stackoverflow.com/questions/3841642/curve-text-on-existing-circle/7114184

你可能感兴趣的:(Swift,IOS开发UI进阶,Swift从入门到精通)