Search code examples
iosswiftuiviewuigesturerecognizeruianimation

Change view to a circle on swipe gesture, iOS, Swift


I am trying to reduce a view to a circle on swipe gesture (in any direction, fast or slow), similar to the experience on WhatsApp videoCall view. See images below, to get an idea of what I am trying to achieve.

I believe I need to use swipe gesture to achieve this, I've added a swipe gesture to my videoView, I am not sure what to do next.

In viewDidLoad I have the below

videoView.addGestureRecognizer(UISwipeGestureRecognizer(target: self, action: #selector(self.minimiseView) ))

I think I need to use gesture location ? and I also need to set the corner radius that increases with the swipe. Could some one please advise how I could achieve this ?

func minimiseView(gesture: UISwipeGestureRecognizer){
        let location = gesture.location(in: self.view)
    }

enter image description here


Solution

  • You basically want to do the following steps:

    1. Capture the starting gesture location
    2. During swipe, measure distance from the original swipe
    3. Use this distance to increase the corner radius of your camera view
      • e.g. set cornerRadius = distanceSwiped
    4. Once the corner radius has reached a certain amount (and the view is a circle), capture the current gesture location
    5. Use this value to start tracking the motion again and use it to reduce the width of the view
    6. When the view is small enough dismiss it

    Here’s a basic setup for how you might accomplish this:

    enum VideoDismissState {
        case cornerRadiusChanging, sizeChanging, complete
    }
    
    var initialGesturePosition: CGPoint = .zero
    var maxCornerRadiusGesturePosition: CGPoint = .zero
    var dismissState: VideoDismissState = .complete
    
    func minimiseView(_ gesture: UISwipeGestureRecognizer) {
        let location = gesture.location(in: videoView)
    
        switch gesture.state {
        case .began:
            initialGesturePosition = gesture.location(in: videoView)
            dismissState = .cornerRadiusChanging
        case .changed:
            let currentPosition = gesture.location(in: videoView)
    
            switch dismissState {
            case cornerRadiusChanging:
                let swipeDistance = distance(between: initialGesturePosition, and: currentPosition)
                // play around with this formula to see what feels right
                videoView.layer.cornerRadius = swipeDistance / 2
    
                // at a certain point, switch to changing the size
                if swipeDistance >= videoView.width / 2 {
                    maxCornerRadiusGesturePosition = currentPosition
                    dismissState = .sizeChanging
                }
            case sizeChanging:
                let swipeDistance = distance(between: maxCornerGesturePosition, and: currentPosition)
                // again try different things to see what feels right here
                let scaleFactor = 50 / swipeDistance
    
                videoView.layer.transform = CGAffineTransform(scaledX: scaleFactor, y: scaleFactor
    
                if scaleFactor <= 0.2 {
                    dismissState = .complete
                }
            case complete:
                // reset values
                initialGesturePosition = .zero
                maxCornerRadiusGesturePosition = .zero
    
                // dismiss videoView
                // for example: videoView.isHidden = true OR videoView.removeFromSuperview()
            }
        case .ended:
            // if the gesture ends too soon you may want to animate the view back to full screen
        }
    }
    
    /// Measure distance between two points
    func distance(between first: CGPoint, and second: CGPoint) -> CGFloat {
        return sqrt((first.x - second.x) ^ 2 + (first.y - second.y) ^ 2)
    }
    

    This may not work entirely as I haven’t tested it but the basic idea should be enough to get you started.