2

I need to be able to save the frame after transformation of a UIImageView. In the below example, the original frame is when the image is added to the superview. The user then has the ability to rotate, scale and pan the image anywhere in the gray area (superview).

I take these images and save their coordinates to an NSDictionary (which is not the problem). The problem is that if I get the frame after the rotation, the frame is completely off. I need to be able to store the new frame with transform in the dictionary, so that when the user comes back to this view and the images are loaded, the frames and saved transformations are just like they intended.

Panning

CGPoint translation = [gestureRecognizer translationInView:[object superview]];
if (CGRectContainsPoint(self.frame, CGPointMake([object center].x + translation.x,  [object center].y + translation.y))) {
    [object setCenter:CGPointMake([object center].x + translation.x, [object center].y + translation.y)];
    [gestureRecognizer setTranslation:CGPointZero inView:[object superview]];
}

Rotating

self.transformRotation = CGAffineTransformRotate([[gestureRecognizer view] transform], [gestureRecognizer rotation]);

[gestureRecognizer view].transform = self.transformRotation;
if ( [gestureRecognizer rotation] != 0 ) {
    self.rotate = [gestureRecognizer rotation];
}
[gestureRecognizer setRotation:0];

Scaling

self.transformScale = CGAffineTransformScale([[gestureRecognizer view] transform], [gestureRecognizer scale], [gestureRecognizer scale]);
[gestureRecognizer view].transform = self.transformScale;
if ( [gestureRecognizer scale] != 1 ) {
    self.scale = [gestureRecognizer scale];
}
[gestureRecognizer setScale:1];

Using the Center point of the view keeps the image closer to the original location when it was saved, the first time it is loaded. Each time it is saved after that the position is the same, because the transform did not change during that session.

- (CGPoint)centerOnCanvas {
    CGPoint originalCenter = self.center;
    return originalCenter;
}

- (CGRect)frameOnCanvas {
    return CGRectMake(
        self.preTransformedFrame.origin.x,
        self.preTransformedFrame.origin.y,
        self.preTransformedFrame.size.width,
        self.preTransformedFrame.size.height
    );
}

- (CGRect)preTransformedFrame {
    CGAffineTransform currentTransform = self.transform;
    self.transform = CGAffineTransformIdentity;
    CGRect originalFrame = self.bounds;
    self.transform = currentTransform;
    return originalFrame;
}

enter image description here

UPDATE: Slightly off and a little larger than the original

enter image description here

WrightsCS
  • 50,551
  • 22
  • 134
  • 186

1 Answers1

4

According to Apple document for UIView's transform property

When the value of this property is anything other than the identity transform, the value in the frame property is undefined and should be ignored.

That's the reason why you can't get frame after changing transform.

As I understand, after rotating, scaling or panning you want to save current state to restore later. In my opinion, all you need to do it is saving transform and center of UIImageView each time they are changed. You don't need frame in this case.

For example, _transformTarget is your UIImageView, to save its current state you can use below method (Instead of saving in a NSDictionary, I use NSUserDefaults. You can change it to NSDictionary)

- (void)saveCurrentState {
    [[NSUserDefaults standardUserDefaults] setObject:NSStringFromCGAffineTransform(_transformTarget.transform) forKey:@"_transformTarget.transform"];
    [[NSUserDefaults standardUserDefaults] setObject:NSStringFromCGPoint(_transformTarget.center) forKey:@"_transformTarget.center"];
}

At the end of each handling gesture method, save current state by using saveCurrentState.

- (void)handlePanGesture:(UIPanGestureRecognizer*)gesture {
    CGPoint translation = [gesture translationInView:self.view];
    CGPoint newCenter = CGPointMake(_transformTarget.center.x + translation.x, _transformTarget.center.y + translation.y);

    if (CGRectContainsPoint(self.view.frame, newCenter)) {
        _transformTarget.center = newCenter;
        [gesture setTranslation:CGPointZero inView:self.view];

        [self saveCurrentState];  // Save current state when center is changed
    }
}

