给定以下 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 种不同的方式呈现图表点和相关内容:
superview
) draw
方法。 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 步(作品):
按照开头所述构建层次结构,只显示在(程序化)缩放和平移期间必须保持对齐的十字架。满足要求 1、2、3、4 和 5:
Gist with playground.
这里的特殊性:
container
View ,以保持简单。 subview
是 superview
的直接 subview . subview
大小与 superview
相同(当然在缩放之前),也为了保持简单。 subview
的 anchor 到原点 (0, 0),这似乎是与全局矩阵同步所必需的。 subviewAnchorTranslation
.这属于我在要求 5 下的项目符号中想到的附加数据。好的,如您所见,这里一切正常。是时候尝试下一步了。
第 2 步(作品):
修改后的第 1 步操场的副本:
container
View ,现在类似于开头描述的 View 层次结构。 subview
,现在是 container
的 subview 要继续在同一位置显示,必须将其移动到顶部和左侧 -container.origin
. 十字架继续同步。满足要求:所有来自步骤 1 + 要求 6。
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 项目,而不仅仅是一个操场(我最初认为它可以工作并想使用实际手势进行测试)。在实际项目中,我正在处理转换效果更好,但我找不到区别。无论如何它不能很好地工作,在某些时候总是有小的偏移并且点/交叉点不同步。
Github project
好的,我该如何解决这个问题才能满足所有条件。十字必须保持同步,连续缩放/平移并更改
container
的框架之间。
最佳答案
当前答案允许任意转换 Child 层次结构中的任何 View 。它不跟踪转换,只是转换一个转换点,从而回答了这个问题:
位于 subview 中的点在另一个 View 的坐标系中的坐标是什么,无论该 subview 已经转换了多少。
为了将 Parent 与裁剪容器分离并提供通用答案,我建议将它们放在同一级别 概念 , 并以不同的顺序 视觉 (†):
使用通用的 super View
要应用从子级到父级的滚动、缩放或任何其他转换,请通过通用 super View (在本示例中命名为 Coordinator)。
该方法与 this Stack Overflow answer 非常相似凡二UIScrollView
以不同的速度滚动。
注意红色细线和黑色细线是如何重叠的,而不管 Child
中的任何 View 的位置、滚动、变换如何。层次结构,包括 Container
.
↻ 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/