Clean up PiP mode for iOS
This commit is contained in:
parent
219b93a3c9
commit
fd44721bac
|
@ -51,6 +51,8 @@
|
|||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
|
|
|
@ -21,19 +21,73 @@ class ViewController: UIViewController {
|
|||
|
||||
@IBOutlet weak var videoButton: UIButton?
|
||||
|
||||
private var jitsiMeetCoordinator: JitsiMeetPresentationCoordinator?
|
||||
fileprivate var pipViewCoordinator: PiPViewCoordinator?
|
||||
fileprivate var jitsiMeetView: JitsiMeetView?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
||||
override func viewWillTransition(to size: CGSize,
|
||||
with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
|
||||
let rect = CGRect(origin: CGPoint.zero, size: size)
|
||||
pipViewCoordinator?.resetBounds(bounds: rect)
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func openJitsiMeet(sender: Any?) {
|
||||
let jitsiMeetCoordinator = JitsiMeetPresentationCoordinator()
|
||||
self.jitsiMeetCoordinator = jitsiMeetCoordinator
|
||||
jitsiMeetCoordinator.jitsiMeetView.welcomePageEnabled = true
|
||||
jitsiMeetCoordinator.jitsiMeetView.load(nil)
|
||||
jitsiMeetCoordinator.show()
|
||||
cleanUp()
|
||||
|
||||
// create and configure jitsimeet view
|
||||
let jitsiMeetView = JitsiMeetView()
|
||||
jitsiMeetView.welcomePageEnabled = true
|
||||
jitsiMeetView.pictureInPictureEnabled = true
|
||||
jitsiMeetView.load(nil)
|
||||
jitsiMeetView.delegate = self
|
||||
self.jitsiMeetView = jitsiMeetView
|
||||
|
||||
// Enable jitsimeet view to be a view that can be displayed
|
||||
// on top of all the things, and let the coordinator to manage
|
||||
// the view state and interactions
|
||||
pipViewCoordinator = PiPViewCoordinator(withView: jitsiMeetView)
|
||||
pipViewCoordinator?.configureAsStickyView(withParentView: view)
|
||||
|
||||
// animate in
|
||||
jitsiMeetView.alpha = 0
|
||||
pipViewCoordinator?.show()
|
||||
}
|
||||
|
||||
fileprivate func cleanUp() {
|
||||
jitsiMeetView?.removeFromSuperview()
|
||||
jitsiMeetView = nil
|
||||
pipViewCoordinator = nil
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewController: JitsiMeetViewDelegate {
|
||||
|
||||
func conferenceFailed(_ data: [AnyHashable : Any]!) {
|
||||
hideJitsiMeetViewAndCleanUp()
|
||||
}
|
||||
|
||||
func conferenceLeft(_ data: [AnyHashable : Any]!) {
|
||||
hideJitsiMeetViewAndCleanUp()
|
||||
}
|
||||
|
||||
func enterPicture(inPicture data: [AnyHashable : Any]!) {
|
||||
DispatchQueue.main.async {
|
||||
self.pipViewCoordinator?.enterPictureInPicture()
|
||||
}
|
||||
}
|
||||
|
||||
private func hideJitsiMeetViewAndCleanUp() {
|
||||
DispatchQueue.main.async {
|
||||
self.pipViewCoordinator?.hide() { _ in
|
||||
self.cleanUp()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,10 +31,8 @@
|
|||
75635B0B20751D6D00F29C9F /* left.wav in Resources */ = {isa = PBXBuildFile; fileRef = 75635B0920751D6D00F29C9F /* left.wav */; };
|
||||
C6245F5D2053091D0040BE68 /* image-resize@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5B2053091D0040BE68 /* image-resize@2x.png */; };
|
||||
C6245F5E2053091D0040BE68 /* image-resize@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5C2053091D0040BE68 /* image-resize@3x.png */; };
|
||||
C6A3425F204EF76800E062DD /* PiPWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425C204EF76800E062DD /* PiPWindow.swift */; };
|
||||
C6A34260204EF76800E062DD /* JitsiMeetPresentationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425D204EF76800E062DD /* JitsiMeetPresentationCoordinator.swift */; };
|
||||
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425E204EF76800E062DD /* DragGestureController.swift */; };
|
||||
C6A3426D204F1C3300E062DD /* JitsiMeetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3426C204F1C3300E062DD /* JitsiMeetViewController.swift */; };
|
||||
C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */; };
|
||||
C6F99C15204DB63E0001F710 /* JitsiMeetView+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
|
@ -68,10 +66,8 @@
|
|||
9C77CA3CC919B081F1A52982 /* Pods-JitsiMeet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.release.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.release.xcconfig"; sourceTree = "<group>"; };
|
||||
C6245F5B2053091D0040BE68 /* image-resize@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@2x.png"; path = "src/picture-in-picture/image-resize@2x.png"; sourceTree = "<group>"; };
|
||||
C6245F5C2053091D0040BE68 /* image-resize@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@3x.png"; path = "src/picture-in-picture/image-resize@3x.png"; sourceTree = "<group>"; };
|
||||
C6A3425C204EF76800E062DD /* PiPWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PiPWindow.swift; sourceTree = "<group>"; };
|
||||
C6A3425D204EF76800E062DD /* JitsiMeetPresentationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JitsiMeetPresentationCoordinator.swift; sourceTree = "<group>"; };
|
||||
C6A3425E204EF76800E062DD /* DragGestureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureController.swift; sourceTree = "<group>"; };
|
||||
C6A3426C204F1C3300E062DD /* JitsiMeetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JitsiMeetViewController.swift; sourceTree = "<group>"; };
|
||||
C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiPViewCoordinator.swift; sourceTree = "<group>"; };
|
||||
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetView+Private.h"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
|
@ -171,9 +167,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
C6A3425E204EF76800E062DD /* DragGestureController.swift */,
|
||||
C6A3425D204EF76800E062DD /* JitsiMeetPresentationCoordinator.swift */,
|
||||
C6A3426C204F1C3300E062DD /* JitsiMeetViewController.swift */,
|
||||
C6A3425C204EF76800E062DD /* PiPWindow.swift */,
|
||||
C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */,
|
||||
);
|
||||
path = "picture-in-picture";
|
||||
sourceTree = "<group>";
|
||||
|
@ -345,14 +339,12 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0BB9AD7B1F5EC8F4001C08DB /* CallKit.m in Sources */,
|
||||
C6A34260204EF76800E062DD /* JitsiMeetPresentationCoordinator.swift in Sources */,
|
||||
0BB9AD7D1F60356D001C08DB /* AppInfo.m in Sources */,
|
||||
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */,
|
||||
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
|
||||
0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */,
|
||||
0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */,
|
||||
C6A3426D204F1C3300E062DD /* JitsiMeetViewController.swift in Sources */,
|
||||
C6A3425F204EF76800E062DD /* PiPWindow.swift in Sources */,
|
||||
C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */,
|
||||
0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */,
|
||||
0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */,
|
||||
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,
|
||||
|
|
|
@ -1,107 +0,0 @@
|
|||
/*
|
||||
* Copyright @ 2017-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Coordinates the presentation of JitsiMeetViewController inside of
|
||||
/// an external window that can be resized and dragged with custom PiP mode
|
||||
open class JitsiMeetPresentationCoordinator: NSObject {
|
||||
|
||||
public let meetViewController: JitsiMeetViewController
|
||||
public let meetWindow: PiPWindow
|
||||
|
||||
public var isInPiP: Bool {
|
||||
get {
|
||||
return meetWindow.isInPiP
|
||||
}
|
||||
}
|
||||
|
||||
public var jitsiMeetView: JitsiMeetView {
|
||||
get {
|
||||
return meetViewController.jitsiMeetView
|
||||
}
|
||||
}
|
||||
|
||||
public init(meetViewController: JitsiMeetViewController? = nil,
|
||||
meetWindow: PiPWindow? = nil) {
|
||||
self.meetViewController = meetViewController ?? JitsiMeetViewController()
|
||||
self.meetWindow = meetWindow ?? PiPWindow(frame: UIScreen.main.bounds)
|
||||
|
||||
super.init()
|
||||
|
||||
configureMeetWindow()
|
||||
configureMeetViewController()
|
||||
}
|
||||
|
||||
/// Show window with jitsi meet and perform a completion closure
|
||||
open func show(completion: CompletionAction? = nil) {
|
||||
meetWindow.show(completion: completion)
|
||||
}
|
||||
|
||||
/// Hide window with jitsi meet and perform a completion closure
|
||||
open func hide(completion: CompletionAction? = nil) {
|
||||
meetWindow.hide(completion: completion)
|
||||
}
|
||||
|
||||
open func cleanUp() {
|
||||
// TODO: more clean up work on this
|
||||
|
||||
meetWindow.isHidden = true
|
||||
meetWindow.stopDragGesture()
|
||||
}
|
||||
|
||||
deinit {
|
||||
cleanUp()
|
||||
}
|
||||
|
||||
// MARK: - helpers
|
||||
|
||||
private func configureMeetViewController() {
|
||||
meetViewController.jitsiMeetView.pictureInPictureEnabled = true
|
||||
meetViewController.delegate = self
|
||||
}
|
||||
|
||||
private func configureMeetWindow() {
|
||||
meetWindow.backgroundColor = .clear
|
||||
meetWindow.windowLevel = UIWindowLevelStatusBar + 100
|
||||
meetWindow.rootViewController = self.meetViewController
|
||||
}
|
||||
}
|
||||
|
||||
extension JitsiMeetPresentationCoordinator: JitsiMeetViewControllerDelegate {
|
||||
|
||||
open func performPresentationUpdate(to: JitsiMeetPresentationUpdate) {
|
||||
switch to {
|
||||
case .enterPictureInPicture:
|
||||
meetWindow.enterPictureInPicture()
|
||||
case .sizeChange:
|
||||
// resize to full screen if rotation happens
|
||||
if meetWindow.isInPiP {
|
||||
meetWindow.exitPictureInPicture()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open func conferenceStarted() {
|
||||
if meetWindow.isHidden {
|
||||
meetWindow.show()
|
||||
}
|
||||
}
|
||||
|
||||
open func conferenceEnded(didFail: Bool) {
|
||||
cleanUp()
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
/*
|
||||
* Copyright @ 2017-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
public enum JitsiMeetPresentationUpdate {
|
||||
|
||||
/// The conference wants to enter Picture-in-Picture
|
||||
case enterPictureInPicture
|
||||
|
||||
/// A screen size change (usually screen rotation)
|
||||
case sizeChange
|
||||
}
|
||||
|
||||
public protocol JitsiMeetViewControllerDelegate: class {
|
||||
|
||||
/// Notifies a change of the conference presentation style.
|
||||
///
|
||||
/// - Parameter to: The presentation state that will be changed to
|
||||
func performPresentationUpdate(to: JitsiMeetPresentationUpdate)
|
||||
|
||||
/// The conference started
|
||||
func conferenceStarted()
|
||||
|
||||
/// The conference ended
|
||||
///
|
||||
/// - Parameter didFail: The reason of ending the conference
|
||||
func conferenceEnded(didFail: Bool)
|
||||
}
|
||||
|
||||
/// Wrapper ViewController of a JitsiMeetView
|
||||
///
|
||||
/// Is suggested to override this class and implement some customization
|
||||
/// on how to handle the JitsiMeetView delegate events
|
||||
open class JitsiMeetViewController: UIViewController {
|
||||
|
||||
open weak var delegate: JitsiMeetViewControllerDelegate?
|
||||
|
||||
private(set) var jitsiMeetView: JitsiMeetView = JitsiMeetView()
|
||||
|
||||
override open func loadView() {
|
||||
super.loadView()
|
||||
self.view = jitsiMeetView
|
||||
}
|
||||
|
||||
open override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
jitsiMeetView.delegate = self
|
||||
}
|
||||
|
||||
open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
delegate?.performPresentationUpdate(to: .sizeChange)
|
||||
}
|
||||
}
|
||||
|
||||
extension JitsiMeetViewController: JitsiMeetViewDelegate {
|
||||
|
||||
open func conferenceWillJoin(_ data: [AnyHashable : Any]!) {
|
||||
// do something
|
||||
}
|
||||
|
||||
open func conferenceJoined(_ data: [AnyHashable : Any]!) {
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.conferenceStarted()
|
||||
}
|
||||
}
|
||||
|
||||
open func conferenceWillLeave(_ data: [AnyHashable : Any]!) {
|
||||
// do something
|
||||
}
|
||||
|
||||
open func conferenceLeft(_ data: [AnyHashable : Any]!) {
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.conferenceEnded(didFail: false)
|
||||
}
|
||||
}
|
||||
|
||||
open func conferenceFailed(_ data: [AnyHashable : Any]!) {
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.conferenceEnded(didFail: true)
|
||||
}
|
||||
}
|
||||
|
||||
open func loadConfigError(_ data: [AnyHashable : Any]!) {
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.conferenceEnded(didFail: true)
|
||||
}
|
||||
}
|
||||
|
||||
open func enterPicture(inPicture data: [AnyHashable : Any]!) {
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.performPresentationUpdate(to: .enterPictureInPicture)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,14 +14,15 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/// Alias defining a completion closure that returns a Bool
|
||||
public typealias CompletionAction = (Bool) -> Void
|
||||
public typealias AnimationCompletion = (Bool) -> Void
|
||||
|
||||
/// A window that allows its root view controller to be presented
|
||||
/// in full screen or in a custom Picture in Picture mode
|
||||
open class PiPWindow: UIWindow {
|
||||
/// 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.
|
||||
public class PiPViewCoordinator {
|
||||
|
||||
/// Limits the boundries of root view position on screen when minimized
|
||||
/// Limits the boundries of view position on screen when minimized
|
||||
public var dragBoundInsets: UIEdgeInsets = UIEdgeInsets(top: 25,
|
||||
left: 5,
|
||||
bottom: 5,
|
||||
|
@ -31,10 +32,10 @@ open class PiPWindow: UIWindow {
|
|||
}
|
||||
}
|
||||
|
||||
/// The size ratio for root view controller view when in PiP mode
|
||||
/// The size ratio of the view when in PiP mode
|
||||
public var pipSizeRatio: CGFloat = {
|
||||
let deviceIdiom = UIScreen.main.traitCollection.userInterfaceIdiom
|
||||
switch (deviceIdiom) {
|
||||
switch deviceIdiom {
|
||||
case .pad:
|
||||
return 0.25
|
||||
case .phone:
|
||||
|
@ -44,50 +45,62 @@ open class PiPWindow: UIWindow {
|
|||
}
|
||||
}()
|
||||
|
||||
/// The PiP state of this contents of the window
|
||||
private(set) var isInPiP: Bool = false
|
||||
private(set) var isInPiP: Bool = false // true if view is in PiP mode
|
||||
|
||||
private let dragController: DragGestureController = DragGestureController()
|
||||
private(set) var view: UIView
|
||||
private var currentBounds: CGRect = CGRect.zero
|
||||
|
||||
/// Used when in PiP mode to enable/disable exit PiP UI
|
||||
private var tapGestureRecognizer: UITapGestureRecognizer?
|
||||
private var exitPiPButton: UIButton?
|
||||
|
||||
/// Help out to bubble up the gesture detection outside of the rootVC frame
|
||||
open override func point(inside point: CGPoint,
|
||||
with event: UIEvent?) -> Bool {
|
||||
guard let vc = rootViewController else {
|
||||
return super.point(inside: point, with: event)
|
||||
}
|
||||
return vc.view.frame.contains(point)
|
||||
private let dragController: DragGestureController = DragGestureController()
|
||||
|
||||
public init(withView view: UIView) {
|
||||
self.view = view
|
||||
}
|
||||
|
||||
/// animate in the window
|
||||
open func show(completion: CompletionAction? = nil) {
|
||||
if self.isHidden || self.alpha < 1 {
|
||||
self.isHidden = false
|
||||
self.alpha = 0
|
||||
/// Configure the view to be always on top of all the contents
|
||||
/// of the provided parent view.
|
||||
/// If a parentView is not provided it will try to use the main window
|
||||
public func configureAsStickyView(withParentView parentView: UIView? = nil) {
|
||||
guard
|
||||
let parentView = parentView ?? UIApplication.shared.keyWindow
|
||||
else { return }
|
||||
|
||||
parentView.addSubview(view)
|
||||
currentBounds = parentView.bounds
|
||||
view.frame = currentBounds
|
||||
view.layer.zPosition = CGFloat(Float.greatestFiniteMagnitude)
|
||||
}
|
||||
|
||||
/// Show view with fade in animation
|
||||
public func show(completion: AnimationCompletion? = nil) {
|
||||
if view.isHidden || view.alpha < 1 {
|
||||
view.isHidden = false
|
||||
view.alpha = 0
|
||||
|
||||
animateTransition(animations: {
|
||||
self.alpha = 1
|
||||
animateTransition(animations: { [weak self] in
|
||||
self?.view.alpha = 1
|
||||
}, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
/// animate out the window
|
||||
open func hide(completion: CompletionAction? = nil) {
|
||||
if !self.isHidden || self.alpha > 0 {
|
||||
animateTransition(animations: {
|
||||
self.alpha = 1
|
||||
/// Hide view with fade out animation
|
||||
public func hide(completion: AnimationCompletion? = nil) {
|
||||
if view.isHidden || view.alpha > 0 {
|
||||
animateTransition(animations: { [weak self] in
|
||||
self?.view.alpha = 0
|
||||
self?.view.isHidden = true
|
||||
}, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
/// Resize the root view to PiP mode
|
||||
open func enterPictureInPicture() {
|
||||
guard let view = rootViewController?.view else { return }
|
||||
/// Resize view to and change state to custom PictureInPicture mode
|
||||
/// This will resize view, add a gesture to enable user to "drag" view
|
||||
/// around screen, and add a button of top of the view to be able to exit mode
|
||||
public func enterPictureInPicture() {
|
||||
isInPiP = true
|
||||
animateRootViewChange()
|
||||
animateViewChange()
|
||||
dragController.startDragListener(inView: view)
|
||||
dragController.insets = dragBoundInsets
|
||||
|
||||
|
@ -99,10 +112,11 @@ open class PiPWindow: UIWindow {
|
|||
view.addGestureRecognizer(tapGestureRecognizer)
|
||||
}
|
||||
|
||||
/// Resize the root view to full screen
|
||||
open func exitPictureInPicture() {
|
||||
/// Exit Picture in picture mode, this will resize view, remove
|
||||
/// exit pip button, and disable the drag gesture
|
||||
@objc public func exitPictureInPicture() {
|
||||
isInPiP = false
|
||||
animateRootViewChange()
|
||||
animateViewChange()
|
||||
dragController.stopDragListener()
|
||||
|
||||
// hide PiP UI
|
||||
|
@ -115,6 +129,13 @@ open class PiPWindow: UIWindow {
|
|||
tapGestureRecognizer = nil
|
||||
}
|
||||
|
||||
/// Reset view to provide bounds, use this method on rotation or
|
||||
/// screen size changes
|
||||
public func resetBounds(bounds: CGRect) {
|
||||
currentBounds = bounds
|
||||
exitPictureInPicture()
|
||||
}
|
||||
|
||||
/// Stop the dragging gesture of the root view
|
||||
public func stopDragGesture() {
|
||||
dragController.stopDragListener()
|
||||
|
@ -132,41 +153,14 @@ open class PiPWindow: UIWindow {
|
|||
button.backgroundColor = .gray
|
||||
button.layer.cornerRadius = size.width / 2
|
||||
button.frame = CGRect(origin: CGPoint.zero, size: size)
|
||||
if let view = rootViewController?.view {
|
||||
button.center = view.convert(view.center, from:view.superview)
|
||||
}
|
||||
button.center = view.convert(view.center, from: view.superview)
|
||||
button.addTarget(target, action: action, for: .touchUpInside)
|
||||
return button
|
||||
}
|
||||
|
||||
// MARK: - Manage presentation switching
|
||||
|
||||
private func animateRootViewChange() {
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.rootViewController?.view.frame = self.changeRootViewRect()
|
||||
self.rootViewController?.view.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
private func changeRootViewRect() -> CGRect {
|
||||
guard isInPiP else {
|
||||
return self.bounds
|
||||
}
|
||||
|
||||
// resize to suggested ratio and position to the bottom right
|
||||
let adjustedBounds = UIEdgeInsetsInsetRect(self.bounds, dragBoundInsets)
|
||||
let size = CGSize(width: bounds.size.width * pipSizeRatio,
|
||||
height: bounds.size.height * pipSizeRatio)
|
||||
let x: CGFloat = adjustedBounds.maxX - size.width
|
||||
let y: CGFloat = adjustedBounds.maxY - size.height
|
||||
return CGRect(x: x, y: y, width: size.width, height: size.height)
|
||||
}
|
||||
|
||||
// MARK: - Exit PiP
|
||||
// MARK: - Interactions
|
||||
|
||||
@objc private func toggleExitPiP() {
|
||||
guard let view = rootViewController?.view else { return }
|
||||
|
||||
if exitPiPButton == nil {
|
||||
// show button
|
||||
let exitSelector = #selector(exitPictureInPicture)
|
||||
|
@ -182,18 +176,40 @@ open class PiPWindow: UIWindow {
|
|||
}
|
||||
}
|
||||
|
||||
@objc private func exitPiP() {
|
||||
exitPictureInPicture()
|
||||
// MARK: - Size calculation
|
||||
|
||||
private func animateViewChange() {
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.view.frame = self.changeViewRect()
|
||||
self.view.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Animation transition
|
||||
private func changeViewRect() -> CGRect {
|
||||
let bounds = currentBounds
|
||||
|
||||
guard isInPiP else {
|
||||
return bounds
|
||||
}
|
||||
|
||||
// resize to suggested ratio and position to the bottom right
|
||||
let adjustedBounds = UIEdgeInsetsInsetRect(bounds, dragBoundInsets)
|
||||
let size = CGSize(width: bounds.size.width * pipSizeRatio,
|
||||
height: bounds.size.height * pipSizeRatio)
|
||||
let x: CGFloat = adjustedBounds.maxX - size.width
|
||||
let y: CGFloat = adjustedBounds.maxY - size.height
|
||||
return CGRect(x: x, y: y, width: size.width, height: size.height)
|
||||
}
|
||||
|
||||
// MARK: - Animation helpers
|
||||
|
||||
private func animateTransition(animations: @escaping () -> Void,
|
||||
completion: CompletionAction?) {
|
||||
completion: AnimationCompletion?) {
|
||||
UIView.animate(withDuration: 0.1,
|
||||
delay: 0,
|
||||
options: .beginFromCurrentState,
|
||||
animations: animations,
|
||||
completion: completion)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue