从设置加载UIView的变换和中心位置会导致不同的位置

5
我正在使用pan、pinch和rotate UIGestureRecognizers,让用户将特定的UI元素放置在他们想要的位置。使用这里的代码http://www.raywenderlich.com/6567/uigesturerecognizer-tutorial-in-ios-5-pinches-pans-and-more(或类似代码从这里http://mobile.tutsplus.com/tutorials/iphone/uigesturerecognizer/),都能为我提供所需的功能,让用户按照自己的意愿放置这些UI元素。
当用户退出“UI布局”模式时,我会保存UIView的变换和中心位置,如下所示:
NSString *transformString = NSStringFromCGAffineTransform(self.transform);
[[NSUserDefaults standardUserDefaults] setObject:transformString forKey:@"UItransform", suffix]];

NSString *centerString = NSStringFromCGPoint(self.center);
[[NSUserDefaults standardUserDefaults] setObject:centerString forKey:@"UIcenter"];

当我重新加载应用程序时,我会读取UIView的transform和center,如下所示:
NSString *centerString = [[NSUserDefaults standardUserDefaults] objectForKey:@"UIcenter"];
if( centerString != nil )
    self.center = CGPointFromString(centerString);

NSString *transformString = [[NSUserDefaults standardUserDefaults] objectForKey:@"UItransform"];

if( transformString != nil )
    self.transform = CGAffineTransformFromString(transformString);

UIView最终被正确旋转和缩放,但位置不正确。此外,在再次进入“UI布局”模式后,我不能总是使用各种手势抓住视图(好像显示的视图不是手势识别器所理解的视图?)

我还有一个重置按钮,将UIView的transform设置为标识,并将其center设置为从NIB加载时的任何位置。但即使在加载更改后的UIView的中心和变换后,重置也无效。UIView的位置不正确。

我的第一个想法是,由于那些手势代码示例会改变center,因此旋转必须围绕不同的中心发生(假设一些不可预测的移动、旋转和缩放序列)。由于我不想保存整个编辑序列(尽管如果我想在布局模式下拥有某些撤消功能,这可能很方便),所以我修改了UIPanGestureRecognizer处理程序以使用变换来移动它。一旦我弄清楚了这一点,我认为只保存变换就可以获得当前的位置和方向,无论事情发生的顺序如何。但这样做没有成功。我仍然以奇怪的方式得到一个位置。

所以我很困惑。如果一个UIView已经被移动和旋转到新的位置,我如何保存该位置和方向,以便稍后加载并将UIView恢复到应该在的位置?提前道歉,如果我没有标记正确或没有正确布局或者犯了其他stackoverflow的错误。这是我第一次在这里发布。编辑:我正在尝试迄今为止的两个建议。我认为它们实际上是相同的事情(一个建议保存frame,另一个建议保存origin,我认为这是frame.origin)。因此,现在从prefs代码中保存/加载包括以下内容。保存:
NSString *originString = NSStringFromCGPoint(self.frame.origin);
[[NSUserDefaults standardUserDefaults] setObject:originString forKey:@"UIorigin"];

在加载转换之前加载:

NSString *originString = [[NSUserDefaults standardUserDefaults] objectForKey:@"UIorigin"];
if( originString ) {
    CGPoint origin = CGPointFromString(originString);
    self.frame = CGRectMake(origin.x, origin.y, self.frame.size.width, self.frame.size.height);
}

我得到了相同的(或类似的 - 很难确定)结果。事实上,我添加了一个按钮来重新加载偏好设置,一旦视图旋转,该“重新加载”按钮将重复地移动UIView一些偏移量(就像frame或transform相对于自身一样 - 我确定这是一个线索,但我不确定它指向什么)。
编辑#2
这让我想到依赖于视图的frame。来自苹果http://developer.apple.com/library/ios/#documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/WindowsandViews/WindowsandViews.html#//apple_ref/doc/uid/TP40009503-CH2-SW6(我强调):
价值中心属性始终有效,即使已将缩放或旋转因子添加到视图的变换中。如果视图的变换不等于标识变换,则frame属性中的值被认为是无效的。

编辑 #3

好的,当我加载偏好设置时,一切看起来都很正常。UI面板的bounds矩形是{{0, 0},{506,254}}。在我的VC的viewDidLoad方法结束时,所有东西仍然看起来都很好。但是到实际显示事物的时候,bounds变成了其他的东西。例如:{{0, 0},{488.321,435.981}}(旋转和缩放后在其父视图中的大小)。如果我将边界重置为它应该有的大小,它就会移回原位。

程序上将边界重置为应有的大小很容易,但我实际上不确定何时去这样做!我本来以为要在viewDidLoad的末尾这样做,但此时bounds仍然正确。

编辑 #4

我尝试在initWithCoder(因为它来自NIB)中捕获self.bounds,然后在layoutSubviews中将self.bounds重置为捕获的CGRect。而那行得通。

