如何使用UIPanGestureRecognizer以180度角对CALayer进行3D旋转和3D翻译?

如何像在半球上移动的图层一样使用UIPanGestureRecognizer与180度一起旋转和变换,我尝试做一些事情,可以在所有方向上进行变换,但方向之间的过渡并不平滑>

简而言之,我想成为like in this video

我只是对这些类进行了编码,它适用于所有方向,但结果并不理想:

//
//  MoveCircleToolViewController.swift
//
//  Created by Coder ACJHP on 17.06.2020.
//  Copyright © 2020 Coder ACJHP. All rights reserved.
//

import UIKit

class MoveCircleToolViewController: UIViewController {

    var currentAngleX: CGFloat = 0
    var currentOffsetX: CGFloat = 0
    var currentAngleY: CGFloat = 0
    var currentOffsetY: CGFloat = 0
    var cardSize: CGSize = .zero
    let transformLayer = CATransformLayer()
    var directionsFrames = Array<CGRect>()

    override func viewDidLoad() {
        super.viewDidLoad()

        let panGesture = UIPanGestureRecognizer(target: self,action: #selector(handlePan(_:)))
        view.addGestureRecognizer(panGesture)

        transformLayer.frame = view.bounds
        view.layer.addSublayer(transformLayer)

        /// Add simple CALayer (circle shape)
        addCircleView()

        /** Calculate 4 corners TR,TL,BR,BL and store them in array list
         to use them inside pan gesture event */
        calculateCorners()
    }

    private func degreeToRadians(degree: CGFloat) -> CGFloat {
        return (degree * CGFloat.pi) / 180
    }

    private func addCircleView() {

        let singleSideSize = self.view.bounds.width * 0.18
        cardSize = CGSize(width: singleSideSize,height: singleSideSize)
        let imageLayer = CALayer()
        let origin = CGPoint(x: (view.bounds.width / 2) - (cardSize.width / 2),y: (view.bounds.height / 2) - (cardSize.height / 2))
        imageLayer.frame = CGRect(origin: origin,size: cardSize)

        imageLayer.contentsGravity = .resizeAspectFill
        imageLayer.borderColor = UIColor.cyan.cgColor
        imageLayer.backgroundColor = UIColor.lightGray.withAlphaComponent(0.5).cgColor
        imageLayer.borderWidth = 3.0
        imageLayer.cornerRadius = cardSize.width / 2
        imageLayer.masksToBounds = true
        imageLayer.isDoubleSided = true
        transformLayer.addSublayer(imageLayer)
    }

    private func calculateCorners() {
        let quarterW = self.view.bounds.width / 2
        let quarterH = self.view.bounds.height / 2

        let topLeftRect = CGRect(x: 0,y: 0,width: quarterW - cardSize.width / 2,height: quarterH - cardSize.height / 2)

        let topRightRect = CGRect(x: topLeftRect.width + cardSize.width,height: quarterH - cardSize.height / 2)

        let bottomLeftRect = CGRect(x: 0,y: topLeftRect.height + cardSize.height,height: quarterH - cardSize.height / 2)

        let bottomRightRect = CGRect(x: bottomLeftRect.width + cardSize.height,y: topRightRect.height + cardSize.height,height: quarterH - cardSize.height / 2)

        directionsFrames.append(topLeftRect)
        directionsFrames.append(topRightRect)
        directionsFrames.append(bottomLeftRect)
        directionsFrames.append(bottomRightRect)
    }

    @objc
    private func handlePan(_ gestureRecognier: UIPanGestureRecognizer) {

        let translationPoint = gestureRecognier.translation(in: view)
        let location = gestureRecognier.location(in: view)

        /// Calculate X and Y offset for animation
        let xOffset = gestureRecognier.translation(in: view).x
        let yOffset = gestureRecognier.translation(in: view).y

        /// Reset offsets
        if gestureRecognier.state == .began {
            currentOffsetX = 0
            currentOffsetY = 0
        }

        /// Calculate angle for rotation X
        let xDifference = xOffset * 0.6 - currentOffsetX
        currentOffsetX += xDifference
        currentAngleX += xDifference
        let angleOffsetX = currentAngleX

        /// Calculate angle for rotation Y
        let yDifference = yOffset * 0.6 + currentOffsetY
        currentOffsetY -= yDifference
        currentAngleY -= yDifference
        let angleOffsetY = currentAngleY

        /// Create transform object
        var transform = CATransform3DIdentity
        transform.m34 = -1 / self.view.bounds.width

        // Top Left
        if directionsFrames[0].contains(location) {
            transform = CATransform3DRotate(transform,degreeToRadians(degree: 30),1,0)
            transform = CATransform3DTranslate(transform,translationPoint.x,translationPoint.y,200)

        // Top Right
        } else if directionsFrames[1].contains(location) {
            transform = CATransform3DRotate(transform,200)

        // Bottom Left
        } else if directionsFrames[2].contains(location) {
            transform = CATransform3DRotate(transform,degreeToRadians(degree: -30),200)

        // Bottom Right
        } else if directionsFrames[3].contains(location) {
            transform = CATransform3DRotate(transform,200)

        } else {

            if let direction = gestureRecognier.direction {
                switch direction {
                case .Left,.Right:
                    transform = CATransform3DRotate(transform,degreeToRadians(degree: angleOffsetX),0)
                    transform = CATransform3DTranslate(transform,200)
                case .Up,.Down:
                    transform = CATransform3DRotate(transform,degreeToRadians(degree: angleOffsetY),200)
                }
            }
        }
        CATransaction.setanimationDuration(0)
        transformLayer.transform = transform
    }
}

public extension UIPanGestureRecognizer {

