"Fossies" - the Fresh Open Source Software Archive 
Member "jitsi-meet-6312/ios/sdk/src/picture-in-picture/PiPViewCoordinator.swift" (4 Jul 2022, 9308 Bytes) of package /linux/misc/jitsi-meet-6312.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Swift source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
1 /*
2 * Copyright @ 2017-present 8x8, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 import UIKit
18
19 public typealias AnimationCompletion = (Bool) -> Void
20
21 public protocol PiPViewCoordinatorDelegate: class {
22
23 func exitPictureInPicture()
24 }
25
26 /// Coordinates the view state of a specified view to allow
27 /// to be presented in full screen or in a custom Picture in Picture mode.
28 /// This object will also provide the drag and tap interactions of the view
29 /// when is presented in Picture in Picture mode.
30 public class PiPViewCoordinator {
31
32 public enum Position {
33 case lowerRightCorner
34 case upperRightCorner
35 case lowerLeftCorner
36 case upperLeftCorner
37 }
38
39 /// Limits the boundaries of view position on screen when minimized
40 public var dragBoundInsets: UIEdgeInsets = UIEdgeInsets(top: 25,
41 left: 5,
42 bottom: 5,
43 right: 5) {
44 didSet {
45 dragController.insets = dragBoundInsets
46 }
47 }
48
49 public let initialPositionInSuperView: Position = .lowerRightCorner
50
51 // Unused. Remove on the next major release.
52 @available(*, deprecated, message: "The PiP window size is now fixed to 150px.")
53 public var c: CGFloat = 0.0
54
55 public weak var delegate: PiPViewCoordinatorDelegate?
56
57 private(set) var isInPiP: Bool = false // true if view is in PiP mode
58 private(set) var view: UIView
59 private var currentBounds: CGRect = CGRect.zero
60
61 private var tapGestureRecognizer: UITapGestureRecognizer?
62 private var exitPiPButton: UIButton?
63
64 private let dragController: DragGestureController = DragGestureController()
65
66 public init(withView view: UIView) {
67 self.view = view
68 // Required because otherwise the view will not rotate correctly.
69 view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
70
71 // Otherwise the enter/exit pip animation looks odd
72 // when pip window is bottom left, top left or top right,
73 // because the jitsi view content does not animate, but jumps to the new size immediately.
74 view.clipsToBounds = true
75 }
76
77 /// Configure the view to be always on top of all the contents
78 /// of the provided parent view.
79 /// If a parentView is not provided it will try to use the main window
80 public func configureAsStickyView(withParentView parentView: UIView? = nil) {
81 guard
82 let parentView = parentView ?? UIApplication.shared.keyWindow
83 else {
84 return
85 }
86
87 parentView.addSubview(view)
88 currentBounds = parentView.bounds
89 view.frame = currentBounds
90 view.layer.zPosition = CGFloat(Float.greatestFiniteMagnitude)
91 }
92
93 /// Show view with fade in animation
94 public func show(completion: AnimationCompletion? = nil) {
95 if view.isHidden || view.alpha < 1 {
96 view.isHidden = false
97 view.alpha = 0
98
99 animateTransition(animations: { [weak self] in
100 self?.view.alpha = 1
101 }, completion: completion)
102 }
103 }
104
105 /// Hide view with fade out animation
106 public func hide(completion: AnimationCompletion? = nil) {
107 if view.isHidden || view.alpha > 0 {
108 animateTransition(animations: { [weak self] in
109 self?.view.alpha = 0
110 self?.view.isHidden = true
111 }, completion: completion)
112 }
113 }
114
115 /// Resize view to and change state to custom PictureInPicture mode
116 /// This will resize view, add a gesture to enable user to "drag" view
117 /// around screen, and add a button of top of the view to be able to exit mode
118 public func enterPictureInPicture() {
119 isInPiP = true
120 // Resizing is done by hand when in pip.
121 view.autoresizingMask = []
122
123 animateViewChange()
124 dragController.startDragListener(inView: view)
125 dragController.insets = dragBoundInsets
126
127 // add single tap gesture recognition for displaying exit PiP UI
128 let exitSelector = #selector(toggleExitPiP)
129 let tapGestureRecognizer = UITapGestureRecognizer(target: self,
130 action: exitSelector)
131 self.tapGestureRecognizer = tapGestureRecognizer
132 view.addGestureRecognizer(tapGestureRecognizer)
133 }
134
135 /// Exit Picture in picture mode, this will resize view, remove
136 /// exit pip button, and disable the drag gesture
137 @objc public func exitPictureInPicture() {
138 isInPiP = false
139 // Enable autoresizing again, which got disabled for pip.
140 view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
141
142 animateViewChange()
143 dragController.stopDragListener()
144
145 // hide PiP UI
146 exitPiPButton?.removeFromSuperview()
147 exitPiPButton = nil
148
149 // remove gesture
150 let exitSelector = #selector(toggleExitPiP)
151 tapGestureRecognizer?.removeTarget(self, action: exitSelector)
152 tapGestureRecognizer = nil
153
154 delegate?.exitPictureInPicture()
155 }
156
157 /// Reset view to provide bounds, use this method on rotation or
158 /// screen size changes
159 public func resetBounds(bounds: CGRect) {
160 currentBounds = bounds
161
162 // Is required because otherwise the pip window is buggy when rotating the device.
163 // When not in pip then autoresize will do the job.
164 if (isInPiP) {
165 view.frame = changeViewRect()
166 }
167 }
168
169 /// Stop the dragging gesture of the root view
170 public func stopDragGesture() {
171 dragController.stopDragListener()
172 }
173
174 /// Customize the presentation of exit pip button
175 open func configureExitPiPButton(target: Any,
176 action: Selector) -> UIButton {
177 let buttonImage = UIImage.init(named: "image-resize",
178 in: Bundle(for: type(of: self)),
179 compatibleWith: nil)
180 let button = UIButton(type: .custom)
181 let size: CGSize = CGSize(width: 44, height: 44)
182 button.setImage(buttonImage, for: .normal)
183 button.backgroundColor = .gray
184 button.layer.cornerRadius = size.width / 2
185 button.frame = CGRect(origin: CGPoint.zero, size: size)
186 button.center = view.convert(view.center, from: view.superview)
187 button.addTarget(target, action: action, for: .touchUpInside)
188 return button
189 }
190
191 // MARK: - Interactions
192 @objc private func toggleExitPiP() {
193 if exitPiPButton == nil {
194 // show button
195 let exitSelector = #selector(exitPictureInPicture)
196 let button = configureExitPiPButton(target: self,
197 action: exitSelector)
198 view.addSubview(button)
199 exitPiPButton = button
200
201 } else {
202 // hide button
203 exitPiPButton?.removeFromSuperview()
204 exitPiPButton = nil
205 }
206 }
207
208 func animateViewChange() {
209 UIView.animate(withDuration: 0.25) {
210 self.view.frame = self.changeViewRect()
211 }
212 }
213
214 private func changeViewRect() -> CGRect {
215 let bounds = currentBounds
216
217 if !isInPiP {
218 return bounds
219 }
220
221 // resize to suggested ratio and position to the bottom right
222 let adjustedBounds = bounds.inset(by: dragBoundInsets)
223 let size = CGSize(width: 150, height: 150)
224 let origin = (dragController.currentPosition ?? initialPositionInSuperView).getOriginIn(bounds: adjustedBounds, size: size)
225
226 return CGRect(x: origin.x, y: origin.y, width: size.width, height: size.height)
227 }
228
229 // MARK: - Animation helpers
230 private func animateTransition(animations: @escaping () -> Void,
231 completion: AnimationCompletion?) {
232 UIView.animate(withDuration: 0.1,
233 delay: 0,
234 options: .beginFromCurrentState,
235 animations: animations,
236 completion: completion)
237 }
238
239 }
240
241 // MARK: -
242 extension PiPViewCoordinator.Position {
243 func getOriginIn(bounds: CGRect, size: CGSize) -> CGPoint {
244 switch self {
245 case .lowerLeftCorner:
246 return CGPoint(x: bounds.minX, y: bounds.maxY - size.height)
247 case .lowerRightCorner:
248 return CGPoint(x: bounds.maxX - size.width, y: bounds.maxY - size.height)
249 case .upperLeftCorner:
250 return CGPoint(x: bounds.minX, y: bounds.minY)
251 case .upperRightCorner:
252 return CGPoint(x: bounds.maxX - size.width, y: bounds.minY)
253 }
254 }
255 }