【问题标题】:How do you translate and scale a view without the transforms from conflicting with each other?您如何在没有相互冲突的转换的情况下翻译和缩放视图?
【发布时间】:2014-06-18 22:16:24
【问题描述】:

我需要将 UIView 从屏幕中心平移和缩放到右上角。

 _____________________________
|                            |
|                            |
|         _________          |
|        |         |         |    START
|        |         |         |
|        |_________|         |
|                            |
|                            |
|____________________________|


 _____________________________
|                       __   |
|                      |__|  |
|                            |
|                            |    END
|                            |
|                            |
|                            |
|                            |
|____________________________|

我验证了我对比例和平移的个人计算是准确的。但是一旦它们放在一起,它们就会相互冲突。以下代码使视图最终离中心太近。

CGFloat finalPadding = 10.0f;
CGFloat finalScale = 46.0f / 170.0f;
CGFloat finalX = self.view.frame.size.width 
    - self.platformProgressView.frame.size.width * finalScale 
    - finalPadding;
CGFloat finalY = finalPadding;
CGFloat deltaX = finalX - self.platformProgressView.frame.origin.x;
CGFloat deltaY = finalY - self.platformProgressView.frame.origin.y;

[UIView
    animateWithDuration:1.0
    delay:1.0
    options:UIViewAnimationOptionCurveEaseOut
    animations:^(void){
        self.platformProgressView.transform = CGAffineTransformConcat(
            CGAffineTransformMakeTranslation(deltaX, deltaY),
            CGAffineTransformMakeScale(finalScale, finalScale)
        );            
    }
    completion:^(BOOL finished) {
    }
];

最终效果:

 _____________________________
|                            |
|                            |
|                __          |
|               |__|         |
|                            |
|                            |
|                            |
|                            |
|____________________________|

更改乘法的顺序会导致视图偏离屏幕的右边缘。

self.platformProgressView.transform = CGAffineTransformConcat(
     CGAffineTransformMakeScale(finalScale, finalScale),
    CGAffineTransformMakeTranslation(deltaX, deltaY)
);

最终效果(预计):

 _____________________________
|                            |
|                            |    __
|                            |   |__|
|                            |
|                            |
|                            |
|                            |
|                            |
|____________________________|

单独应用它们会导致突然跳跃,最终位置更糟。

self.platformProgressView.transform = CGAffineTransformMakeTranslation(deltaX, deltaY);
self.platformProgressView.transform = CGAffineTransformMakeScale(finalScale, finalScale);

最终效果:

 _____________________________
|                            |
|                            | 
|                            |  
|                            |
|       __                   |
|      |__|                  |
|                            |
|                            |
|____________________________|

【问题讨论】:

  • 我更新了我的答案。我不确定系统是否会通知你,所以我写这篇评论是为了确保你得到一个 ping。

标签: ios cocoa-touch matrix linear-algebra


【解决方案1】:

理解变换

要实现的主要内容是变换的原点是视图矩形的中心点。最好用一个例子来说明这一点。

首先我们翻译视图。 v1 是其原始位置的视图,v2 是其平移位置的视图。 p 是您想要的填充(finalPadding 在您的代码中)。 c 标记视图的中心点。

+--------------------------------+
|                        ^       |
|                        | p     |
|                        v       |
|              +- v2 --------+   |
|              |             |   |
|              |      c      |<->|
|              |             | p |
|              +-------------+   |
|                                |
|                                |
|    +- v1 --------+             |
|    |             |             |
|    |      c      |             |
|    |             |             |
|    +-------------+             |
|                                |
+--------------------------------+

接下来我们缩放视图。 v3 是其缩放位置的视图。请注意 v3 的中心点如何保持不变,而其周围的视图尺寸会缩小。虽然尺寸是正确的,但视图的定位和生成的填充 p' 是错误的。

+--------------------------------+
|                       ^        |
|                       | p'     |
|                       |        |
|                       v        |
|                 +- v3 --+      |
|                 |   c   |<---->|
|                 +-------+  p'  |
|                                |
|                                |
|    +- v1 --------+             |
|    |             |             |
|    |      c      |             |
|    |             |             |
|    +-------------+             |
|                                |
+--------------------------------+

增量计算的修复

既然我们知道了缩放的工作原理,我们需要修复计算平移增量的代码。以下是正确的做法:

CGRect windowFrame = self.window.frame;
CGRect viewFrame = self.platformProgressView.frame;
CGPoint finalCenter = CGPointZero;
finalCenter.x = (windowFrame.size.width
                 - (viewFrame.size.width * finalScale) / 2.0f
                 - finalPadding);
finalCenter.y = (finalPadding
                 + (viewFrame.size.height * finalScale) / 2.0f);
CGPoint viewCenter = self.platformProgressView.center;
CGFloat deltaX = finalCenter.x - viewCenter.x;
CGFloat deltaY = finalCenter.y - viewCenter.y;

变换顺序

最后,正如您自己所指出的,转换与CGAffineTransformConcat 连接的顺序很重要。在您的第一次尝试中,您有序列 1) 变换 + 2) 缩放。结果是比例变换(在序列中稍后出现)会影响为平移变换指定的增量。