    enum PanDirection: Int {
        case Up,Down,Left,Right
        public var isVertical: Bool { return [.Up,.Down].contains(self) }
        public var isHorizontal: Bool { return !isVertical }
    }

    var direction: PanDirection? {
        let translation = self.translation(in: view)
        let isVertical = abs(translation.y) > abs(translation.x)
        switch (isVertical,translation.x,translation.y) {
        case (true,_,let y) where y < 0: return .Up
        case (true,let y) where y > 0: return .Down
        case (false,let x,_) where x > 0: return .Right
        case (false,_) where x < 0: return .Left
        default: return nil
        }
    }
}

预先感谢

iCMS 回答:如何使用UIPanGestureRecognizer以180度角对CALayer进行3D旋转和3D翻译?

您似乎只允许用户在一个轴或另一个轴上旋转,并且可能使事情复杂化。

给出一些UIView,其中包含我们要旋转的“ primaryView”子视图,以及我们要旋转和在3D空间中向前移动的“ secondaryView”子视图(充当链接的视频中的圆圈),并且给定一些2D点以及该点“在其前面”的距离,我们可以计算从该点到该2D投影点的弧度X和Y欧拉角:

// Note,"self" is some UIView. "primaryView" and "secondaryView"s are subviews of self in the below example:

let somePoint: CGPoint = CGPoint(...)
let distanceInfront: CGFloat = 10

// First we need to convert this point to a coordinate relative to the "center" of what we're trying to orbit around. For your case,this center would be the center of the view itself:

let center = CGPoint(x: frame.size.width / 2,y: frame.size.height / 2)
let offset = CGPoint(x: center.x - somePoint,y: center.y - somePoint)

// If we picture the problem as if we're looking at it "from the side",we're essentially trying to calculate a 2D angle between a horizontal line and some 2D point to obtain the x rotation angle. The point we're trying to calculate the angle to has an x value of our desired distance,and a y value of the calculated offset's y value:

let xP = CGPoint(x: distance,y: offset.y)
let xAngle = atan2(xP.y,xP.x)

// We can do the same to calculate the y angle,picturing the problem "from above":

let yP = CGPoint(x: distance,y: offset.x)
let yAngle = atan2(yP.y,yP.x)

// Now we can use our calculated x and y angles to compute our transform:

var primaryTransform = CATransform3DIdentity
primaryTransform.m34 = 1 / self.bounds.width
primaryTransform = CATransform3DRotate(primaryTransform,yAngle,1,0)
primaryTransform = CATransform3DRotate(primaryTransform,-xAngle,0)
primaryView.layer.transform = primaryTransform

// Our primary view is now "looking at" the 2D point we've provided,at a distance "distance" in front of our view.

// We can then take that same transform and shift it forwards by our desired distance to compute the transform of the secondaryView (the circle view):
let secondaryTransform = CATransform3DTranslate(primaryTransform,-distance)
secondaryView.layer.transform = secondaryTransform

这里是一个Swift Playground,将其包装在一起以演示:

import UIKit
import PlaygroundSupport

class OrbitView: UIView {

