同步超视图的变换矩阵和不同坐标空间中的各个视图

Ixx*_*Ixx 8 math transform matrix uiview ios

给定以下视图层次结构

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
Run Code Online (Sandbox Code Playgroud)

任务:

在全局变换的情况下,必须始终对齐superview并且subview必须始终对齐."要求"部分中的更多详细信息.

语境:

上面的视图层次结构属于图表.为了提供最大的灵活性,它允许以3种不同的方式呈现图表点和相关内容:

  1. 在图表的基本视图(superview)draw方法中绘图.

  2. 添加子视图到subview.subview在zoom/pan上进行转换,并自动显示其子视图.

  3. 将子视图添加到兄弟姐妹中subview.为简单起见,未在视图层次结构中显示,因为它与问题无关.这里只提一下概述.这个方法和2.之间的区别在于,这里视图没有被转换,所以它留给内容的实现来"手动"更新所有子节点的转换.

最大的灵活性!但实现这一目标的成本实施起来有点棘手.具体点2.

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

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

我在视图层次结构部分提到"十字架",因为这是我在游乐场中使用的一个图表点的简化表示(使用x/y指南)(您可以向下滚动图像和图表).

要求:

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

我做了什么:

以下是我试图更好地理解问题的分步操场:

第1步(工作):

如开头所述构建层次结构,只显示在(编程)缩放和平移期间必须保持对齐的十字架.符合要求1,2,3,4和5:

在此输入图像描述 要点与操场.

特殊性:

  • 我跳过了container视图,保持简单.subview是直接的子视图superview.
  • subview与当前superview缩放前的尺寸相同,也是为了保持简单.
  • 我将锚点设置为subview原点(0,0),这似乎是与全局矩阵同步的必要条件.
  • 必须记住用于锚更改的转换,以便再次将其与全局矩阵一起应用.否则会被覆盖.为此,我使用变量subviewAnchorTranslation.这属于我在要求5下的子弹中考虑的额外数据.

好的,正如你看到的一切都在这里工作.是时候尝试下一步了.

第2步(工作):

步骤1游乐场的副本和修改:

  • 添加了container视图,现在类似于开头描述的视图层次结构.
  • 为了subview现在是container继续显示在同一位置的子视图,它必须被移动到顶部和左侧-container.origin.
  • 现在,缩放和平移调用随机调用交错,以更改容器的帧位置/大小.

十字架继续保持同步.满足要求:全部来自步骤1 +要求6. 在此输入图像描述 要点与操场

第3步(不起作用):

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

现在还subview必须进行缩放,以便container在正确的位置放入/显示十字架.其中添加了第二个变量(第一个是subviewAnchorTranslation),我调用它contentScalingFactor,包含这个缩放,必须包含在subview矩阵计算中.

在这里,我做了多次实验,但都失败了.在当前状态下,subview以相同的帧开始,container并且当帧发生container变化时,其帧被调整+缩放.此外,subview现在在容器内部,即它的起源是现在container的起源而不是superview起源,我必须设置更新它的锚点,使原点不在(0,0)但是(-x,-y), x和y的原点坐标container,这样就可以subview继续相对于superview原点的位置.每次container改变它的原点时更新这个锚似乎是合乎逻辑的,因为这会改变从content原点到原点的相对位置superview.

我为此上传了代码 - 在这种情况下是一个完整的iOS项目,而不仅仅是一个游乐场(我最初认为它正在工作,并希望使用实际手势进行测试).在实际项目中,我正在努力改造工作,但我找不到差异.无论如何它不能很好地工作,在某些时候总是存在小的偏移并且点/交叉不同步.

在此输入图像描述 Github项目

好的,我如何解决这个问题,以满足所有条件.十字架必须保持同步,连续缩放/平移并改变container其间的帧.

Swi*_*ect 0

当前的答案允许任意转换子层次结构中的任何视图。它不跟踪变换,仅转换变换点,从而回答以下问题:

\n

位于子视图中的点在另一个视图的坐标系中的坐标是多少,无论该子视图已经变换了多少

\n

为了将父级与剪切容器分离并提供通用答案,我建议将它们在概念上放置在同一级别,并在视觉上以不同的顺序 (\xe2\x80\xa0)

\n

修改的层次结构

\n

使用通用的超级视图

\n

要应用从ChildParent的滚动、缩放或任何其他转换,请通过公共超级视图(在本示例中名为Coordinator )。

\n

该方法与Stack Overflow 的答案非常相似,其中两个UIScrollView以不同的速度滚动。

\n

请注意红色发际线和黑色发际线如何重叠,无论层次结构中任何和所有视图(包括 的视图)的位置、滚动和变换Child如何Container

\n

演示 - 所有交叉匹配
\n \xe2\x86\xbb 重播动画

\n
\n

代码

\n

坐标转换

\n

为了清楚起见,进行了简化,在子视图的坐标系中使用任意点(50,50)(该点被有效绘制),并将其转换到视图系统中,如下所示:

\n
func coordinate() {\n    let transfer = theChild.convert(CGPoint(x:50, y:50), to: coordinator)\n    let final = coordinator.convert(transfer, to: theParent)\n    theParent.transformed = final\n    theParent.setNeedsDisplay()\n}\n
Run Code Online (Sandbox Code Playgroud)\n

缩放和平移容器

\n
func zoom(center: CGPoint, delta: CGPoint) {\n    theContainer.transform = theContainer.transform.scaledBy(x: delta.x, y: delta.y)\n    coordinate()\n}\n\nfunc translate(delta: CGPoint) {\n    theContainer.transform = theContainer.transform.translatedBy(x: delta.x, y: delta.y)\n    coordinate()\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

(\xe2\x80\xa0) 我已将SuperviewSubview分别重命名为ParentChild

\n