fix(ios) fix PiP resizing and positioning
Co-authored-by: Tobias Marschall <tobias.marschall@online.de>
This commit is contained in:
parent
607021a890
commit
7f2fec756d
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue