天天看點

iOS程式設計——Swift實作一個3D可旋轉的立方體(可做菜單頁)

分享一個自己做的3d立方體(在瑞士航空見到過類似的這種首頁),還可以添加以下要素變得更加完善:

1)旋轉過程中每個面的透明度可以更改

2)手勢滑動結束可以添加慣性轉動動畫 

效果如圖:

iOS程式設計——Swift實作一個3D可旋轉的立方體(可做菜單頁)

直接上代碼了 ,直接全粘貼到一個swift檔案裡既可以使用

1.聲明變量 公共函數

import UIKit

class CubeMenuView: UIView {
    
    //立方體四個面
    var sidesArray: [UIImageView]!
    //立方體上面
    var topImageView: UIImageView!
    //立方體下面陰影
    var shadowImageView: UIImageView!
    //立方體每個面的長寬
    var cubeWidth: CGFloat = 0.0
    
    //記錄手勢開始點
    var beginPoint: CGPoint?
    //手勢停止時x軸移動的距離
    var moveX: CGFloat = 0.0
    //每個面移動的角度
    var moveAngle: CGFloat = 0.0
    //每個面目前的角度
    var currentAngle: CGFloat = 0.0
    //最前面一個面的index
    var currentIndex: UInt = 1
    
    //整個立方體沿X軸的傾斜角度
    let kCubeInclinedAngle_Y: CGFloat = -CGFloat(M_PI_4 / 4.0)
    let distanceZ: CGFloat = 1000.0
           
func CATransform3DMakePerspective(center: CGPoint, disZ: CGFloat) -> CATransform3D{
        let transToCenter = CATransform3DMakeTranslation(-center.x, -center.y, 0)
        let transBack = CATransform3DMakeTranslation(center.x, center.y, 0)
        var scale = CATransform3DIdentity
        scale.m34 = -1.0 / disZ
        
        return CATransform3DConcat(CATransform3DConcat(transToCenter, scale), transBack)
    }
    
