【问题标题】:How do I apply a perspective transform to a UIView?如何将透视变换应用于 UIView?
【发布时间】:2010-09-25 18:03:05
【问题描述】:

我正在寻找对 UIView 执行透视变换(例如在封面流中看到的)

有人知道这是否可能吗?

我已经使用 CALayer 进行了调查,并浏览了所有实用程序员 Core Animation 播客,但我仍然不清楚如何在 iPhone 上创建这种转换。

非常感谢任何帮助、指针或示例代码 sn-ps!

【问题讨论】:

  • 我不确定这是否适合你,但是当我制作动画时,我发现m14更适合我仅供参考:)
  • 谢谢! - 你给这个值是什么?
  • 通过设置 m14 你实际上是在奇怪地倾斜 X 轴上的视锥。数学是完全有效的,但在一般情况下它会使事情变得有点混乱。
  • 一篇非常非常好的关于CALayer 3D变换和透视的文章,包括对m34字段的透彻讲解,可以在这篇优秀的文章中找到:core-animation-3d-model

标签: ios cocoa-touch uiview core-animation calayer


【解决方案1】:

正如 Ben 所说,您需要使用 UIView's 层,使用 CATransform3D 来执行 layer's rotation。像described here 一样,获得透视工作的技巧是直接访问CATransform3D (m34) 的矩阵cells 之一。矩阵数学从来都不是我的事,所以我无法确切解释为什么会这样,但确实如此。您需要将此值设置为初始变换的负分数,然后将图层旋转变换应用于该值。您还应该能够执行以下操作:

Objective-C

UIView *myView = [[self subviews] objectAtIndex:0];
CALayer *layer = myView.layer;
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform.m34 = 1.0 / -500;
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, 45.0f * M_PI / 180.0f, 0.0f, 1.0f, 0.0f);
layer.transform = rotationAndPerspectiveTransform;

Swift 5.0

if let myView = self.subviews.first {
    let layer = myView.layer
    var rotationAndPerspectiveTransform = CATransform3DIdentity
    rotationAndPerspectiveTransform.m34 = 1.0 / -500
    rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, 45.0 * .pi / 180.0, 0.0, 1.0, 0.0)
    layer.transform = rotationAndPerspectiveTransform
}

它为每次旋转从头开始重建图层变换。

可以在here 中找到一个完整的示例(带有代码),根据 Bill Dudney 的示例,我在几个CALayers 上实现了基于触摸的旋转和缩放。最新版本的程序,在页面的最底部,实现了这种透视操作。代码应该相当简单易读。

您在回复中提到的sublayerTransform 是一种转换,应用于您的UIView's CALayer 的子层。如果您没有任何子图层,请不要担心。我在示例中使用 sublayerTransform 只是因为我正在旋转的一层中包含两个 CALayers

【讨论】:

  • 谢谢布拉德 - 你是明星。 PS:很抱歉您的出色答案迟到了!尼克。
  • 这对我来说效果很好,除了我似乎失去了一半的图像(沿着垂直分割)——有时是 LHS,有时是 RHS。
  • @iPadDeveloper2011 - 奇怪的是,当同一平面上有另一个不透明视图或图层时,您正在尝试旋转视图或图层。从屏幕投影的视图或图层的一半将位于另一个视图下方,因此被隐藏。您可以重新排列视图层次结构以防止这种情况发生,也可以使用 zPosition 属性将前景视图移动到足够高的位置,使其高于切断它的视图。
  • 仅供参考,m34 单元影响透视变换的原因是矩阵中的单元会影响世界空间 Z 值如何映射到剪辑空间 W 值(用于透视投影)。阅读同质变换矩阵以获取更多信息。
  • @uerceg - 你会想把它作为一个单独的问题提出来,最好用图片来说明正在发生的事情以及你想要发生的事情。
【解决方案2】:

您只能使用直接应用于 UIView 的变换属性的核心图形(Quartz,仅限 2D)变换。要在 Coverflow 中获得效果,您必须使用 CATransform3D,它应用于 3-D 空间,因此可以为您提供所需的透视图。您只能将 CATransform3D 应用于图层,而不是视图,因此您必须为此切换到图层。

查看 Xcode 附带的“CovertFlow”示例。它仅适用于 mac(即不适用于 iPhone),但很多概念都可以很好地转移。

【讨论】:

  • 我正在/Developer/Examples 中寻找这个coverflow 示例,没有成功,您在哪里可以找到它?
  • 其实是“CovertFlow”,在/Developer/Examples/Quartz/Core Animation/"
【解决方案3】:

