Understand convertRect:toView:, convertRect:FromView:, convertPoint:toView: and convertPoint:fromView: methods

78,384

Solution 1

Each view has its own coordinate system - with an origin at 0,0 and a width and height. This is described in the bounds rectangle of the view. The frame of the view, however, will have its origin at the point within the bounds rectangle of its superview.

The outermost view of your view hierarchy has it's origin at 0,0 which corresponds to the top left of the screen in iOS.

If you add a subview at 20,30 to this view, then a point at 0,0 in the subview corresponds to a point at 20,30 in the superview. This conversion is what those methods are doing.

Your example above is pointless (no pun intended) since it converts a point from a view to itself, so nothing will happen. You would more commonly find out where some point of a view was in relation to its superview - to test if a view was moving off the screen, for example:

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

The "receiver" is a standard objective-c term for the object that is receiving the message (methods are also known as messages) so in my example here the receiver is superview.

Solution 2

I always find this confusing so I made a playground where you can visually explore what the convert function does. This is done in Swift 3 and 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

Remember to show the Assistant Editor () in order to see the views, it should look like this:

enter image description here

Feel free to contribute more examples here or in this gist.

Solution 3

Here's an explanation in plain English.

When you want to convert the rect of a subview (aView is a subview of [aView superview]) to the coordinate space of another view (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];

Solution 4

Every view in iOS have a coordinate system. A coordinate system is just like a graph, which has x axis(horizontal line) and y axis(vertical line). The point at which the lines interesect is called origin. A point is represented by (x, y). For example, (2, 1) means that the point is 2 pixels left, and 1 pixel down.

You can read up more about coordinate systems here - http://en.wikipedia.org/wiki/Coordinate_system

But what you need to know is that, in iOS, every view has it's OWN coordinate system, where the top left corner is the origin. X axis goes on increasing to the right, and y axis goes on increasing down.

For the converting points question, take this example.

There is a view, called V1, which is 100 pixels wide and 100 pixels high. Now inside that, there is another view, called V2, at (10, 10, 50, 50) which means that (10, 10) is the point in V1's coordinate system where the top left corner of V2 should be located, and (50, 50) is the width and height of V2. Now, take a point INSIDE V2's coordinate system, say (20, 20). Now, what would that point be inside V1's coordinate system? That is what the methods are for(of course you can calculate themselves, but they save you extra work). For the record, the point in V1 would be (30, 30).

Hope this helps.

Solution 5

Thank you all for posting the question and your answers: It helped me get this sorted out.

My view controller has it's normal view.

Inside that view there are a number of grouping views that do little more than give their child views a clean interaction with auto layout constraints.

Inside one of those grouping views I have an Add button that presents a popover view controller where the user enters some information.

view
--groupingView
----addButton

During device rotation the view controller is alerted via the UIPopoverViewControllerDelegate call 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];
}

The essential part that comes from the explanation given by the first two answers above was that the rect I needed to convert from was the bounds of the add button, not its frame.

I haven't tried this with a more complex view hierarchy, but I suspect that by using the view supplied in the method call (inView:) we get around the complications of multi-tiered leaf view kinds of ugliness.

Share:
78,384
Lorenzo B
Author by

Lorenzo B

Software Engineer | PhD in Computer Science

Updated on January 28, 2021

Comments

  • Lorenzo B
    Lorenzo B over 3 years

    I'm trying to understand the functionalities of these methods. Could you provide me with a simple use case to understand their semantics?

    From the documentation, for example, convertPoint:fromView: method is described as follows:

    Converts a point from the coordinate system of a given view to that of the receiver.

    What does the coordinate system mean? What about the receiver?

    For example, does it make sense using convertPoint:fromView: like the following?

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

    Using NSLog utility, I've verified that p value coincides with view1's center.

    Thank you in advance.

    EDIT: For those interested, I've created a simple code snippet to understand these methods.

    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));
    

    In both cases self.window is the receiver. But there is a difference. In the first case the convertPoint parameter is expressed in view1 coordinates. The output is the following:

    convertPoint:fromView: {100, 100}

    In the second one, instead, convertPoint is expressed in superview (self.window) coordinates. The output is the following:

    convertPoint:toView: {0, 0}

  • Lorenzo B
    Lorenzo B over 12 years
    Thank you for your reply, jrturton. Very useful explanation. convertPoint and convertRect differ in the return type. CGPoint or CGRect. But what about from and to? Is there a rule of thumb that I could use? Thank you.
  • jrturton
    jrturton over 12 years
    from when you want to convert from, to when you want to convert to?
  • jrturton
    jrturton over 11 years
    My example was incorrect, I have updated the answer. As you say, the center property is already in the superview's coordinate space.
  • Van Du Tran
    Van Du Tran over 11 years
    What if superview is not the direct parent view of subview, will it still work?
  • jrturton
    jrturton over 11 years
    @VanDuTran yes, as long as they are in the same window (which most views in an iOS app are)
  • JaeGeeTee
    JaeGeeTee over 10 years
    Great answer. Helped clear up a lot for me. Any additional reading?
  • Carl
    Carl about 10 years
    This isn't true. It doesn't move the view, it just gives you the co-ordinates of the view in terms of another. In your case it will give you the co-ordinates of aView in terms of where they'd be in self.
  • horseshoe7
    horseshoe7 about 10 years
    Correct. It doesn't move the view. The method returns a CGRect. What you choose to do with that CGRect is your business. :-) In the case above, it could be used to move one view to another's hierarchy while maintaining its visual position on screen.
  • latenitecoder
    latenitecoder about 9 years
    "I needed to convert from was the bounds of the add button, not it's frame." - essentially saved me from a lot of ugly frame code to make it work. Thanks for taking the time to point this out
  • ilovecomputer
    ilovecomputer about 8 years
    @jrturton Is center also in superView's coordinate system?
  • Jakub Truhlář
    Jakub Truhlář over 7 years
    Great overview. However I think those self.view are redundant, simple use view if self is not needed.
  • Stefan
    Stefan about 7 years
    @jrturton what if the object in question is in another class
  • phi
    phi over 6 years
    Thanks @JakubTruhlář, I just removed self