简体   繁体   中英

How to find the center of a UIView after superviews have been transformed?

NOTE: code written in browser; probably not perfectly accurate, but it should give the general idea.

I've got a stack of views, something like this:

CGRect theFrame = CGRectMake(0, 0, 10, 10);
UIView *v1 = [UIView alloc] initWithFrame: theFrame];
UIView *v2 = [UIView alloc] initWithFrame: theFrame];
UIView *v3 = [UIView alloc] initWithFrame: theFrame];

// Set all the background colors, so I can see them: snip
// set all the clipsToBounds = NO, so I can place however I want: snip

v2.center = CGPointMake(100, 80);
[v1 addSubview: v2];

v3.center = CGPointMake (57, 42);
[v2 addSubview: v3];

v1.center = CGPointMake (193, 44);
[self.view addSubview: v1];

// etc., time passes, user presses TEST button

CGAffineTransform sXfrm = CGAffineTransformMakeScale(3.5, 4.7);
CGAffineTransform rXfrm = CGAffineTransformMakeRotation(M_PI / 3.);
v1.transform = CGAffineTransformConcat(sXfrm, rXfrm);

// etc., rotate & scale v2, while we're at it.  snip
// Leave v3 unrotated & unscaled.

Ok, at this point, everything is displaying on the screen exactly as desired. My question is:

Where (that is: where, in self.view's coordinate space) is v3.center ?

Here's some code that gives the CLOSE answer, but not-quite right, and I can't seem to figure out what's wrong with it:

CGRect testRect = CGRectMake(0, 0, 20, 20);
UIView *testView = [[UIView alloc] initWithFrame: testRect];
testView.backgroundColor = [UIColor greebColor];
[self.view addSubview: testView];

CGPoint center = v1.center;

#if 1   // Apply Transform
  CGPoint ctr2 = CGPointApplyAffineTransform(v2.center, v1.transform);

#else   // use layer
  CGPoint ctr2 = CGPointMake ((v2.layer.frame.origin.x + (v2.layer.frame.size.width  / 2.)),
                               (v2.layer.frame.origin.y + (v2.layer.frame.size.height / 2.)) );
  ctr2 = CGPointApplyAffineTransform(ctr2, v1.transform);

#endif

center.x += ctr2.x;
center.y += ctr2.y;

CGPoint ctr3 = CGPointApplyAffineTransform(v3.center, CGAffineTransformConcat(v2.transform, v1.transform));
center.x += ctr3.x;
center.y += ctr3.y;
testView.center = center;  // I want this to lay on top of v3
[self.view addSubview: testView];

NOTE: In my actual code, I put in-between test-views at all the intermediate centers. What I get is: testView1 (center) is correct, testView2 (ctr2) is off by a little, testView3 (ctr3) is off by a little more.

The #if is because I was experimenting with using ApplyAffine vs layer. Turns out they both give the same result, but it's a tad off.

Hopefully clarifying image:

在此输入图像描述

You can use the UIView's convert points methods:

convertPoint:toView:
convertPoint:fromView:
convertRect:toView:
convertRect:fromView:

However once you apply a transformation, you should stop using frames and use center + bounds instead (which might be the reason your code is not working), from apple docs (for frame):

Warning: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.

The only other thing you have to be conscious about is, which view invoques the convert to what other view since the results change. (source coordinate system -> target coordinate system.)

EDIT:

Thanks to this answer and Chiquis' comments in the question, my (Olie's) final (working) code looks like this:

CGRect testRect = CGRectMake(0, 0, 20, 20);
UIView *testView[3];
for (int ii = 0 ; ii < 3 ; ++ii)
{
    testView[ii] = [[UIView alloc] initWithFrame: testRect];
    testView[ii].backgroundColor = [UIColor redColor];
    [self.view addSubview: testView[ii]];
}

testView[0].center =  v0.center;    
testView[1].center = [v0 convertPoint: v1.center toView: self.view];
testView[2].center = [v1 convertPoint: v2.center toView: self.view];

To clarify some, v1 is a subview of v0, and v2 is a subview of v1; this dictates the receivers in the covertPoint: calls.

I try to let iOS do the heavy lifting for me when I need to transform points between different layers.

CGPoint point = v3.center;
CGPoint ctr3 = [v3.layer.presentationLayer convertPoint:point toLayer:self.layer.presentationLayer];

The presentation layer object represents the state of the layer as it currently appears onscreen. This can help avoid timing issues if animations are involved.

Just noticed the comment that came in while I was composing: yes, I use this to account for rotation transforms similar to what you describe.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM