From 7f2fec756d8095000dd6976e2cc4f6906cf9d32e Mon Sep 17 00:00:00 2001 From: Alex Bumbu Date: Mon, 23 May 2022 15:06:36 +0300 Subject: [PATCH] fix(ios) fix PiP resizing and positioning Co-authored-by: Tobias Marschall --- .../DragGestureController.swift | 46 +++++---- .../PiPViewCoordinator.swift | 93 +++++++++++-------- 2 files changed, 83 insertions(+), 56 deletions(-) diff --git a/ios/sdk/src/picture-in-picture/DragGestureController.swift b/ios/sdk/src/picture-in-picture/DragGestureController.swift index a6938ddae..ea59f6eb0 100644 --- a/ios/sdk/src/picture-in-picture/DragGestureController.swift +++ b/ios/sdk/src/picture-in-picture/DragGestureController.swift @@ -17,14 +17,14 @@ import UIKit final class DragGestureController { - var insets: UIEdgeInsets = UIEdgeInsets.zero + var currentPosition: PiPViewCoordinator.Position? = nil private var frameBeforeDragging: CGRect = CGRect.zero private weak var view: UIView? private lazy var panGesture: UIPanGestureRecognizer = { - return UIPanGestureRecognizer(target: self, - action: #selector(handlePan(gesture:))) + UIPanGestureRecognizer(target: self, + action: #selector(handlePan(gesture:))) }() func startDragListener(inView view: UIView) { @@ -40,7 +40,7 @@ final class DragGestureController { } @objc private func handlePan(gesture: UIPanGestureRecognizer) { - guard let view = self.view else { return } + guard let view = view else { return } let translation = gesture.translation(in: view.superview) let velocity = gesture.velocity(in: view.superview) @@ -65,7 +65,7 @@ final class DragGestureController { let velocityMagnitude = magnitude(vector: velocity) let animationDuration = 0.5 let initialSpringVelocity = - velocityMagnitude / distanceMagnitude / CGFloat(animationDuration) + velocityMagnitude / distanceMagnitude / CGFloat(animationDuration) frame.origin = CGPoint(x: finalPos.x, y: finalPos.y) @@ -75,8 +75,7 @@ final class DragGestureController { initialSpringVelocity: initialSpringVelocity, options: .curveLinear, animations: { - view.frame = frame - }, completion: nil) + view.frame = frame }) default: break @@ -85,9 +84,11 @@ final class DragGestureController { private func calculateFinalPosition() -> CGPoint { guard - let view = self.view, + let view = view, let bounds = view.superview?.frame - else { return CGPoint.zero } + else { + return CGPoint.zero + } let currentSize = view.frame.size let adjustedBounds = bounds.inset(by: insets) @@ -109,19 +110,26 @@ final class DragGestureController { goUp = location.y < bounds.midY } - let finalPosX: CGFloat = - goLeft - ? adjustedBounds.origin.x - : bounds.size.width - insets.right - currentSize.width - let finalPosY: CGFloat = - goUp - ? adjustedBounds.origin.y - : bounds.size.height - insets.bottom - currentSize.height + if (goLeft && goUp) { + currentPosition = .upperLeftCorner + } - return CGPoint(x: finalPosX, y: finalPosY) + if (!goLeft && goUp) { + currentPosition = .upperRightCorner + } + + if (!goLeft && !goUp) { + currentPosition = .lowerRightCorner + } + + if (goLeft && !goUp) { + currentPosition = .lowerLeftCorner + } + + return currentPosition!.getOriginIn(bounds: adjustedBounds, size: currentSize) } private func magnitude(vector: CGPoint) -> CGFloat { - return sqrt(pow(vector.x, 2) + pow(vector.y, 2)) + sqrt(pow(vector.x, 2) + pow(vector.y, 2)) } } diff --git a/ios/sdk/src/picture-in-picture/PiPViewCoordinator.swift b/ios/sdk/src/picture-in-picture/PiPViewCoordinator.swift index 79a455604..a452dbe18 100644 --- a/ios/sdk/src/picture-in-picture/PiPViewCoordinator.swift +++ b/ios/sdk/src/picture-in-picture/PiPViewCoordinator.swift @@ -19,16 +19,23 @@ import UIKit public typealias AnimationCompletion = (Bool) -> Void public protocol PiPViewCoordinatorDelegate: class { - + func exitPictureInPicture() } /// Coordinates the view state of a specified view to allow /// to be presented in full screen or in a custom Picture in Picture mode. /// This object will also provide the drag and tap interactions of the view -/// when is presented in Picure in Picture mode. +/// when is presented in Picture in Picture mode. public class PiPViewCoordinator { - + + public enum Position { + case lowerRightCorner + case upperRightCorner + case lowerLeftCorner + case upperLeftCorner + } + /// Limits the boundaries of view position on screen when minimized public var dragBoundInsets: UIEdgeInsets = UIEdgeInsets(top: 25, left: 5, @@ -39,23 +46,15 @@ public class PiPViewCoordinator { } } - public enum Position { - case lowerRightCorner - case upperRightCorner - case lowerLeftCorner - case upperLeftCorner - } - - public var initialPositionInSuperview = Position.lowerRightCorner - + private let initialPositionInSuperView: Position = .lowerRightCorner + // Unused. Remove on the next major release. @available(*, deprecated, message: "The PiP window size is now fixed to 150px.") public var c: CGFloat = 0.0 - + public weak var delegate: PiPViewCoordinatorDelegate? private(set) var isInPiP: Bool = false // true if view is in PiP mode - private(set) var view: UIView private var currentBounds: CGRect = CGRect.zero @@ -66,6 +65,13 @@ public class PiPViewCoordinator { public init(withView view: UIView) { self.view = view + // Required because otherwise the view will not rotate correctly. + view.autoresizingMask = [.flexibleWidth, .flexibleHeight] + + // Otherwise the enter/exit pip animation looks odd + // when pip window is bottom left, top left or top right, + // because the jitsi view content does not animate, but jumps to the new size immediately. + view.clipsToBounds = true } /// Configure the view to be always on top of all the contents @@ -74,7 +80,9 @@ public class PiPViewCoordinator { public func configureAsStickyView(withParentView parentView: UIView? = nil) { guard let parentView = parentView ?? UIApplication.shared.keyWindow - else { return } + else { + return + } parentView.addSubview(view) currentBounds = parentView.bounds @@ -109,6 +117,9 @@ public class PiPViewCoordinator { /// around screen, and add a button of top of the view to be able to exit mode public func enterPictureInPicture() { isInPiP = true + // Resizing is done by hand when in pip. + view.autoresizingMask = [] + animateViewChange() dragController.startDragListener(inView: view) dragController.insets = dragBoundInsets @@ -125,6 +136,9 @@ public class PiPViewCoordinator { /// exit pip button, and disable the drag gesture @objc public func exitPictureInPicture() { isInPiP = false + // Enable autoresizing again, which got disabled for pip. + view.autoresizingMask = [.flexibleWidth, .flexibleHeight] + animateViewChange() dragController.stopDragListener() @@ -136,7 +150,7 @@ public class PiPViewCoordinator { let exitSelector = #selector(toggleExitPiP) tapGestureRecognizer?.removeTarget(self, action: exitSelector) tapGestureRecognizer = nil - + delegate?.exitPictureInPicture() } @@ -144,6 +158,12 @@ public class PiPViewCoordinator { /// screen size changes public func resetBounds(bounds: CGRect) { currentBounds = bounds + + // Is required because otherwise the pip window is buggy when rotating the device. + // When not in pip then autoresize will do the job. + if (isInPiP) { + view.frame = changeViewRect() + } } /// Stop the dragging gesture of the root view @@ -169,7 +189,6 @@ public class PiPViewCoordinator { } // MARK: - Interactions - @objc private func toggleExitPiP() { if exitPiPButton == nil { // show button @@ -186,44 +205,28 @@ public class PiPViewCoordinator { } } - // MARK: - Size calculation - - private func animateViewChange() { + func animateViewChange() { UIView.animate(withDuration: 0.25) { self.view.frame = self.changeViewRect() - self.view.setNeedsLayout() } } private func changeViewRect() -> CGRect { let bounds = currentBounds - guard isInPiP else { + if !isInPiP { return bounds } // resize to suggested ratio and position to the bottom right let adjustedBounds = bounds.inset(by: dragBoundInsets) let size = CGSize(width: 150, height: 150) - let origin = initialPositionFor(pipSize: size, bounds: adjustedBounds) + let origin = (dragController.currentPosition ?? initialPositionInSuperView).getOriginIn(bounds: adjustedBounds, size: size) + return CGRect(x: origin.x, y: origin.y, width: size.width, height: size.height) } - - private func initialPositionFor(pipSize size: CGSize, bounds: CGRect) -> CGPoint { - switch initialPositionInSuperview { - case .lowerLeftCorner: - return CGPoint(x: bounds.minX, y: bounds.maxY - size.height) - case .lowerRightCorner: - return CGPoint(x: bounds.maxX - size.width, y: bounds.maxY - size.height) - case .upperLeftCorner: - return CGPoint(x: bounds.minX, y: bounds.minY) - case .upperRightCorner: - return CGPoint(x: bounds.maxX - size.width, y: bounds.minY) - } - } // MARK: - Animation helpers - private func animateTransition(animations: @escaping () -> Void, completion: AnimationCompletion?) { UIView.animate(withDuration: 0.1, @@ -234,3 +237,19 @@ public class PiPViewCoordinator { } } + +// MARK: - +extension PiPViewCoordinator.Position { + func getOriginIn(bounds: CGRect, size: CGSize) -> CGPoint { + switch self { + case .lowerLeftCorner: + return CGPoint(x: bounds.minX, y: bounds.maxY - size.height) + case .lowerRightCorner: + return CGPoint(x: bounds.maxX - size.width, y: bounds.maxY - size.height) + case .upperLeftCorner: + return CGPoint(x: bounds.minX, y: bounds.minY) + case .upperRightCorner: + return CGPoint(x: bounds.maxX - size.width, y: bounds.minY) + } + } +}