Swift 5.0

func makeTransform(horizontalDegree: CGFloat, verticalDegree: CGFloat, maxVertical: CGFloat,rotateDegree: CGFloat, maxHorizontal: CGFloat) -> CATransform3D {
    var transform = CATransform3DIdentity
           
    transform.m34 = 1 / -500
    
    let xAnchor = (horizontalDegree / (2 * maxHorizontal)) + 0.5
    let yAnchor = (verticalDegree / (-2 * maxVertical)) + 0.5
    let anchor = CGPoint(x: xAnchor, y: yAnchor)
    
    setAnchorPoint(anchorPoint: anchor, forView: self.imgView)
    let hDegree  = (CGFloat(horizontalDegree) * .pi)  / 180
    let vDegree  = (CGFloat(verticalDegree) * .pi)  / 180
    let rDegree  = (CGFloat(rotateDegree) * .pi)  / 180
    transform = CATransform3DRotate(transform, vDegree , 1, 0, 0)
    transform = CATransform3DRotate(transform, hDegree , 0, 1, 0)
    transform = CATransform3DRotate(transform, rDegree , 0, 0, 1)
    
    return transform
}

func setAnchorPoint(anchorPoint: CGPoint, forView view: UIView) {
    var newPoint = CGPoint(x: view.bounds.size.width * anchorPoint.x, y: view.bounds.size.height * anchorPoint.y)
    var oldPoint = CGPoint(x: view.bounds.size.width * view.layer.anchorPoint.x, y: view.bounds.size.height * view.layer.anchorPoint.y)
    
    newPoint = newPoint.applying(view.transform)
    oldPoint = oldPoint.applying(view.transform)
    
    var position = view.layer.position
    position.x -= oldPoint.x
    position.x += newPoint.x
    
    position.y -= oldPoint.y
    position.y += newPoint.y
    
    print("Anchor: \(anchorPoint)")
    
    view.layer.position = position
    view.layer.anchorPoint = anchorPoint
}

您只需要使用您的学位调用该函数。例如:

var transform = makeTransform(horizontalDegree: 20.0 , verticalDegree: 25.0, maxVertical: 25, rotateDegree: 20, maxHorizontal: 25)
imgView.layer.transform = transform

【讨论】:

    【解决方案4】:

    您可以使用 iCarousel SDK 获得准确的轮播效果。

    您可以通过使用奇妙且免费的 iCarousel 库在 iOS 上获得即时的 Cover Flow 效果。你可以从https://github.com/nicklockwood/iCarousel 下载它,然后通过添加一个桥接头(它是用Objective-C 编写的)很容易地把它放到你的Xcode 项目中。

    如果您之前没有将 Objective-C 代码添加到 Swift 项目中,请按照以下步骤操作:

    • 下载 iCarousel 并解压
    • 进入您解压缩的文件夹,打开其 iCarousel 子文件夹,然后选择 iCarousel.h 和 iCarousel.m 并将它们拖到您的项目导航中 - 这是 Xcode 中的左侧窗格。就在 Info.plist 下面就可以了。
    • 选中“如果需要复制项目”,然后单击完成。
    • Xcode 将提示您消息“您想配置一个 Objective-C 桥接头吗?”点击“创建桥接头” 您应该会在项目中看到一个名为 YourProjectName-Bridging-Header.h 的新文件。
    • 将此行添加到文件中:#import "iCarousel.h"
    • 将 iCarousel 添加到项目后,您就可以开始使用它了。
    • 确保您同时遵守 iCarouselDelegate 和 iCarouselDataSource 协议。

    Swift 3 示例代码:

        override func viewDidLoad() {
          super.viewDidLoad()
          let carousel = iCarousel(frame: CGRect(x: 0, y: 0, width: 300, height: 200))
          carousel.dataSource = self
          carousel.type = .coverFlow
          view.addSubview(carousel) 
        }
    
       func numberOfItems(in carousel: iCarousel) -> Int {
            return 10
        }
    
        func carousel(_ carousel: iCarousel, viewForItemAt index: Int, reusing view: UIView?) -> UIView {
            let imageView: UIImageView
    
            if view != nil {
                imageView = view as! UIImageView
            } else {
                imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 128, height: 128))
            }
    
            imageView.image = UIImage(named: "example")
    
            return imageView
        }
    

    【讨论】:

      猜你喜欢
      • 2023-04-06
      • 1970-01-01
      • 2016-08-13
      • 2012-06-07
      • 1970-01-01
      • 2014-08-13
      • 2019-10-27
      相关资源
      最近更新 更多