了解convertRect:toView:,convertRect:FromView:,convertPoint:toView:和convertPoint:fromView:方法


128

我正在尝试了解这些方法的功能。您能否提供一个简单的用例来理解他们的语义?

在文档中,例如,convertPoint:fromView:方法描述如下:

将点从给定视图的坐标系转换为接收者的坐标系。

什么是坐标系是什么意思?怎么样接收器

例如,像下面这样使用convertPoint:fromView:是否有意义?

CGPoint p = [view1 convertPoint:view1.center fromView:view1];

使用NSLog实用程序,我已验证p值与view1的中心重合。

先感谢您。

编辑:对于那些感兴趣的人,我创建了一个简单的代码片段以了解这些方法。

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是用超级视图(self.window)坐标表示的。输出如下:

convertPoint:toView:{0,0}

Answers:


184

每个视图都有自己的坐标系-原点为0,0,宽度和高度为。这bounds在视图的矩形中进行了描述。该frame视图的,但是,将有它的起源点内的边界矩形它的父的。

视图层次结构的最外面的视图的原点是0,0,它对应于iOS屏幕的左上角。

如果向该视图添加20,30的子视图,则该子视图中位于0,0的点对应于该超级视图中位于20,30的点。这些方法正在执行此转换。

上面的示例是毫无意义的(无双关语),因为它将点从视图转换为自身,因此不会发生任何事情。您通常会发现某个视图相对于其超级视图的位置在何处-测试视图是否移出屏幕,例如:

CGPoint originInSuperview = [superview convertPoint:CGPointZero fromView:subview];

“接收者”是接收消息的对象的标准objective-c术语(方法也称为消息),因此在我的示例中,接收者为superview


3
谢谢您的回复,jrturton。非常有用的解释。convertPoint并且convertRect返回类型不同。CGPointCGRect。但是,我们fromto?有没有可以使用的经验法则?谢谢。
Lorenzo B

从何时要转换为,到何时要转换为?
jrturton 2011年

3
如果超级视图不是子视图的直接父视图,该视图仍将起作用吗?
Van Du Tran

3
@VanDuTran是的,只要它们在同一窗口中(iOS应用程序中的大多数视图都在其中)
jrturton 2013年

好答案。帮助我清理了很多东西。还有其他读物吗?
JaeGeeTee 2014年

39

我总是感到困惑,因此我创建了一个游乐场,您可以在其中直观地查看该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

请记住显示助手编辑器()以查看视图,它应如下所示:

在此处输入图片说明

随意在这里或本要点中贡献更多示例。


谢谢您,这对于理解转化非常有用。
阿南德

很棒的概述。但是我认为这些self.view是多余的,view如果self不需要的话,可以简单地使用。
雅各布·特鲁拉斯(JakubTruhlář)

谢谢@JakubTruhlář,我才删除了self
phi

非常感谢!您的游乐场对我很有帮助,可以弄清楚这种转换是如何工作的。
安瓦尔·阿齐佐夫

30

这是简单的英语说明。

当您想将子视图(aView的子视图[aView superview])的rect转换为另一个视图(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];

3
这不是真的 它不会移动视图,而只是为您提供另一个视图的坐标。在您的情况下,它将为您提供aView的坐标,以表明它们的位置。
2014年

正确。它不会移动视图。该方法返回一个CGRect。您选择与该CGRect合作的是您的业务。:-)在上述情况下,它可以用于将一个视图移动到另一个视图的层次结构,同时保持其在屏幕上的视觉位置。
horseshoe7 2014年

14

iOS中的每个视图都有一个坐标系。坐标系就像一个图形,它具有x轴(水平线)和y轴(垂直线)。线相交的点称为原点。点由(x,y)表示。例如,(2,1)表示该点还剩2个像素,而又剩1个像素。

您可以在此处详细了解坐标系统-http: //en.wikipedia.org/wiki/Coordinate_system

但是您需要知道的是,在iOS中,每个视图都有其OWN坐标系,左上角是原点。X轴继续向右增加,y轴继续向下增加。

对于转换点问题,请使用以下示例。

有一个称为V1的视图,该视图宽100像素,高100像素。现在在其中有另一个视图,称为V2,位于(10,10,50,50),这意味着(10,10)是V1坐标系中应位于V2左上角的点,并且( 50、50)是V2的宽度和高度。现在,在INSIDE V2的坐标系中取得一个点,例如(20,20)。现在,那一点在V1的坐标系中会是什么?这就是方法的目的(当然您可以自己计算,但是它们可以节省您的额外工作)。为了记录,V1中的点将是(30,30)。

希望这可以帮助。


9

谢谢大家发布问题和您的答案:它帮助我解决了这一问题。

我的视图控制器具有普通视图。

在该视图内有许多分组视图,它们的作用不仅仅在于使子视图具有自动布局约束的清晰交互。

在这些分组视图之一的内部,我有一个添加按钮,该按钮提供了一个弹出视图控制器,用户可以在其中输入一些信息。

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];
}

上面的前两个答案给出的解释的关键部分是,我需要转换的矩形是add按钮的边界,而不是其框架。

我没有在更复杂的视图层次结构中进行尝试,但是我怀疑通过使用方法调用(inView :)中提供的视图,我们可以避免多层叶子视图带来的复杂性。


3
“我需要转换的是添加按钮的边界,而不是框架。” -本质上使我免于使用许多丑陋的框架代码来使其正常工作。感谢您抽出
宝贵

3

我用这篇文章来申请我的情况。希望这对将来的读者有所帮助。

视图只能看到其直接子视图和父视图。它看不到其祖父母或孙子女的观点。

因此,就我而言,我有一个名为的大父视图,self.view为此,self.view我添加了称为的子视图。在,我已经添加子视图叫,。 现在,如果我物理上移至的另一位置上的另一个位置,则可以计算内的另一个位置self.child1OfViewself.child2OfViewself.child1OfViewself.child1OfView1self.child2OfView1
self.child1OfView1self.child1OfViewself.viewself.child1OfView1self.view:

CGPoint newPoint = [self.view convertPoint:self.child1OfView1.center fromView:self.child1OfView];

3

您可以查看以下代码,以便了解其实际工作原理。

    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)

}

1

我阅读了答案并理解了机制,但我认为最后的示例是不正确的。根据API文档,视图的center属性在超级视图的坐标系中包含视图的已知中心点。

如果是这种情况,那么我认为尝试让超级视图从子视图坐标系转换子视图的中心就没有意义,因为该值不在子视图坐标系中。有意义的是做相反的事情,即从超级视图坐标系转换为子视图的坐标系...

您可以通过两种方式来做到这一点(两种方式都应产生相同的值):

CGPoint centerInSubview = [subview convertPoint:subview.center fromView:subview.superview];

要么

CGPoint centerInSubview = [subview.superview convertPoint:subview.center toView:subview];

我在理解它应该如何工作方面还遥遥无期吗?


我的例子不正确,我已经更新了答案。正如您所说,center属性已经在超级视图的坐标空间中。
jrturton 2012年

1

关于使用这些API的另一点要点。确保要转换的矩形与到/从视图之间的父视图链完整。例如-aView,bView和cView-

  • aView是bView的子视图
  • 我们想要将aView.frame转换为cView

如果在添加bView作为cView的子视图之前尝试执行该方法,我们将返回一个上下铺响应。不幸的是,这种情况下的方法没有内置保护措施。这看起来似乎很明显,但是在转换经过一长串父母的情况下,需要注意一点。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.