- (void)handleRotationGesture:(UIRotationGestureRecognizer*)gesture {
    _transformTarget.transform = CGAffineTransformRotate(_transformTarget.transform, gesture.rotation);
    gesture.rotation = 0;

    [self saveCurrentState];  // Save current state when transform is changed
}

- (void)handlePinchGesture:(UIPinchGestureRecognizer*)gesture {
    _transformTarget.transform = CGAffineTransformScale(_transformTarget.transform, gesture.scale, gesture.scale);
    gesture.scale = 1;

    [self saveCurrentState];  // Save current state when transform is changed
}

Now, the information about UIImageView is saved every time it's changed. At the next time user comes back, get info about center and transform from your dictionary and set them again.

- (void)restoreFromSavedState {
    NSString *transformString = [[NSUserDefaults standardUserDefaults] objectForKey:@"_transformTarget.transform"];
    CGAffineTransform transform = transformString ? CGAffineTransformFromString(transformString) : CGAffineTransformIdentity;

    NSString *centerString = [[NSUserDefaults standardUserDefaults] objectForKey:@"_transformTarget.center"];
    CGPoint center = centerString ? CGPointFromString(centerString) : self.view.center;

    _transformTarget.center = center;
    _transformTarget.transform = transform;
}

Result

For more detail, you can take a look at my sample repo https://github.com/trungducc/stackoverflow/tree/restore-view-transform

trungduc
  • 11,926
  • 4
  • 31
  • 55
  • This example makes a ton of sense now, seeing that I tried to concat rotation and scale... so applying just the final transformation and saving that looks like it is working for you. I tried to simplify my code down , but I am still having some issues with the coordinates being off. Wondering if that's because the gesture code is in my object (UIImageView in your case) class, and not the superview itself. I am basing it off of the superview frame, so I dont see why that would be an issue. – WrightsCS Mar 15 '19 at 18:53
  • @WrightsCS I’m not sure what you are talking about. Could you explain a little bit about “the coordinates being off”? I dont think that handle gestures in the object class will lead to any issue. If you want me to handle everything inside object class, I will try to make it – trungduc Mar 15 '19 at 19:15
  • See the updated image in the original question, slightly larger images and frame is a little off, but I checked the transform and center strings when saved and when retrieved, and they are the same, so not sure why the difference! – WrightsCS Mar 15 '19 at 19:55
  • @WrightsCS Updated repo by moving gestures implementation to object class. You can check it again. I think that your problem maybe because original frames before applying transform are different. – trungduc Mar 16 '19 at 04:52
  • would you suggest applying the original frame before applying the transform? I have tried that, as well as setting the center and a few other things. Its a bit annoying but hopefully get it figured out! – WrightsCS Mar 16 '19 at 16:37
  • @WrightsCS Did you try updated repo? I moved gestures to object class. Maybe it can help you figure out. – trungduc Mar 16 '19 at 16:40
  • Also, do you think auto layout would have some adverse affect? – WrightsCS Mar 16 '19 at 16:54
  • 1
    Yes, of course. In my opinion, you shouldnt use autolayout in this case. I think auto layout is the reason for your problem – trungduc Mar 16 '19 at 16:56
  • The Canvas view (the gray area), sits on top of the main view. The canvas has constraints to the main view, but inside the canvas there are no constraints. Do you think the outside constraints to the main view are affecting this? I don't see why they would, but I need these constraints and I have set `translatesAutoresizingMaskIntoConstraints` false on the object as well as the canvas view. I guess my only other option would be to set the frames manually on the main subviews, which would be a PITA! – WrightsCS Mar 16 '19 at 17:26
  • 1
    @WrightsCS I think you still can you constraints for canvas view. But try to call `layoutIfNeeded` before applying center and transform for objects inside to make sure canvas's frame won't be changed after (Which can lead to the problem) – trungduc Mar 17 '19 at 04:56