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

634 views Asked by At

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

1

There are 1 answers

3
Paolo On BEST ANSWER

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.