iosswiftuiviewcgaffinetransformcgaffinetransformscale

Resizing UIView using CGAffineTransform scale doesn't update frame


I'm trying to resize a UIView (Parent) with a few subviews in it using CGAffineTransform scale. I'm resizing the parent by dragging it from one corner using pan gesture. The resizing works as expected but if I try to resize it again, it jumps back to the initial frame. It's like it never knew it was resized.

These are the steps I am doing so far:

1.- Just when the pan gesture begins I get the initial frame and the touch location in superview:

if gesture.state == .began {
        
        //We get all initial values from the first touch
        initialFrame = self.frame;
        touchStart = gesture.location(in: superview)
    }

2.- Then I go to the handle I'm dragging (Top right in this case), set the anchor point, calculate deltas (Initial touch - gesture distance traveled), calculate new frame, scales, and apply transform.

    case topRight:
        
        if gesture.state == .began {
            self.setAnchorPoint(anchorPoint: CGPoint(x: 0, y: 1))
        }

        
        let deltaX = -1 * (touchStart.x - gesture.location(in: superview).x)
        let deltaY = 1 * (touchStart.y - gesture.location(in: superview).y)
        
        let newWidth = initialFrame.width + deltaX;
        let newHeight = initialFrame.height + deltaY;
        
        let scaleX:CGFloat = newWidth / initialFrame.width;
        let scaleY:CGFloat = newHeight / initialFrame.height;

        self.transform = CGAffineTransform.identity.scaledBy(x: scaleX, y: scaleY)

3.- Finally I reset the anchor point to the middle of the UIView.

        if gesture.state == .ended {
            self.setAnchorPoint(anchorPoint: CGPoint(x: 0.5, y: 0.5))
         
 
        }

I attached a gif where you can see the UIView is resized from the top right handle. When I try to resize it again, it jumps back to the initial frame. (It seems that the video is restarted, but this is the jump)

enter image description here

what am I missing? do I need to update something else? Thank you all!


Solution

  • So the answer is actually quite simple, this following line of code was incorrect:

    self.transform = CGAffineTransform.identity.scaledBy(x: scaleX, y: scaleY)
    

    Here I am applying the scaling to the identity transform which is always the original size.

    Just by changing this to:

    self.transform = self.initialTransform.scaledBy(x: scaleX, y: scaleY)
    

    Now I am applying the scaling to the current transform which is always saved when the gesture begins.

    @objc func handleUserPan(gesture:UIPanGestureRecognizer) {
            
            if gesture.state == .began {
                
    
                self.initialTransform = self.transform;
                self.touchStart = gesture.location(in: resizableSelf.superview);
            }
            
            switch gesture.view! {
            case self.topRight: //This is just the circle view
    
                if gesture.state == .began {
                    self..setAnchorPoint(anchorPoint: CGPoint(x: 1, y: 1))
                }
                
                
                let deltaX = 1 * (self.touchStart.x - gesture.location(in: superview).x)
                let deltaY = 1 * (self.touchStart.y - gesture.location(in: superview).y)
                
                let newWidth = self.initialFrame.width + deltaX;
                let newHeight = self.initialFrame.height + deltaY;
                
                let scaleX:CGFloat = newWidth / self.initialFrame.width;
                let scaleY:CGFloat = newHeight / self.initialFrame.height;
    
                self.transform = self.initialTransform.scaledBy(x: scaleX, y: scaleY)
                
            }
            default:()
            }
    
            gesture.setTranslation(CGPoint.zero, in: self)
            self.updateDragHandles()
    
    
            if gesture.state == .ended {
                self.setAnchorPoint(anchorPoint: CGPoint(x: 0.5, y: 0.5))
            }
        }
    

    Hopefully this can be helpful for those who would like to scale a UIView similar to like Photoshop does with it's layers. It even works if the subviews were rotated.

    Keep in mind that once the view is transformed, you cannot set the frame value anymore. That's why by having all views inside a superView is more convenient so it takes care of all resizing.

    The goal was to replicate the same behavior that Photoshop does when resizing layers.