了解 convertRect:toView:、convertRect:FromView:、convertPoint:toView: 和 convertPoint:fromView: 方法
Posted
技术标签:
【中文标题】了解 convertRect:toView:、convertRect:FromView:、convertPoint:toView: 和 convertPoint:fromView: 方法【英文标题】:Understand convertRect:toView:, convertRect:FromView:, convertPoint:toView: and convertPoint:fromView: methods 【发布时间】:2012-01-17 22:27:49 【问题描述】:我正在尝试了解这些方法的功能。你能提供一个简单的用例来理解它们的语义吗?
从文档中,例如convertPoint:fromView:
方法描述如下:
将一个点从给定视图的坐标系转换为接收器的坐标系。
坐标系是什么意思? 接收器呢?
例如,像下面这样使用convertPoint:fromView:
有意义吗?
CGPoint p = [view1 convertPoint:view1.center fromView:view1];
使用 NSLog 实用程序,我已验证 p
的值与 view1
的中心一致。
提前谢谢你。
编辑:对于那些感兴趣的人,我创建了一个简单的代码 sn-p 来理解这些方法。
UIView* view1 = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 150, 200)];
view1.backgroundColor = [UIColor redColor];
NSLog(@"view1 frame: %@", NSStringFromCGRect(view1.frame));
NSLog(@"view1 center: %@", NSStringFromCGPoint(view1.center));
CGPoint originInWindowCoordinates = [self.window convertPoint:view1.bounds.origin fromView:view1];
NSLog(@"convertPoint:fromView: %@", NSStringFromCGPoint(originInWindowCoordinates));
CGPoint originInView1Coordinates = [self.window convertPoint:view1.frame.origin toView:view1];
NSLog(@"convertPoint:toView: %@", NSStringFromCGPoint(originInView1Coordinates));
在这两种情况下,self.window
都是接收者。但是有区别。在第一种情况下,convertPoint
参数以view1
坐标表示。输出如下:
convertPoint:fromView: 100, 100
在第二个中,convertPoint
用 superview (self.window
) 坐标表示。输出如下:
convertPoint:toView: 0, 0
【问题讨论】:
【参考方案1】:每个视图都有自己的坐标系 - 原点位于 0,0,宽度和高度。这在视图的bounds
矩形中进行了描述。然而,视图的frame
的原点将位于其父视图的边界矩形内的点。
视图层次结构的最外层视图的原点位于 0,0,对应于 ios 屏幕的左上角。
如果您将 20,30 处的子视图添加到此视图,则子视图中 0,0 处的点对应于父视图中 20,30 处的点。这种转换就是那些方法正在做的事情。
您上面的示例毫无意义(不是双关语),因为它将观点从视图转换为自身,因此不会发生任何事情。您通常会发现某个视图的某个点与其父视图相关的位置 - 以测试视图是否正在移出屏幕,例如:
CGPoint originInSuperview = [superview convertPoint:CGPointZero fromView:subview];
“接收者”是接收消息的对象的标准 Objective-c 术语(方法也称为消息),因此在我的示例中,接收者是 superview
。
【讨论】:
感谢您的回复,jrturton。非常有用的解释。convertPoint
和 convertRect
的返回类型不同。 CGPoint
或 CGRect
。但是from
和to
呢?有没有我可以使用的经验法则?谢谢。
从你想转换的时间到你想转换到的时间?
如果superview不是subview的直接父view怎么办?
@VanDuTran 是的,只要它们在同一个窗口中(iOS 应用程序中的大多数视图都是)
很好的答案。帮我清理了很多。任何额外的阅读?【参考方案2】:
我总是觉得这很令人困惑,所以我创建了一个游乐场,您可以在其中直观地探索 convert
函数的作用。这是在 Swift 3 和 Xcode 8.1b 中完成的:
import UIKit
import PlaygroundSupport
class MyViewController: UIViewController
override func viewDidLoad()
super.viewDidLoad()
// Main view
view.backgroundColor = .black
view.frame = CGRect(x: 0, y: 0, width: 500, height: 500)
// Red view
let redView = UIView(frame: CGRect(x: 20, y: 20, width: 460, height: 460))
redView.backgroundColor = .red
view.addSubview(redView)
// Blue view
let blueView = UIView(frame: CGRect(x: 20, y: 20, width: 420, height: 420))
blueView.backgroundColor = .blue
redView.addSubview(blueView)
// Orange view
let orangeView = UIView(frame: CGRect(x: 20, y: 20, width: 380, height: 380))
orangeView.backgroundColor = .orange
blueView.addSubview(orangeView)
// Yellow view
let yellowView = UIView(frame: CGRect(x: 20, y: 20, width: 340, height: 100))
yellowView.backgroundColor = .yellow
orangeView.addSubview(yellowView)
// Let's try to convert now
var resultFrame = CGRect.zero
let randomRect: CGRect = CGRect(x: 0, y: 0, width: 100, height: 50)
/*
func convert(CGRect, from: UIView?)
Converts a rectangle from the coordinate system of another view to that of the receiver.
*/
// The following line converts a rectangle (randomRect) from the coordinate system of yellowView to that of self.view:
resultFrame = view.convert(randomRect, from: yellowView)
// Try also one of the following to get a feeling of how it works:
// resultFrame = view.convert(randomRect, from: orangeView)
// resultFrame = view.convert(randomRect, from: redView)
// resultFrame = view.convert(randomRect, from: nil)
/*
func convert(CGRect, to: UIView?)
Converts a rectangle from the receiver’s coordinate system to that of another view.
*/
// The following line converts a rectangle (randomRect) from the coordinate system of yellowView to that of self.view
resultFrame = yellowView.convert(randomRect, to: view)
// Same as what we did above, using "from:"
// resultFrame = view.convert(randomRect, from: yellowView)
// Also try:
// resultFrame = orangeView.convert(randomRect, to: view)
// resultFrame = redView.convert(randomRect, to: view)
// resultFrame = orangeView.convert(randomRect, to: nil)
// Add an overlay with the calculated frame to self.view
let overlay = UIView(frame: resultFrame)
overlay.backgroundColor = UIColor(white: 1.0, alpha: 0.9)
overlay.layer.borderColor = UIColor.black.cgColor
overlay.layer.borderWidth = 1.0
view.addSubview(overlay)
var ctrl = MyViewController()
PlaygroundPage.current.liveView = ctrl.view
记得显示助理编辑器 (⎇⌘⏎) 以查看视图,它应该如下所示:
请随时在此处或this gist 中提供更多示例。
【讨论】:
很棒的概述。但是我认为那些self.view
是多余的,如果不需要self
,则简单使用view
。
谢谢@JakubTruhlář,我刚刚删除了self
【参考方案3】:
这里有一个简单的英语解释。
当您想将一个子视图的矩形(aView
是[aView superview]
的子视图)转换为另一个视图的坐标空间(self
)时。
// So here I want to take some subview and put it in my view's coordinate space
_originalFrame = [[aView superview] convertRect: aView.frame toView: self];
【讨论】:
这不是真的。它不会移动视图,它只是为您提供视图相对于另一个视图的坐标。在您的情况下,它将根据它们在 self 中的位置为您提供 aView 的坐标。 正确。它不会移动视图。该方法返回一个 CGRect。你选择用那个 CGRect 做什么是你的事。 :-) 在上述情况下,它可用于将一个视图移动到另一个视图的层次结构,同时保持其在屏幕上的视觉位置。【参考方案4】:iOS 中的每个视图都有一个坐标系。坐标系就像一个图,它有 x 轴(水平线)和 y 轴(垂直线)。线相交的点称为原点。一个点由 (x, y) 表示。例如,(2, 1) 表示该点向左 2 个像素,向下 1 个像素。
您可以在此处阅读有关坐标系的更多信息 - http://en.wikipedia.org/wiki/Coordinate_system
但你需要知道的是,在 iOS 中,每个视图都有自己的坐标系,左上角是原点。 X轴向右递增,y轴向下递增。
换算点问题,以这个为例。
有一个名为 V1 的视图,它宽 100 像素,高 100 像素。现在在里面,有另一个视图,称为 V2,位于 (10, 10, 50, 50),这意味着 (10, 10) 是 V1 坐标系中应位于 V2 左上角的点,并且 ( 50, 50) 是 V2 的宽度和高度。现在,在 V2 的坐标系中取一个点,例如 (20, 20)。现在,那个点在 V1 的坐标系中会是什么?这就是这些方法的用途(当然你可以自己计算,但它们会为你节省额外的工作)。作为记录,V1 中的点是 (30, 30)。
希望这会有所帮助。
【讨论】:
【参考方案5】:感谢大家发布问题和答案:它帮助我解决了这个问题。
我的视图控制器有它的正常视图。
在该视图中,有许多分组视图,它们只是为它们的子视图提供与自动布局约束的干净交互。
在其中一个分组视图中,我有一个添加按钮,它显示了一个弹出视图控制器,用户可以在其中输入一些信息。
view
--groupingView
----addButton
在设备旋转期间,视图控制器通过 UIPopoverViewControllerDelegate 调用 popoverController:willRepositionPopoverToRect:inView: 得到警报:
- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing *)view
*rect = [self.addButton convertRect:self.addbutton.bounds toView:*view];
上面前两个答案给出的解释的基本部分是我需要转换的矩形是添加按钮的 bounds,而不是它的框架。
我还没有尝试过使用更复杂的视图层次结构,但我怀疑通过使用方法调用 (inView:) 中提供的视图,我们可以解决多层叶视图类型的丑陋问题。
【讨论】:
"我需要转换的是添加按钮的边界,而不是框架。" - 基本上让我免于使用许多丑陋的框架代码以使其正常工作。感谢您抽出宝贵时间指出这一点【参考方案6】:我使用这篇文章来申请我的案例。希望这对将来的其他读者有所帮助。
一个视图只能看到它的直接子视图和父视图。它看不到它的祖父母或孙子的观点。
因此,就我而言,我有一个名为self.view
的祖父视图,在此self.view
中,我添加了名为self.child1OfView
、self.child2OfView
的子视图。在self.child1OfView
中,我添加了名为self.child1OfView1
、self.child2OfView1
的子视图。
现在,如果我将self.child1OfView1
物理移动到self.child1OfView
边界之外的区域到self.view
上的另一个位置,然后计算self.child1OfView1
在self.view:
内的新位置
CGPoint newPoint = [self.view convertPoint:self.child1OfView1.center fromView:self.child1OfView];
【讨论】:
【参考方案7】:您可以查看下面的代码,以便了解它的实际工作原理。
let scrollViewTemp = UIScrollView.init(frame: CGRect.init(x: 10, y: 10, width: deviceWidth - 20, height: deviceHeight - 20))
override func viewDidLoad()
super.viewDidLoad()
scrollViewTemp.backgroundColor = UIColor.lightGray
scrollViewTemp.contentSize = CGSize.init(width: 2000, height: 2000)
self.view.addSubview(scrollViewTemp)
let viewTemp = UIView.init(frame: CGRect.init(x: 100, y: 100, width: 150, height: 150))
viewTemp.backgroundColor = UIColor.green
self.view.addSubview(viewTemp)
let viewSecond = UIView.init(frame: CGRect.init(x: 100, y: 700, width: 300, height: 300))
viewSecond.backgroundColor = UIColor.red
self.view.addSubview(viewSecond)
self.view.convert(viewTemp.frame, from: scrollViewTemp)
print(viewTemp.frame)
/* First take one point CGPoint(x: 10, y: 10) of viewTemp frame,then give distance from viewSecond frame to this point.
*/
let point = viewSecond.convert(CGPoint(x: 10, y: 10), from: viewTemp)
//output: (10.0, -190.0)
print(point)
/* First take one point CGPoint(x: 10, y: 10) of viewSecond frame,then give distance from viewTemp frame to this point.
*/
let point1 = viewSecond.convert(CGPoint(x: 10, y: 10), to: viewTemp)
//output: (10.0, 210.0)
print(point1)
/* First take one rect CGRect(x: 10, y: 10, width: 20, height: 20) of viewSecond frame,then give distance from viewTemp frame to this rect.
*/
let rect1 = viewSecond.convert(CGRect(x: 10, y: 10, width: 20, height: 20), to: viewTemp)
//output: (10.0, 210.0, 20.0, 20.0)
print(rect1)
/* First take one rect CGRect(x: 10, y: 10, width: 20, height: 20) of viewTemp frame,then give distance from viewSecond frame to this rect.
*/
let rect = viewSecond.convert(CGRect(x: 10, y: 10, width: 20, height: 20), from: viewTemp)
//output: (10.0, -190.0, 20.0, 20.0)
print(rect)
【讨论】:
【参考方案8】:我阅读了答案并理解了机制,但我认为最后一个示例不正确。根据 API 文档,视图的 center 属性包含视图在父视图坐标系中的已知中心点。
如果是这种情况,我认为尝试要求超级视图从子视图坐标系转换子视图的中心是没有意义的,因为该值不在子视图坐标系中。有意义的是做相反的事情,即从超级视图坐标系转换为子视图的坐标系......
您可以通过两种方式来实现(两者都应该产生相同的值):
CGPoint centerInSubview = [subview convertPoint:subview.center fromView:subview.superview];
或
CGPoint centerInSubview = [subview.superview convertPoint:subview.center toView:subview];
我在理解这应该如何工作方面还有很长的路要走吗?
【讨论】:
我的例子不正确,我已经更新了答案。正如你所说,center属性已经在superview的坐标空间中了。【参考方案9】:关于使用这些 API 的另一个重要点。确保父视图链在您要转换的矩形和 to/from 视图之间是完整的。 例如 - aView、bView 和 cView -
aView 是 bView 的子视图 我们想将 aView.frame 转换为 cView如果我们在 bView 被添加为 cView 的子视图之前尝试执行该方法,我们将得到一个双层响应。不幸的是,在这种情况下,方法中没有内置保护。这似乎很明显,但在转换经过一长串父母的情况下需要注意。
【讨论】:
以上是关于了解 convertRect:toView:、convertRect:FromView:、convertPoint:toView: 和 convertPoint:fromView: 方法的主要内容,如果未能解决你的问题,请参考以下文章