@objc func panDidFire(panner: UIPanGestureRecognizer) { let translation = panner.translation(in: view) panner.setTranslation(CGPoint.zero, in: view) if panner.state == .began { // Animate the appearing of the delete button UIView.animate(withDuration: Constants.animationDuration) { self.showDeleteView() } } if panner.state == .changed { let newButtonView = UIView(frame: CGRect(x: self.button.frame.origin.x + translation.x, y: self.button.frame.origin.y + translation.y, width: Constants.buttonWidth, height: Constants.buttonHeight)) // Move the delete button according to the fab button let ratio: CGFloat = 0.1 var tempDeleteCenter = self.deleteView.center tempDeleteCenter.x += translation.x * ratio let yPos = tempDeleteCenter.y + translation.y * ratio let yMaxPos = UIScreen.main.bounds.size.height - Constants.offset - self.deleteView.bounds.size.height / 2 tempDeleteCenter.y = yPos > yMaxPos ? yMaxPos : yPos // Check if the fab intersect with delete button let diffX = Constants.deleteWidth - (Constants.buttonWidth / 2) let diffY = Constants.deleteHeight - (Constants.buttonHeight / 2) let deleteZone = self.deleteView.frame.insetBy(dx: diffX, dy: diffY) var deleteTmpNewButtonOrigin = newButtonView.frame.origin deleteTmpNewButtonOrigin.x += (Constants.buttonWidth / 2) deleteTmpNewButtonOrigin.y += (Constants.buttonHeight / 2) let isIn = deleteZone.contains(newButtonView.frame.origin) if isIn { var tempNewButtonDeleteCenter = tempDeleteCenter tempNewButtonDeleteCenter.x -= (Constants.buttonWidth / 2) tempNewButtonDeleteCenter.y -= (Constants.buttonHeight / 2) newButtonView.frame.origin = tempNewButtonDeleteCenter } self.button.frame = newButtonView.frame self.deleteView.center = tempDeleteCenter // Calculate the final fab position during the drag self.button.center = isIn ? self.deleteView.center : newButtonView.center } if panner.state == .ended || panner.state == .cancelled { // The delete zone has to be calculated before reseting the delete view position ! let diffX = Constants.deleteWidth - Constants.buttonWidth let diffY = Constants.deleteHeight - Constants.buttonHeight let deleteZone = self.deleteView.frame.insetBy(dx: diffX, dy: diffY) // Now, we can safely update the delete view position // (not in animation, otherwise it does not work) self.deleteView.center = CGPoint(x: self.deletingPoint.x, y: self.deletingPoint.y) UIView.animate(withDuration: Constants.animationDuration) { self.snapButtonToSocket(deleteZone: deleteZone) self.hideDeleteView() } } } private func snapButtonToSocket(deleteZone: CGRect) { if deleteZone.contains(button.center) { button.center = deletingPoint deletePlayer() self.view.isHidden = true } else { let bestSocket = getBestSocket(deleteZoneCenter: deletingPoint) if bestSocket == deletingPoint { button.center = deletingPoint deletePlayer() } else { button.center = bestSocket PlayerManager.shared.smallPlayerLastLocation = bestSocket } } } private func getPossiblePoints() -> [CGPoint] { var possiblePoints: [CGPoint] = [] // the current y position var yPos: CGFloat = button.center.y let minY = Constants.offset + Constants.buttonHeight / 2 let maxY = UIScreen.main.bounds.size.height - Constants.offset - Constants.buttonHeight / 2 // Check if fab is out of limits if button.center.y < minY { // limit top yPos = minY } else if button.center.y > maxY { // limit bottom yPos = maxY } // limit right possiblePoints.append(CGPoint(x: UIScreen.main.bounds.size.width - Constants.offset - Constants.buttonWidth / 2, y: yPos)) // limit left possiblePoints.append(CGPoint(x: Constants.offset + Constants.buttonWidth / 2, y: yPos)) // return the possible points return possiblePoints } private func getBestSocket(deleteZoneCenter: CGPoint) -> CGPoint { var bestSocket = CGPoint.zero var distanceToBestSocket = CGFloat.infinity var possiblePoints: [CGPoint] = getPossiblePoints() possiblePoints.append(deleteZoneCenter) for socket in possiblePoints { let distance = hypot(button.center.x - socket.x, button.center.y - socket.y) if distance < distanceToBestSocket { distanceToBestSocket = distance bestSocket = socket } } return bestSocket }