    func CATransform3DPerspect(t: CATransform3D, center: CGPoint, disZ: CGFloat) -> CATransform3D {
        return CATransform3DConcat(t, CATransform3DMakePerspective(center, disZ: disZ))
    }
    
    
    func randomColor() -> UIColor {
        var h = CGFloat(Double(arc4random() % 256) / 256.0)
        var s = CGFloat(Double(arc4random() % 128) / 256.0) + 0.5
        var b = CGFloat(Double(arc4random() % 128) / 256.0) + 0.5
        
        return UIColor(hue: h, saturation: s, brightness: b, alpha: 1.0)
    }
           

2.初始化

init(frame: CGRect, fourImagesArray: [UIImage?], topImage: UIImage?, shadowImage: UIImage?,  width: CGFloat) {
        
        super.init(frame: frame)

        if (topImage != nil) {
            topImageView = UIImageView(image: topImage)
        } else {
            topImageView = UIImageView()
            topImageView.backgroundColor = randomColor()
        }
        
        if (shadowImage != nil) {
            shadowImageView = UIImageView(image: shadowImage)
        } else {
            shadowImageView = UIImageView()
            shadowImageView.backgroundColor = randomColor()
        }
        
        sidesArray = Array()
        for (var i = 0; i < 4; i++){
            let imgView = UIImageView()
            imgView.backgroundColor = randomColor()
            if fourImagesArray.count == 4 {
                imgView.image = fourImagesArray[i]
            }
            sidesArray.append(imgView)
        }
        
        cubeWidth = width
        moveAngle = 0.0
        currentAngle = 0.0
        currentIndex = 1
        
        addViews()
        showCube()
    }
    
    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
           

3.添加各個view

func addViews(){
        //所有的6個層面開始時都位于中心點
        var frame = CGRectMake(CGRectGetMidX(bounds) - cubeWidth / 2, CGRectGetMidY(bounds) - cubeWidth / 2, cubeWidth, cubeWidth)
        
        topImageView.frame = frame
        addSubview(topImageView!)
        
        for imgView in sidesArray {
            imgView.frame = frame
            addSubview(imgView)
        }
        
        frame.origin.y += cubeWidth / 2 + 30
        shadowImageView.frame = frame
        addSubview(shadowImageView)
        
    }
           

4.旋轉view組合成立方體

//旋轉各個面形成一個立方體
    func showCube() {
       
        let upT = CATransform3DMakeTranslation(0, 0, cubeWidth / 2)
        let downT = CATransform3DMakeTranslation(0, 0, -cubeWidth / 2)
        
        var rotationZ = CATransform3DMakeRotation(-moveAngle, 0, 0, 1)
        var rotationX = CATransform3DMakeRotation(kCubeInclinedAngle_Y + CGFloat(M_PI_2), 1, 0, 0);
        var topRotate = CATransform3DConcat(CATransform3DConcat(rotationZ, upT), CATransform3DConcat(rotationX, downT))
        topImageView.layer.transform = CATransform3DPerspect(topRotate, center: CGPointZero, disZ: distanceZ)
        
        var shadowT = CATransform3DConcat(rotationZ, CATransform3DMakeRotation(kCubeInclinedAngle_Y + CGFloat(M_PI_2 - M_PI_4 / 5), 1, 0, 0))
        shadowImageView.layer.transform = CATransform3DPerspect(shadowT, center: CGPointZero, disZ: distanceZ)


        
        let commonRotationX = CATransform3DMakeRotation(kCubeInclinedAngle_Y, 1, 0, 0)
        var rotation: CATransform3D
        
        for var i = 0; i < sidesArray.count; i++ {
            let imgView = sidesArray[i]
            rotation = CATransform3DMakeRotation(CGFloat(M_PI_2 * Double(i)) + moveAngle, 0, 1, 0)
            var mat = CATransform3DConcat(CATransform3DConcat(upT, rotation), CATransform3DConcat(commonRotationX, downT))
            imgView.layer.transform = CATransform3DPerspect(mat, center: CGPointZero, disZ: distanceZ)
        }
    }
           

5.手勢動作

override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
        let allTouches = event.allTouches()
        if allTouches?.count == 1 {
            moveX = 0
            beginPoint = (touches as NSSet).anyObject()?.locationInView(self)
        }
        
    }
    
    
    override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
        let allTouches = event.allTouches()
        if allTouches?.count == 1 {
            let currentPoint = (touches as NSSet).anyObject()?.locationInView(self)
            moveX = currentPoint!.x - beginPoint!.x
            
            //旋轉360度時置0
            let oneLap = CGFloat(M_PI * 2);
            if (fabs(moveAngle) >= oneLap) {
                moveAngle = 0;
                currentAngle = 0;
            }
            
            
            moveAngle = currentAngle + moveX / 320.0 * CGFloat(M_PI)
            
            showCube()
        }
        
    }
    
    override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
        let allTouches = event.allTouches()
        if allTouches?.count == 1 {
            let currentPoint = (touches as NSSet).anyObject()?.locationInView(self)
            let endX = currentPoint!.x - beginPoint!.x
            
            if fabs(endX) > 0 {
                //轉動
                currentAngle = moveAngle
                
                //顯示正确的一面
                showRightSide()
            } else {
                //點選
                var alertView = UIAlertView(title: "提示", message: "點選了第\(currentIndex)面", delegate: nil, cancelButtonTitle: "确定")
                alertView.show()
            }

        }

    }
           

6.手勢停止後的校正立方體

//根據目前立方體旋轉的角度判斷哪個面應該在最前面
    func showRightSide() {
        let stopAngle = fabs(currentAngle)
        
        if (stopAngle > CGFloat(7 * M_PI_4) || stopAngle <= CGFloat(M_PI_4)) {
            //第一個面
            moveAngle = 0.0
            currentIndex = 1
        } else if (stopAngle > CGFloat(M_PI_4) && stopAngle <= CGFloat(3 * M_PI_4)) {
            if (moveAngle >= 0) {
                //第四個面
                moveAngle = CGFloat(M_PI_2)
                currentIndex = 4
            } else {
                //第二個面
                moveAngle = CGFloat(3 * M_PI / 2)
                currentIndex = 2
            }
        } else if (stopAngle > CGFloat(3 * M_PI_4) && stopAngle <= CGFloat(5 * M_PI_4)){
            //第三個面
            moveAngle = CGFloat(M_PI)
            currentIndex = 3
        } else if (stopAngle > CGFloat(5 * M_PI_4) && stopAngle <= CGFloat(7 * M_PI_4)){
            //第二個面
            moveAngle = CGFloat(3 * M_PI / 2)
            currentIndex = 2
        }
        
        currentAngle = moveAngle
        
        //動畫旋轉立方體
        UIView.animateWithDuration(0.3, animations: { () -> Void in
            self.showCube()
        })
        
    }
           

測試代碼:随意找個view初始化添加即可,用圖檔效果更好

let menuView = CubeMenuView(frame: CGRectMake(10, 100, 300, 500), fourImagesArray: Array(), topImage: nil, shadowImage: nil, width: 180.0)
        view.addSubview(menuView)
           

參考相關資料:http://blog.sina.com.cn/s/blog_51a995b70101mz3q.html

繼續閱讀