但是这似乎非常笨拙并充满危险。这不可能是正确的方法。 (难道可以吗?) skram在下面的答案看起来很直接,但当应用程序重新加载时对我无效。

很棒的第一个问题!欢迎来到SO! - jrturton
谢谢,jrturton :) SO真是与众不同。我在几分钟内得到了两个答案(即使我还没有成功),这真是太棒了! - sbkp
你可能正在使用缩放和旋转变换的组合来进行旋转/缩放,而仅仅是移动中心来进行平移? - jrturton
这是我现在拥有的(也是一开始拥有的)。在这之间,我将翻译放入了变换中,因为我认为旋转和移动可能以依赖顺序的方式相互作用,而我想用变换将其全部呈现出来。但它只是让我的平移手势识别器代码变得更加复杂 :) - sbkp
5个回答

2
您需要保存frame属性。您可以使用NSStringFromCGRect()CGRectFromString()
加载时,先设置框架,然后应用变换。这是我在我的一个应用程序中所做的。
希望这可以帮助您。
更新:在我的情况下,我有可拖动的UIViews,可以应用旋转和调整大小。我使用NSCoding来保存和加载我的对象,以下是示例。
//encoding
....
[coder encodeCGRect:self.frame forKey:@"rect"];
// you can save with NSStringFromCGRect(self.frame);
[coder encodeObject:NSStringFromCGAffineTransform(self.transform) forKey:@"savedTransform"];

//init-coder
CGRect frame = [coder decodeCGRectForKey:@"rect"];
// you can use frame = CGRectFromString(/*load string*/);
[self setFrame:frame];
self.transform = CGAffineTransformFromString([coder decodeObjectForKey:@"savedTransform"]);

这个做法可以保存我的frame和transform,并在需要时加载它们。同样的方法也可以应用在NSStringFromCGRect()CGRectFromString()上。

更新2:针对你的情况,可以按照以下方式进行操作...

[self setFrame:CGRectFromString([[NSUserDefaults standardUserDefaults] valueForKey:@"UIFrame"])];
self.transform = CGAffineTransformFromString([[NSUserDefaults standardUserDefaults] valueForKey:@"transform"]);

假设您正在使用NSUserDefaultsUIFrame,以及transform键进行保存。

谢谢,skram。我认为你的建议本质上与dianz的相同,不是吗?他建议保存视图(可能是框架)的起点。无论是按照他的建议还是你的(就我理解的),我仍然得到了一个奇怪的结果。如果可以的话,您能否发布一段代码片段来指导我正确的方向? - sbkp
增加了一个例子,展示我如何使用相同的方法。 - skram
谢谢!我仍然得到相同的结果。但是让我在这里检查一些东西。我正在使用NSUserDefaults而没有NSCoding。因此,我的保存代码(例如)与您的不同,如下所示:[[NSUserDefaults standardUserDefaults] NSStringFromCGRect(self.frame) forKey:@"UIframe"]; [[NSUserDefaults standardUserDefaults] NSStringFromCGAffineTransform(self.transform) forKey:@"UItransform"];这似乎实现了与您相同的效果(不是吗)? - sbkp
进行了另一个编辑,以特别展示您的情况。可能会让您对我的场景感到困惑,抱歉。 - skram

2
我有困难复现你的问题。我使用了以下代码,它执行了以下操作:
  • 添加视图
  • 通过更改中心点移动视图
  • 使用变换缩放视图
  • 使用另一个变换将其旋转并连接到第一个变换上
  • 将变换和中心点保存为字符串
  • 添加另一个视图,并应用来自字符串的中心点和变换
这将导致两个视图完全相同的位置和姿势:
- (void)viewDidLoad
{
    [super viewDidLoad];

    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    view1.layer.borderWidth = 5.0;
    view1.layer.borderColor = [UIColor blackColor].CGColor;
    [self.view addSubview:view1];
    view1.center = CGPointMake(150,150);
    view1.transform = CGAffineTransformMakeScale(1.3, 1.3);
    view1.transform = CGAffineTransformRotate(view1.transform, 0.5);

    NSString *savedCentre = NSStringFromCGPoint(view1.center);
    NSString *savedTransform = NSStringFromCGAffineTransform(view1.transform);

    UIView *view2 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    view2.layer.borderWidth = 2.0;
    view2.layer.borderColor = [UIColor greenColor].CGColor;
    [self.view addSubview:view2];
    view2.center = CGPointFromString(savedCentre);
    view2.transform = CGAffineTransformFromString(savedTransform);

}

提供:

enter image description here

这符合我从文档中期望的内容,即所有变换都发生在中心点周围,因此不会受到影响。我唯一能想象到你无法将项目恢复到其以前状态的方式是,如果一些父视图与其自身的变换或不同的帧或完全不同的视图不同。但我无法从你的问题中看出来。

总之,你问题中的原始代码应该是可以工作的,所以还有其他问题!希望这个答案能帮助你缩小范围。


