Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does UIView convert points between views?

Tags:

ios

uikit

Does anyone know the algorithm used by UIView's convertPoint:toView: and convertPoint:fromView: methods? In particular, do you know if they first convert the point to window coordinates and then to the target view's coordinate system, or might they take a short cut since the "from" view is an ancestor of the "to" view?

I ask because I have a loop which tests a point against a possibly large number of views and I'm wondering if it would be more efficient to convert that point to window coordinates once before the loop (which also has the advantage of not having to pass the point's view along into the loop and whatever it calls, knowing I can pass nil for the "fromView" argument) or to keep the point relative to the parent view.

The difference may be minor but, since it makes little difference to my code, I'd rather work with the system than against it.

like image 591
big_m Avatar asked May 16 '11 22:05

big_m


1 Answers

Well, I'm not a whole lot closer to knowing what the algorithm is, but I now have some idea of what it's not doing. I had a bit of time last night to whip up a test program to compare the methods and the results were rather surprising.

The fastest method was pre-converting the point to window coordinates and then converting to each target view. What was surprising was how much faster it was: about 25-27 times faster! And on top of that, doing a two-stage conversion each time (first to window coordinates and then a separate call to convert to the target view) was still more than 10 times faster than a single call to convertPoint:fromView: with a non-nil view argument. I really cannot account for this. I might have expected one method to be up to twice as fast, but it really shouldn't be faster to do two conversions than to do one directly! (I tried the tests in various orders as well, to make sure that wasn't affecting the timings.)

Here are the results (for 1 million conversions, run on an iPad 1, iOS 4.3.3):

convert from window point: 0.204297
two-step convert through window coordinates: 0.390832
convert from subview point: 5.020129

I'll post the code below for review, but if these results are correct, my only guess is that the conversion code is highly optimized for converting to and from window/screen coordinates and that it uses some (much slower) method to convert directly between two view coordinate systems when given a non-nil view argument. Perhaps there are conditions where the disparity is not so great (such as when there are non-identity transformations involved), but for what I expect is the common case, it seems like it would be a whole lot better for it to do the two-step conversion first to window coordinates and then to the other view.

Again, there are probably few circumstances where this will have a noticeable affect on a program, but it's something to keep in mind if you ever have to make a lot of repeated calls to convertPoint:toView: or convertPoint:fromView: .

Here's my code, if anyone cares to check it over for errors or run it themselves:

@implementation TestConvertPointViewController

- (NSTimeInterval) timeForBlock:(void (^)())block
{
    CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
    block();
    return CFAbsoluteTimeGetCurrent() - startTime;
}

- (void) millionTimes:(void (^)(NSUInteger))block
{
    for (NSUInteger i = 0; i < 1000000; i++) {
        block(i);
    }
}

- (void)loadView
{
    UIView* rootView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
    UIView* subview = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 250, 500)];
    UIView* subSubview = [[UIView alloc] initWithFrame:CGRectMake(20, 10, 130, 190)];
    UIView* subSubSubview = [[UIView alloc] initWithFrame:CGRectMake(50, 10, 50, 100)];
    [subSubview addSubview:subSubSubview];
    [subview addSubview:subSubview];
    [rootView addSubview:subview];
    self.view = rootView;
}

- (void)test
{
    UIView* subview = [[self.view subviews] objectAtIndex:0];
    UIView* subSubview = [[subview subviews] objectAtIndex:0];
    UIView* subSubSubview = [[subSubview subviews] objectAtIndex:0];

    CGPoint testPoint = CGPointMake(10.0, 30.0);
    NSTimeInterval time;

    time = [self timeForBlock:^{ [self millionTimes: ^(NSUInteger i){
        [subSubSubview convertPoint:testPoint fromView:nil];
    } ]; } ];
    NSLog(@"convert from window point: %f", time);

    time = [self timeForBlock:^{ [self millionTimes: ^(NSUInteger i){
        CGPoint rootPoint = [subview convertPoint:testPoint toView:nil];
        [subSubSubview convertPoint:rootPoint fromView:nil];
    } ]; } ];
    NSLog(@"two-step convert through window coordinates: %f", time);

    time = [self timeForBlock:^{ [self millionTimes: ^(NSUInteger i){
        [subSubSubview convertPoint:testPoint fromView:subview];
    } ]; } ];
    NSLog(@"convert from subview point: %f", time);
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self test];
}

@end

I also tested it with 100 sub-sub-subviews, choosing a different one each time through, in case there was some caching going on, and the only difference could probably be accounted for in the extra overhead of the array lookups.

So, I'm not sure what to make of this, but I know how I'm going to use convertPoint:fromView: in the future! :-)

like image 69
big_m Avatar answered Oct 11 '22 20:10

big_m