简体   繁体   English

泛码更新中心后,UIView框架和中心是否不相关?

[英]UIView frame and center not correlated after pan code updates center?

在此处输入图片说明

So I've got a stripped down ViewController example that reproduces this behavior which I can't seem to wrap my head around. 因此,我得到了一个精简的ViewController示例,该示例再现了这种行为,而我似乎无法缠住我的头。 I'm using a UIPanGestureRecognizer with some code that correctly allows a UIView to be moved around the window. 我正在使用带有一些代码的UIPanGestureRecognizer,该代码正确地允许UIView在窗口中移动。 However, if I update the frame, even to itself, the view jumps to an unexpected location, presumably based on the view's center. 但是,如果我更新框架,甚至更新框架本身,视图也会跳到意外位置,大概是基于视图的中心。

I have two questions about this code. 我对此代码有两个问题。 First, why is the view's center the position of the last touch (basically the anchorPoint mapped to the view's bounds), and apparently not in any way related to the center of the view's frame? 首先,为什么视图的中心位于最后一次触摸的位置(基本上是将anchorPoint映射到视图的边界),并且显然与视图框架的中心无关?

Second, why when changing the frame property to the current value does the view move? 其次,为什么在将frame属性更改为当前值时视图会移动? It seems to me that the view's center and frame are only partly correlated and I'm not sure what I'm missing. 在我看来,视图的中心和框架只是部分相关,我不确定我缺少什么。

This example just creates a basic test view controller in the app delegate: 此示例仅在应用程序委托中创建一个基本的测试视图控制器:

TestViewController *vc = [[TestViewController alloc] init];
self.window.rootViewController = vc;

The TestViewController.m: TestViewController.m:

#import "TestViewController.h"
#import <QuartzCore/QuartzCore.h>

@implementation TestViewController

- (void)loadView
{
    self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1024, 748)];
    self.view.backgroundColor = UIColor.redColor;

    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(onPan:)];
    pan.maximumNumberOfTouches = 1;
    [self.view addGestureRecognizer:pan];
}

- (void)onPan:(UIPanGestureRecognizer*)recognizer
{
    if (recognizer.state == UIGestureRecognizerStateBegan) {
        UIView *view = recognizer.view;
        CGPoint locationInView = [recognizer locationInView:view];
        CGPoint locationInSuperview = [recognizer locationInView:view.superview];

        view.layer.anchorPoint = CGPointMake(locationInView.x / view.bounds.size.width, locationInView.y / view.bounds.size.height);
        view.center = locationInSuperview;
    }

    if (recognizer.state == UIGestureRecognizerStateBegan || recognizer.state == UIGestureRecognizerStateChanged) {

        CGPoint translation = [recognizer translationInView:[recognizer.view superview]];
        [recognizer.view setCenter:CGPointMake(recognizer.view.center.x + translation.x, recognizer.view.center.y+translation.y)];
        [recognizer setTranslation:CGPointZero inView:[recognizer.view superview]];
    }

    if(recognizer.state == UIGestureRecognizerStateEnded) {

        NSLog(@"AnchorPoint: %@", NSStringFromCGPoint(self.view.layer.anchorPoint));
        NSLog(@"Center: %@", NSStringFromCGPoint(self.view.center));
        NSLog(@"Frame: %@", NSStringFromCGRect(self.view.frame));

        self.view.frame = self.view.frame;
    }
}

@end

When the pan gesture ends, if I remove the following line, the pan works as expected: 平移手势结束后,如果我删除以下行,则平移将按预期工作:

self.view.frame = self.view.frame;

I really don't see how that line can cause the view to move around at all, unless the previous center property didn't "fully stick" or something. 我真的看不到这条线怎么会导致视图移动,除非以前的center属性没有“完全粘住”或其他东西。 Furthermore, I don't understand the value of the center property, since it appears to be the last place touched, and not the center of the view at all. 此外,我不了解center属性的值,因为它似乎是最后触摸的地方,而不是视图的中心。 Example log: 日志示例:

AnchorPoint: {0.204427, 0.748506}
Center: {625, 218.5}
Frame: {{14, -34}, {768, 1004}}

What I am trying to implement here, in the long run, is a view which can scale, but when scrolled beyond the edges will snap back (like a UIScrollView would). 从长远来看,我试图在这里实现的是一个可以缩放的视图,但是当滚动到边缘之外时,它将回弹(就像UIScrollView一样)。

If you remove these two lines 如果删除这两行

    view.layer.anchorPoint = CGPointMake(locationInView.x / view.bounds.size.width, locationInView.y / view.bounds.size.height);
    view.center = locationInSuperview;

Then your view panning behaves as you would expect. 然后,您的视图平移将达到您的预期。 Why are you changing the layer's anchorpoint anyway? 为什么仍要更改图层的定位点? IN general your code seems over-complicated for what you are trying to achieve. 通常,您要实现的代码看起来过于复杂。 See David Rönnqvist, understanding the anchor point as to how changing the anchorpoint has (often unintended) implications for the view's frame property. 请参阅DavidRönnqvist, 了解锚点了解如何更改锚点会对视图的帧属性产生(通常是意外的)影响。

Moreover, it is usually a good idea to leave the top-level view alone (the view controller's self.view ) and manipulate subviews. 此外,通常最好不要self.view顶级视图(视图控制器的self.view )并操作子视图。 That way you know how everything relates to the viewController's view without wondering what is going on with respect to the UIWindow. 这样,您就知道一切都与viewController的视图相关联,而不必怀疑UIWindow的状况。 When you rotate the device, a rotation transform is applied to that top-level view, which doesn't help when you are inspecting frame data. 旋转设备时,旋转变换将应用于该顶层视图,这在检查框架数据时无济于事。 It's subviews' properties aren't affected by that transform. 子视图的属性不受该转换的影响。

Your code behaves as you expect it to in untransformed portrait mode, and only throws up strange results when in landscape or portrait-upsidedown. 您的代码在未转换的纵向模式下的行为与预期的一样,并且仅在横向或纵向倒置时会抛出奇怪的结果。 These are the cases where the view has had a rotational transform applied. 在这些情况下,视图已应用旋转变换。 Apple says that the frame's origin property is undefined when a transform has been applied to the view, so when you do this: 苹果公司说,将变换应用于视图后,框架的origin属性是未定义的,因此在执行此操作时:

self.view.frame = self.view.frame

you are assigning a property in an undefined state. 您正在分配处于未定义状态的属性。 Expect the unexpected. 期待意外。

You could change your viewloading code thus: 您可以这样更改视图加载代码:

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIView* subview = [[UIView alloc] initWithFrame:
                       CGRectMake(0, 0
                                  , self.view.bounds.size.width
                                  , self.view.bounds.size.height)];
    [self.view addSubview:subview];
    subview.backgroundColor = UIColor.redColor;
    [subview setAutoresizingMask:UIViewAutoresizingFlexibleWidth 
                                |UIViewAutoresizingFlexibleHeight];
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] 
                                     initWithTarget:self 
                                             action:@selector(onPan:)];
    pan.maximumNumberOfTouches = 1;
    [subview addGestureRecognizer:pan];
}

Here we do not override -loadView, leave that alone to create the top-level view automatically, then we implement a subview in viewDidLoad instead. 在这里,我们不重写-loadView,而让它自动创建顶层视图,然后在viewDidLoad实现一个子视图。 Now your test app behaves correctly. 现在,您的测试应用程序可以正常运行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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