这里有 2 个解决方案:您自己的第二次尝试,您可以反转序列,使其变为 1) 缩放 + 2) 变换。或者您使用我在答案的第一版中建议的辅助函数。所以这个

self.platformProgressView.transform = CGAffineTransformConcat(
    CGAffineTransformMakeScale(finalScale, finalScale),
    CGAffineTransformMakeTranslation(deltaX, deltaY)
);

等价于

CGAffineTransform CGAffineTransformMakeScaleTranslate(CGFloat sx, CGFloat sy, CGFloat dx, CGFloat dy)
{
  return CGAffineTransformMake(sx, 0.0f, 0.0f, sy, dx, dy);
}

self.platformProgressView.transform = CGAffineTransformMakeScaleTranslate(finalScale, finalScale, deltaX, deltaY);

另一种解决方案:设置锚点

如果您对中心点作为视图变换的原点不满意,您可以通过设置视图 CALayer 的 anchorPoint 属性来更改此设置。默认锚点位于 0.5/0.5,表示视图矩形的中心。显然,锚点不是坐标而是一种乘数。

如果我们假设锚点位于视图的左上角,那么您对平移增量的原始计算是正确的。所以如果你这样做

self.platformProgressView.layer.anchorPoint = CGPointMake(0.0f, 0.0f)

您可以保留原来的计算。

参考

那里可能有更多信息材料,但我最近观看了 WWDC 2011 视频Understanding UIKit Rendering,它极大地帮助我提高了对边界、中心、变换和框架之间关系的理解。

另外,如果您要更改 CALayer anchorPoint 属性,您应该先阅读CALayer class reference 中的属性文档。

【讨论】:

  • 直接设置缩放和平移矩阵项Link to the source
  • 不幸的是,这仅节省了一行代码,并且不会产生与单独创建两个转换并将它们连接起来的不同结果。是的,我实际上运行了我的代码示例,上面的代码和所有六个矩阵元素(a、b、c、d、tx、ty)都是相同的。
  • 中心解决方案有效。锚点解决方案影响不大。
  • 嗨@herzbube,你能帮我解决我的问题吗,我在过去两天一直被困在这个问题上,请stackoverflow.com/questions/55771275/…
【解决方案2】:

变换适用于所有以前的变换。所以必须考虑到这一点并仔细考虑订单。

这些值需要配置为,如果一个发生,然后是下一个,即使它们被连接到一个转换中。

【讨论】:

  • 我需要同时做这两件事才能获得飞走的效果。
  • 您可以通过将它们与concat 方法组合来创建一个变换,然后应用组合变换。但是转换的计算是一样的,如果每个都完成,然后另一个。
  • 我不明白您的代码与我的代码有何不同。您分配了比例并转换为局部变量,然后将它们的串联分配给第三个局部变量。我的代码只是在一个命令中完成所有操作,而无需创建额外的变量。
  • 实际上并没有什么不同。我在回答这个声明:“我需要同时做这两件事”。这些值需要配置为,如果一个发生,然后是下一个。
  • 我不关注你。你是说x = matrix0 * matrix1 不起作用,而y = matrix0; z = matrix1; x = y * z; 会起作用。
【解决方案3】:

这是一个很好的答案,这里是如何在 Swift 4 中做到这一点

 let containerFrame: CGRect  = self.view.frame
    let viewFrame: CGRect  = self.containerView.frame
    var finalCenter: CGPoint  = CGPoint.zero
    let finalPadding: CGFloat  = 0
    let finalScale: CGFloat  = 2.0 //whatever scale that you want

    finalCenter.x = (containerFrame.size.width - (viewFrame.size.width * finalScale) / 2.0 - finalPadding)
    finalCenter.y = (finalPadding + (viewFrame.size.height * finalScale) / 2.0);

    let containerCenter: CGPoint  = self.containerView.center
    let deltaX: CGFloat  = finalCenter.x - containerCenter.x
    let deltaY: CGFloat  = finalCenter.y - containerCenter.y

    let scale = CGAffineTransform(scaleX: finalScale, y: finalScale)
    //MARK: changing the deltas for positive to negative values will change the origin x and y of the transformed view
    let translation = CGAffineTransform(translationX: -deltaX, y: deltaY)

    self.containerView.transform = scale.concatenating(translation)

【讨论】:

    【解决方案4】:

    旋转和平移:

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
    
        let yPos = scrollView.contentOffset.y
        if yPos < 0 {
            let scaleX = ((yPos * -1) / 200) + 1
            let translateY = yPos / 2
            var commonTransform = CGAffineTransform.identity
            commonTransform = commonTransform.translatedBy(x: 0, y: translateY)
            commonTransform = commonTransform.scaledBy(x: scaleX, y: scaleX)
            self.customView?.transform = commonTransform
        }else{
            self.customView?.transform = CGAffineTransform.identity
        }
    }
    

    【讨论】:

    • 您的问题似乎与本帖无关
    • 好的,去德尔。对不起
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-27
    • 1970-01-01
    • 1970-01-01
    • 2015-05-28
    • 2022-11-18
    • 2023-04-06
    • 2022-11-11
    相关资源
    最近更新 更多