ios - 同步不同坐标空间中的超 View 和单个 View 的变换矩阵

标签 ios math matrix uiview transform

给定以下 View 层次结构:

root (e.g. view of a view controller)
  |_superview: A view where we will draw a cross using core graphics
    |_container: Clips subview
      |_subview: A view where we will show a cross adding subviews, which has to align perfectly with the cross drawn in superview
        |_horizontal line of cross
        |_vertical line of cross

任务:
superview的十字架和 subview给定全局变换,必须始终对齐。 “要求”部分中的更多详细信息。

上下文:

上面的 View 层次结构属于一个图表。为了提供最大的灵活性,它允许以 3 种不同的方式呈现图表点和相关内容:
  • 在图表的基本 View 中绘制 ( superview ) draw方法。
  • 将 subview 添加到 subview . subview在缩放/平移时转换,并自动转换其 subview 。
  • 将 subview 添加到 subview 的兄弟 View .为简单起见,不在 View 层次结构中显示,因为它与问题无关。仅在此处提及以进行概述。这种方法和2.的区别在于,这里的 View 没有转换,所以留给内容的实现来“手动”更新所有子项的转换。

  • 最大的灵活性!但是随之而来的代价是实现起来有点棘手。具体点2。

    目前,我通过基本处理 superview 的变换来进行缩放/平移工作。核心图形绘制和subview分开,但这会导致冗余和容易出错,例如边界检查等的重复代码。

    所以现在我试图重构它以使用一个全局矩阵来存储所有变换并从中导出所有内容。将全局矩阵应用于 superview 使用的坐标绘制是微不足道的,但推导出 subview 的矩阵,鉴于下一节中列出的要求,不是那么多。

    我在 View 层次结构部分提到“交叉”,因为这是我在我的操场中使用的作为一个图表点的简化表示(带有 x/y 指南)(您可以向下滚动查看图像和要点)。

    要求:
  • 内容可以缩放和平移。
  • 十字架始终保持完美对齐。
  • subview的 subview ,即不能触摸交叉线 View (例如对它们应用变换) - 所有可以修改的是 subview的变换。
  • 缩放和平移变换仅存储在全局矩阵中 matrix .
  • matrix然后用于计算 superview 中绘制的十字的坐标(平凡的),以及 subview 的变换矩阵(不是微不足道的 - 这个问题的原因)。
  • 由于似乎无法推导出 subview 的矩阵唯一来自全局矩阵,它允许在变量中存储额外的数据,然后与全局矩阵一起使用来计算 subview的矩阵。
  • container的大小/来源可以在缩放/平移期间更改。这样做的原因是 y 轴的标签可以有不同的长度,并且图表需要根据标签占用的空间(缩放和平移期间)动态调整内容大小。
  • 当然当大小container变化,域-屏幕坐标的比例必须相应变化,使得完整的原始可见域继续包含在container中。 .例如,如果我在宽度为 500pt 的容器框架中显示域 [0, 10] 的 x 轴,即,将域点转换为屏幕坐标的比率为 500/10=50 ,并将容器宽度缩小到 250,现在我的 [0, 10] 域必须适应这个新宽度,比率为 25。
  • 它也必须适用于多个交叉(同时)和每个交叉的任意域位置。这应该通过解决 1-7 自动发生,但为了完整性而提及它。

  • 我做了什么:

    以下是我为了更好地理解问题所做的分步操场:

    第 1 步(作品):

    按照开头所述构建层次结构,只显示在(程序化)缩放和平移期间必须保持对齐的十字架。满足要求 1、2、3、4 和 5:

    enter image description here
    Gist with playground.

    这里的特殊性:
  • 我跳过了 container View ,以保持简单。 subviewsuperview 的直接 subview .
  • subview大小与 superview 相同(当然在缩放之前),也为了保持简单。
  • 我设置了subview的 anchor 到原点 (0, 0),这似乎是与全局矩阵同步所必需的。
  • 必须记住用于 anchor 更改的翻译,以便与全局矩阵一起再次应用它。否则它会被覆盖。为此,我使用变量 subviewAnchorTranslation .这属于我在要求 5 下的项目符号中想到的附加数据。

  • 好的,如您所见,这里一切正常。是时候尝试下一步了。

    第 2 步(作品):

    修改后的第 1 步操场的副本:
  • 已添加 container View ,现在类似于开头描述的 View 层次结构。
  • 为了subview ,现在是 container 的 subview 要继续在同一位置显示,必须将其移动到顶部和左侧 -container.origin .
  • 现在缩放和平移调用与更改容器的帧位置/大小的调用随机交错。

  • 十字架继续同步。满足要求:所有来自步骤 1 + 要求 6。
    enter image description here
    Gist with playground

    第 3 步(不起作用):

    到目前为止,我一直在使用从 0(可见操场结果的左侧)开始的屏幕范围。这意味着 container不满足包含范围的功能,即要求 7。为了满足这一点,container的原点必须包含在比率计算中。

    现在还有subview必须缩放以适应 container/在正确的位置显示十字架。它添加了第二个变量(第一个是 subviewAnchorTranslation ),我称之为 contentScalingFactor ,包含此缩放比例,必须包含在 subview 中的矩阵计算。

    我在这里做了多次实验,都失败了。在当前状态下,subview从与 container 相同的帧开始当container的帧被调整+缩放时,它的帧被调整+缩放变化。另外,subview现在在容器内,即它的原点现在是 container的起源而不是 superview的原点,我必须设置更新它的 anchor ,使得原点不在 (0,0) 而是 (-x,-y),x 和 y 是 container 的坐标的起源,使得 subview继续定位于 superview的起源。每次更新这个 anchor 似乎是合乎逻辑的 container改变它的原点,因为这改变了 content 的相对位置的起源地superview的起源。

    我为此上传了代码 - 在这种情况下是一个完整的 iOS 项目,而不仅仅是一个操场(我最初认为它可以工作并想使用实际手势进行测试)。在实际项目中,我正在处理转换效果更好,但我找不到区别。无论如何它不能很好地工作,在某些时候总是有小的偏移并且点/交叉点不同步。

    enter image description here
    Github project

    好的,我该如何解决这个问题才能满足所有条件。十字必须保持同步,连续缩放/平移并更改 container 的框架之间。

    最佳答案

    当前答案允许任意转换 Child 层次结构中的任何 View 。它不跟踪转换,只是转换一个转换点,从而回答了这个问题:
    位于 subview 中的点在另一个 View 的坐标系中的坐标是什么,无论该 subview 已经转换了多少。
    为了将 Parent 与裁剪容器分离并提供通用答案,我建议将它们放在同一级别 概念 , 并以不同的顺序 视觉 (†):
    Modified hierarchy
    使用通用的 super View
    要应用从子级到父级的滚动、缩放或任何其他转换,请通过通用 super View (在本示例中命名为 Coordinator)。
    该方法与 this Stack Overflow answer 非常相似凡二UIScrollView以不同的速度滚动。
    注意红色细线和黑色细线是如何重叠的,而不管 Child 中的任何 View 的位置、滚动、变换如何。层次结构,包括 Container .
    Demo - all crosses match
    ↻ replay animation

    代码
    坐标转换
    为清晰起见,使用 subview 坐标系中的任意点 (50,50)(有效绘制该点的位置)进行简化,并在父 View 系统中将其转换如下所示:

    func coordinate() {
        let transfer = theChild.convert(CGPoint(x:50, y:50), to: coordinator)
        let final = coordinator.convert(transfer, to: theParent)
        theParent.transformed = final
        theParent.setNeedsDisplay()
    }
    
    缩放和翻译容器
    func zoom(center: CGPoint, delta: CGPoint) {
        theContainer.transform = theContainer.transform.scaledBy(x: delta.x, y: delta.y)
        coordinate()
    }
    
    func translate(delta: CGPoint) {
        theContainer.transform = theContainer.transform.translatedBy(x: delta.x, y: delta.y)
        coordinate()
    }
    

    (†) 我已将 Superview 和 Subview 分别重命名为 Parent 和 Child。

    关于ios - 同步不同坐标空间中的超 View 和单个 View 的变换矩阵,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41337146/

    相关文章:

    algorithm - 对于给定的 n,如何找到具有以下递推关系的序列中的第 n 项?

    javascript - OpenGL中检测矩阵是否镜像对象

    c - 优化的 2x2 矩阵乘法 : Slow assembly versus fast SIMD

    ios - SpriteKit 检测节点是否从父节点移除

    ios - 检查依赖性...没有要编译的体系结构(ARCHS=i386,VALID_ARCHS=arm64 armv7s armv7)

    java - Scala 中的 BigDecimal

    matlab - 沿矩阵的每一行进行一维卷积

    ios - UIActivityViewController UIActivityViewControllerCompletionWithItemsHandler

    ios - 重新加载部分:withRowAnimation: doesn't update number of rows

    java - 慢慢减少数量