    let primaryView = UIView()
    let secondaryView = UIView()

    public init(primaryRadius: CGFloat,secondaryRadius: CGFloat) {
        super.init(frame: .zero)
        primaryView.backgroundColor = .blue
        primaryView.layer.cornerRadius = primaryRadius/2.0
        primaryView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(primaryView)
        NSLayoutConstraint.activate([
            primaryView.centerXAnchor.constraint(equalTo: centerXAnchor),primaryView.centerYAnchor.constraint(equalTo: centerYAnchor),primaryView.widthAnchor.constraint(equalToConstant: primaryRadius),primaryView.heightAnchor.constraint(equalTo: primaryView.widthAnchor),])

        secondaryView.backgroundColor = .red
        secondaryView.layer.cornerRadius = secondaryRadius/2.0
        secondaryView.translatesAutoresizingMaskIntoConstraints = false
        primaryView.addSubview(secondaryView)
        NSLayoutConstraint.activate([
            secondaryView.centerXAnchor.constraint(equalTo: centerXAnchor),secondaryView.centerYAnchor.constraint(equalTo: centerYAnchor),secondaryView.widthAnchor.constraint(equalToConstant: secondaryRadius),secondaryView.heightAnchor.constraint(equalTo: secondaryView.widthAnchor),])
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    public func lookAt(_ location: CGPoint,distanceInfront distance: CGFloat) {

        // Compute how far the location is from the center of our view
        let center = CGPoint(x: frame.size.width / 2,y: frame.size.height / 2)
        let offset = CGPoint(x: center.x - location.x,y: center.y - location.y)

        // Calculate the x angle to the point "infront" of us
        let xP = CGPoint(x: distance,y: offset.y)
        let xAngle = atan2(xP.y,xP.x)

        // Calculate the y angle to the point "infront" of us
        let yP = CGPoint(x: distance,y: offset.x)
        let yAngle = atan2(yP.y,yP.x)


        // Construct a transform that rotates our primary subview's layer to point to the location in 3D space
        var primaryTransform = CATransform3DIdentity
        primaryTransform.m34 = 1 / self.bounds.width
        primaryTransform = CATransform3DRotate(primaryTransform,0)
        primaryTransform = CATransform3DRotate(primaryTransform,0)

        // Set our primary layer's transform
        primaryView.layer.transform = primaryTransform

        // Now,shift this primary transform forward by the distance infront of our view we're "looking",// and apply this transform to our secondary subview
        let secondaryTransform = CATransform3DTranslate(primaryTransform,-distance)
        secondaryView.layer.transform = secondaryTransform

    }

}


class MyViewController : UIViewController {

    let orbitView = OrbitView(primaryRadius: 200,secondaryRadius: 50)

    override func loadView() {
        let view = UIView()
        view.backgroundColor = .white
        self.view = view
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // Construct an orbit view for demonstration purposes,and embed it in our view
        orbitView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(orbitView)
        NSLayoutConstraint.activate([
            orbitView.centerXAnchor.constraint(equalTo: view.centerXAnchor),orbitView.centerYAnchor.constraint(equalTo: view.centerYAnchor),orbitView.widthAnchor.constraint(equalToConstant: 200),orbitView.heightAnchor.constraint(equalTo: orbitView.widthAnchor),])

        // We'll use a pan gesture recognizer to update our orbit view
        let panGesture = UIPanGestureRecognizer(target: self,action: #selector(panRecognized(_:)))
        view.addGestureRecognizer(panGesture)
    }

    @objc private func panRecognized(_ recognizer: UIPanGestureRecognizer) {
        // Tell our orbit view to "look" at a point in 3D space relative to where we are currently touching
        let location = recognizer.location(in: self.orbitView)
        orbitView.lookAt(location,distanceInfront: 100)
    }

}

// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

enter image description here

请注意,在上面的GIF中,当我们第一次开始拖动时,红色圆圈“突然弹出”。为避免这种情况,您可能会想通过将红色圆圈(secondaryView)的变换设置为在首次创建时向前移动所需的距离来开始。

,

@Adam Eisfeld向我展示了正确的计算方式,因此最终我解决了问题,最终代码如下:

//
//  MoveCircleToolViewController.swift
//
//  Created by Coder ACJHP on 17.06.2020.
//  Copyright © 2020 Coder ACJHP. All rights reserved.
//

import UIKit

class MoveCircleToolViewController: UIViewController {

    var cardSize: CGSize = .zero
    let transformLayer = CATransformLayer()

    override func viewDidLoad() {
        super.viewDidLoad()

        let panGesture = UIPanGestureRecognizer(target: self,action: #selector(handlePan(_:)))
        view.addGestureRecognizer(panGesture)

        transformLayer.frame = view.bounds
        view.layer.addSublayer(transformLayer)

        /// Add simple CALayer (circle shape)
        addCircleView()
    }

    private func addCircleView() {

        let singleSideSize = self.view.bounds.width * 0.18
        cardSize = CGSize(width: singleSideSize,height: singleSideSize)
        let imageLayer = CALayer()
        let origin = CGPoint(x: (view.bounds.width / 2) - (cardSize.width / 2),y: (view.bounds.height / 2) - (cardSize.height / 2))
        imageLayer.frame = CGRect(origin: origin,size: cardSize)

        imageLayer.contentsGravity = .resizeAspectFill
        imageLayer.borderColor = UIColor.cyan.cgColor
        imageLayer.backgroundColor = UIColor.lightGray.withAlphaComponent(0.5).cgColor
        imageLayer.borderWidth = 3.0
        imageLayer.cornerRadius = cardSize.width / 2
        imageLayer.masksToBounds = true
        imageLayer.isDoubleSided = true
        transformLayer.addSublayer(imageLayer)
    }

    @objc
    private func handlePan(_ gestureRecognier: UIPanGestureRecognizer) {

        let locationOnView = gestureRecognier.location(in: view)

        let distance: CGFloat = -300
        // Compute how far the location is from the center of our view
        let center = CGPoint(x: view.frame.size.width / 2,y: view.frame.size.height / 2)
        let offset = CGPoint(x: center.x - locationOnView.x,y: center.y - locationOnView.y)

        // Calculate the x angle to the point "infront" of us
        let xP = CGPoint(x: distance,yP.x)

        /// Create transform object
        var transform = CATransform3DIdentity
        transform.m34 = -1 / self.view.bounds.width
        transform = CATransform3DRotate(transform,0)
        transform = CATransform3DRotate(transform,-yAngle,0)
        transform = CATransform3DTranslate(transform,-distance)
        CATransaction.setAnimationDuration(0)
        transformLayer.transform = transform
    }
}
本文链接:https://www.f2er.com/2110580.html

大家都在问