我碰巧尝试了类似的事情:保存视图的变换和中心并重新加载它们。这个东西不会移动(重现你的结果)。但是当我重新启动应用程序时,它会出现在其他地方。所以根据你关于父视图可能是问题的想法,也许当我在启动时读取首选项时,一些父视图中的状态还没有设置。我会深入研究并报告回来。非常感谢! - sbkp
好的,当我加载偏好设置时,一切看起来都很正常。UI面板的“bounds”矩形是{{0, 0},{506, 254}}。在我的VC的viewDidLoad方法结束时,一切仍然看起来都很好。但是到实际显示的时候,“bounds”矩形变成了其他东西。例如:{{0, 0},{488.321,435.981}}。如果我将边界重置为它应该有的值,它就会回到原位。因此,在程序中很容易这样做,但我实际上不确定何时应该这样做!我本以为要在viewDidLoad的末尾这样做,但此时“bounds”仍然是正确的。 - sbkp
2
当调用viewDidLoad时,几何结构(框架、边界等)尚未完全设置,但在viewWillAppear之前已经设置。因此,那就是检查几何结构并进行任何调整的地方。(不过,如果不了解更改了什么,重新设置边界可能会产生其他后果。)自动调整大小掩码可能会在视图出现之前更改框架/边界/中心。 - salo.dm
这就是我找到正确答案的原因,尽管答案并没有完全描述清楚。作为一个 SO 的新手,我应该如何处理这个问题?谢谢! - sbkp
如果没有一个答案真正解决了问题,但是有帮助,你可以为它们点赞。将实际的解决方案放在你自己的回答中(回答自己的问题也没关系),并将其标记为已接受。这样,未来的读者遇到相同的问题就可以直接获得解决方案。 - jrturton
好的,再次感谢你的帮助。我已经发布了我的解决方案。 - sbkp

1

你还应该保存UIView的位置信息,

CGPoint position = CGPointMake(self.view.origin.x, self.view.origin.y)

NSString _position = NSStringFromCGPoint(position);

// Do the saving

谢谢,dianz。不幸的是,我还没有让它工作。我在我的帖子中(“EDIT”后)放置了我对你意思的理解。你能否看一下我是否理解得正确?(保存self.frame.origin)或者如果可以的话,你是否能够发布一个代码片段,以指导我走向正确的方向? - sbkp
这甚至不能编译,与中心属性没有任何区别。 - jrturton

0

感谢所有提供答案的人!他们的贡献最终让我得出以下结论:

问题似乎是在启动时从首选项加载变换后,bounds CGRect被重置了,但在实时修改首选项时没有进行重置。

我认为有两个解决方案。一个是从layoutSubviews而不是viewDidLoad中首先加载首选项。在调用layoutSubviews后,bounds似乎不会发生任何变化。

然而,基于我的应用程序中的其他原因,从视图控制器的viewDidLoad加载首选项更加方便。所以我使用的解决方案是:

// UserTransformableView.h
@interface UserTransformableView : UIView {
    CGRect defaultBounds;
}

// UserTransformableView.m
- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if( self ) {
        defaultBounds = self.bounds;
    }
    return self;
}

- (void)layoutSubviews {
    self.bounds = defaultBounds;
}

0

我不确定发生了什么事情,但这里有一些可能有所帮助的想法。

1- skram 的解决方案似乎可行,但你要保存的是 bounds 而不是 frame。(请注意,如果没有旋转,中心和边界定义了框架。因此,设置这两个属性与设置框架相同。)从你提供的 View Programming Guide for IOS 中可以看到:

重要提示 如果视图的 transform 属性不是 identity transform,则该视图的 frame 属性的值是未定义的,必须忽略。在对视图应用变换时,必须使用视图的 bounds 和 center 属性来获取视图的大小和位置。任何子视图的 frame 矩形仍然有效,因为它们是相对于视图的 bounds。

2- 另一个想法。当您重新加载应用程序时,可以尝试以下操作:

  • 首先,将视图的变换设置为标识变换。
  • 然后,将视图的边界和中心设置为保存的值。
  • 最后,将视图的变换设置为保存的变换。

根据您的应用程序重新启动的位置,它可能会重新启动并保留一些旧的几何信息。我真的不认为这会改变任何事情,但尝试起来很容易。
更新:经过一些测试,似乎这不会产生任何影响。更改变换似乎不会更改边界或中心(尽管它确实更改了框架)。

3- 最后,您可以通过重写捏合手势识别器以在bounds上操作而不是transform上来节省一些麻烦。(再次使用bounds而不是frame,因为早期的旋转可能使frame无效。)通过这种方式,变换仅用于旋转,我认为没有其他方法可以进行旋转而不重新绘制。

来自同一指南,苹果的建议是:

通常情况下,当您想要实现动画效果时,会修改视图的 transform 属性。例如,您可以使用此属性创建一个围绕其中心点旋转的视图动画。您不应该使用此属性对视图进行永久更改,例如在其父视图的坐标空间内修改其位置或大小。对于这种类型的更改,您应该改为修改视图的 frame 矩形。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接