Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 67e2ff5ce5 | |||
| 1c12c101b6 | |||
| cd31f0d55d | |||
| 7a4a020c9c | |||
| 7208c56820 | |||
| 8d7f902344 | |||
| eca9258206 | |||
| 87824eb760 | |||
| 5bcd63c6b9 | |||
| ec233369aa | |||
| 98ff7089cd | |||
| 1d41bbd85e | |||
| c2bc901427 | |||
| 6025ea6cc2 | |||
| 1bf0e61732 | |||
| 82c5086787 | |||
| a8b7f702b4 | |||
| a7a5cba7d9 | |||
| 20e44ee90a | |||
| 0d364aa66b | |||
| 3a18df11ea | |||
| 5f8cba0388 | |||
| 930bc42cef | |||
| c004ca0c2c | |||
| da55c724e4 | |||
| 9487460c56 | |||
| 3b0d66211e |
@@ -2,7 +2,7 @@
|
||||
|
||||
Welcome to LocalConsole! This Swift Package makes on-device debugging easy with a convenient PiP-style console that can display items in the same way ```print()``` will in Xcode. This tool can also dynamically display view frames and restart SpringBoard right from your live app.
|
||||
|
||||
<img src="https://github.com/duraidabdul/LocalConsole/blob/main/Demo.gif?raw=true" width="250" height="500">
|
||||
<img src="https://github.com/duraidabdul/LocalConsole/blob/main/Demo.gif?raw=true" width="250">
|
||||
|
||||
## **Setup**
|
||||
|
||||
@@ -21,20 +21,30 @@ let localConsoleManager = LCManager.shared
|
||||
Once prepared, the localConsole can be used throughout your project.
|
||||
```swift
|
||||
|
||||
// Show local console.
|
||||
// Show the console view.
|
||||
localConsoleManager.isVisible = true
|
||||
|
||||
// Hide local console.
|
||||
// Hide the console view.
|
||||
localConsoleManager.isVisible = false
|
||||
```
|
||||
|
||||
// Print items to local console.
|
||||
```swift
|
||||
// Print items to the console view.
|
||||
localConsoleManager.print("Hello, world!")
|
||||
|
||||
// Clear local console text.
|
||||
// Clear text in the console view.
|
||||
localConsoleManager.clear()
|
||||
```
|
||||
|
||||
## **Upcoming Features**
|
||||
```swift
|
||||
// Change the console view font size.
|
||||
localConsoleManager.fontSize = 5
|
||||
```
|
||||
|
||||
|
||||
## **To-Do**
|
||||
* Custom console view size
|
||||
* Custom console view font size
|
||||
* Support for iOS 13
|
||||
* Screen edge console hiding
|
||||
* Custom pinch to resize gesture
|
||||
* Make console view reactive to landscape/portrait switch
|
||||
|
||||
@@ -15,6 +15,13 @@ extension UIScreen {
|
||||
static var size: CGSize {
|
||||
return UIScreen.main.bounds.size
|
||||
}
|
||||
|
||||
static var portraitSize: CGSize {
|
||||
return CGSize(width: UIScreen.main.nativeBounds.width / UIScreen.main.nativeScale,
|
||||
height: UIScreen.main.nativeBounds.height / UIScreen.main.nativeScale)
|
||||
}
|
||||
|
||||
static var hasRoundedCorners = UIScreen.main.value(forKey: "_" + "display" + "Corner" + "Radius") as! CGFloat > 0
|
||||
}
|
||||
|
||||
extension UIApplication {
|
||||
@@ -35,4 +42,22 @@ extension UIFont {
|
||||
}
|
||||
}
|
||||
|
||||
extension UIControl {
|
||||
func addActions(highlightAction: UIAction, unhighlightAction: UIAction) {
|
||||
addAction(highlightAction, for: .touchDown)
|
||||
addAction(highlightAction, for: .touchDragEnter)
|
||||
|
||||
addAction(unhighlightAction, for: .touchUpInside)
|
||||
addAction(unhighlightAction, for: .touchDragExit)
|
||||
addAction(unhighlightAction, for: .touchCancel)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
func roundOriginToPixel() {
|
||||
frame.origin.x = (round(frame.origin.x * UIScreen.main.scale)) / UIScreen.main.scale
|
||||
frame.origin.y = (round(frame.origin.y * UIScreen.main.scale)) / UIScreen.main.scale
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -16,31 +16,74 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
public static let shared = LCManager()
|
||||
|
||||
let consoleSize = CGSize(width: 212, height: 124)
|
||||
/// Set the font size. The font can be set to a minimum value of 5.0 and a maximum value of 20.0. The default value is 7.5.
|
||||
public var fontSize: CGFloat = 7.5 {
|
||||
didSet {
|
||||
guard fontSize >= 4 else { fontSize = 4; return }
|
||||
guard fontSize <= 20 else { fontSize = 20; return }
|
||||
|
||||
setAttributedText(consoleTextView.text)
|
||||
}
|
||||
}
|
||||
|
||||
// Strong reference needed to keep the window alive.
|
||||
let defaultConsoleSize = CGSize(width: 212, height: 124)
|
||||
|
||||
/// The fixed size of the console view.
|
||||
lazy var consoleSize = defaultConsoleSize {
|
||||
didSet {
|
||||
consoleView.frame.size = consoleSize
|
||||
if consoleView.frame.size.width > ResizeController.kMaxConsoleWidth {
|
||||
consoleTextView.frame.size.width = ResizeController.kMaxConsoleWidth
|
||||
} else {
|
||||
consoleTextView.frame.size.width = consoleSize.width
|
||||
}
|
||||
// TODO: Snap to nearest position.
|
||||
|
||||
UserDefaults.standard.set(consoleSize.width, forKey: "LocalConsole_Width")
|
||||
UserDefaults.standard.set(consoleSize.height, forKey: "LocalConsole_Height")
|
||||
}
|
||||
}
|
||||
|
||||
/// Strong reference keeps the window alive.
|
||||
var consoleWindow: ConsoleWindow?
|
||||
|
||||
// The console needs a view controller to display context menus.
|
||||
// The console needs a parent view controller in order to display context menus.
|
||||
let viewController = UIViewController()
|
||||
lazy var consoleView = viewController.view!
|
||||
|
||||
/// Text view that displays printed items.
|
||||
let consoleTextView = UITextView()
|
||||
|
||||
/// Button that reveals menu.
|
||||
var menuButton: UIButton!
|
||||
|
||||
/// Tracks whether the PiP console is in text view scroll mode or pan mode.
|
||||
var scrollLocked = true
|
||||
|
||||
/// Feedback generator for the long press action.
|
||||
let feedbackGenerator = UISelectionFeedbackGenerator()
|
||||
|
||||
lazy var possibleEndpoints = [CGPoint(x: consoleSize.width / 2 + 12,
|
||||
y: UIApplication.shared.statusBarHeight + consoleSize.height / 2 + 5),
|
||||
CGPoint(x: UIScreen.size.width - consoleSize.width / 2 - 12,
|
||||
y: UIApplication.shared.statusBarHeight + consoleSize.height / 2 + 5),
|
||||
CGPoint(x: consoleSize.width / 2 + 12,
|
||||
y: UIScreen.size.height - consoleSize.height / 2 - 56),
|
||||
CGPoint(x: UIScreen.size.width - consoleSize.width / 2 - 12,
|
||||
y: UIScreen.size.height - consoleSize.height / 2 - 56)]
|
||||
lazy var panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(consolePiPPanner(recognizer:)))
|
||||
lazy var longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(recognizer:)))
|
||||
|
||||
/// Gesture endpoints. Each point represents a corner of the screen. TODO: Handle screen rotation.
|
||||
var possibleEndpoints: [CGPoint] {
|
||||
if consoleSize.width < UIScreen.portraitSize.width - 112 {
|
||||
return [CGPoint(x: consoleSize.width / 2 + 12,
|
||||
y: (UIScreen.hasRoundedCorners ? 44 : 16) + consoleSize.height / 2 + 12),
|
||||
CGPoint(x: UIScreen.portraitSize.width - consoleSize.width / 2 - 12,
|
||||
y: (UIScreen.hasRoundedCorners ? 44 : 16) + consoleSize.height / 2 + 12),
|
||||
CGPoint(x: consoleSize.width / 2 + 12,
|
||||
y: UIScreen.portraitSize.height - consoleSize.height / 2 - (consoleWindow?.safeAreaInsets.bottom ?? 0) - 12),
|
||||
CGPoint(x: UIScreen.portraitSize.width - consoleSize.width / 2 - 12,
|
||||
y: UIScreen.portraitSize.height - consoleSize.height / 2 - (consoleWindow?.safeAreaInsets.bottom ?? 0) - 12)]
|
||||
} else {
|
||||
return [CGPoint(x: UIScreen.portraitSize.width / 2,
|
||||
y: (UIScreen.hasRoundedCorners ? 44 : 16) + consoleSize.height / 2 + 12),
|
||||
CGPoint(x: UIScreen.portraitSize.width / 2,
|
||||
y: UIScreen.portraitSize.height - consoleSize.height / 2 - (consoleWindow?.safeAreaInsets.bottom ?? 0) - 12)]
|
||||
}
|
||||
}
|
||||
|
||||
lazy var initialViewLocation: CGPoint = .zero
|
||||
|
||||
@@ -56,15 +99,16 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
if let windowScene = windowScene as? UIWindowScene {
|
||||
consoleWindow = ConsoleWindow(windowScene: windowScene)
|
||||
consoleWindow?.frame = UIScreen.main.bounds
|
||||
consoleWindow?.windowLevel = UIWindow.Level.normal
|
||||
consoleWindow?.windowLevel = UIWindow.Level.statusBar
|
||||
consoleWindow?.isHidden = false
|
||||
consoleWindow?.addSubview(consoleView)
|
||||
|
||||
UIWindow.swizzleStatusBarAppearanceOverride
|
||||
}
|
||||
|
||||
// Configure console view.
|
||||
consoleView.frame.size = consoleSize
|
||||
consoleSize = CGSize(width: UserDefaults.standard.object(forKey: "LocalConsole_Width") as? CGFloat ?? consoleSize.width,
|
||||
height: UserDefaults.standard.object(forKey: "LocalConsole_Height") as? CGFloat ?? consoleSize.height)
|
||||
|
||||
consoleView.backgroundColor = .black
|
||||
|
||||
consoleView.layer.shadowRadius = 16
|
||||
@@ -73,12 +117,20 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
consoleView.center = possibleEndpoints.first!
|
||||
consoleView.alpha = 0
|
||||
|
||||
consoleView.layer.borderWidth = 1
|
||||
consoleView.layer.borderColor = UIColor(white: 1, alpha: 0.08).cgColor
|
||||
|
||||
consoleView.layer.cornerRadius = 19
|
||||
consoleView.layer.cornerRadius = 20
|
||||
consoleView.layer.cornerCurve = .continuous
|
||||
|
||||
let borderView = UIView()
|
||||
borderView.frame = CGRect(x: -1, y: -1,
|
||||
width: consoleSize.width + 2,
|
||||
height: consoleSize.height + 2)
|
||||
borderView.layer.borderWidth = 1
|
||||
borderView.layer.borderColor = UIColor(white: 1, alpha: 0.08).cgColor
|
||||
borderView.layer.cornerRadius = consoleView.layer.cornerRadius + 1
|
||||
borderView.layer.cornerCurve = .continuous
|
||||
borderView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
consoleView.addSubview(borderView)
|
||||
|
||||
// Configure text view.
|
||||
consoleTextView.frame = CGRect(x: 0, y: 2, width: consoleSize.width, height: consoleSize.height - 4)
|
||||
consoleTextView.isEditable = false
|
||||
@@ -88,17 +140,16 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
consoleTextView.isSelectable = false
|
||||
consoleTextView.showsVerticalScrollIndicator = false
|
||||
consoleTextView.contentInsetAdjustmentBehavior = .never
|
||||
consoleTextView.autoresizingMask = [.flexibleHeight]
|
||||
consoleView.addSubview(consoleTextView)
|
||||
|
||||
// Configure gesture recognizers.
|
||||
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(consolePiPPanner(recognizer:)))
|
||||
panRecognizer.maximumNumberOfTouches = 1
|
||||
panRecognizer.delegate = self
|
||||
|
||||
let tapRecognizer = UITapStartEndGestureRecognizer(target: self, action: #selector(consolePiPTapStartEnd(recognizer:)))
|
||||
tapRecognizer.delegate = self
|
||||
|
||||
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(recognizer:)))
|
||||
longPressRecognizer.minimumPressDuration = 0.1
|
||||
|
||||
consoleView.addGestureRecognizer(panRecognizer)
|
||||
@@ -106,18 +157,30 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
consoleView.addGestureRecognizer(longPressRecognizer)
|
||||
|
||||
// Prepare menu button.
|
||||
let diameter = CGFloat(25)
|
||||
let diameter = CGFloat(26)
|
||||
|
||||
menuButton = UIButton(frame: CGRect(x: consoleView.bounds.width - diameter - (consoleView.layer.cornerRadius - diameter / 2),
|
||||
y: consoleView.bounds.height - diameter - (consoleView.layer.cornerRadius - diameter / 2),
|
||||
width: diameter, height: diameter))
|
||||
menuButton.layer.cornerRadius = diameter / 2
|
||||
menuButton.backgroundColor = UIColor(white: 1, alpha: 0.20)
|
||||
// This tuned button frame is used to adjust where the menu appears.
|
||||
menuButton = UIButton(frame: CGRect(x: consoleView.bounds.width - 44,
|
||||
y: consoleView.bounds.height - 36,
|
||||
width: 44,
|
||||
height: 36 + 4 /*Offests the context menu by the desired amount*/))
|
||||
menuButton.autoresizingMask = [.flexibleLeftMargin, .flexibleTopMargin]
|
||||
|
||||
let circleFrame = CGRect(
|
||||
x: menuButton.bounds.width - diameter - (consoleView.layer.cornerRadius - diameter / 2),
|
||||
y: menuButton.bounds.height - diameter - (consoleView.layer.cornerRadius - diameter / 2) - 4,
|
||||
width: diameter, height: diameter)
|
||||
|
||||
let circle = UIView(frame: circleFrame)
|
||||
circle.backgroundColor = UIColor(white: 0.2, alpha: 0.95)
|
||||
circle.layer.cornerRadius = diameter / 2
|
||||
circle.isUserInteractionEnabled = false
|
||||
menuButton.addSubview(circle)
|
||||
|
||||
let ellipsisImage = UIImageView(image: UIImage(systemName: "ellipsis", withConfiguration: UIImage.SymbolConfiguration(pointSize: 16)))
|
||||
ellipsisImage.frame.size = menuButton!.bounds.size
|
||||
ellipsisImage.frame.size = circle.bounds.size
|
||||
ellipsisImage.contentMode = .center
|
||||
menuButton.addSubview(ellipsisImage)
|
||||
circle.addSubview(ellipsisImage)
|
||||
|
||||
menuButton.tintColor = UIColor(white: 1, alpha: 0.75)
|
||||
menuButton.menu = makeMenu()
|
||||
@@ -127,6 +190,8 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
UIView.swizzleDebugBehaviour
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
public var isVisible = false {
|
||||
|
||||
didSet {
|
||||
@@ -149,6 +214,37 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
/// Print items to the console view.
|
||||
public func print(_ items: Any) {
|
||||
let string: String = {
|
||||
if consoleTextView.text == "" {
|
||||
return "\(items)"
|
||||
} else {
|
||||
return "\(items)\n" + consoleTextView.text
|
||||
}
|
||||
}()
|
||||
|
||||
setAttributedText(string)
|
||||
|
||||
// Update the context menu to show the clipboard/clear actions.
|
||||
menuButton.menu = makeMenu()
|
||||
}
|
||||
|
||||
/// Clear text in the console view.
|
||||
public func clear() {
|
||||
consoleTextView.text = ""
|
||||
|
||||
// Update the context menu to hide the clipboard/clear actions.
|
||||
menuButton.menu = makeMenu()
|
||||
}
|
||||
|
||||
/// Copy the console view text to the device's clipboard.
|
||||
public func copy() {
|
||||
UIPasteboard.general.string = consoleTextView.text
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private var debugBordersEnabled = false {
|
||||
didSet {
|
||||
GLOBAL_DEBUG_BORDERS = debugBordersEnabled
|
||||
@@ -202,33 +298,41 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
consoleView.backgroundColor = .black
|
||||
}
|
||||
|
||||
public func print(_ items: Any) {
|
||||
|
||||
func setAttributedText(_ string: String) {
|
||||
let paragraphStyle = NSMutableParagraphStyle()
|
||||
paragraphStyle.headIndent = 7
|
||||
|
||||
let attributes: [NSAttributedString.Key: Any] = [
|
||||
.paragraphStyle: paragraphStyle,
|
||||
.foregroundColor: UIColor.white,
|
||||
.font: UIFont.systemFont(ofSize: 7, weight: .semibold, design: .monospaced)
|
||||
.font: UIFont.systemFont(ofSize: fontSize, weight: .semibold, design: .monospaced)
|
||||
]
|
||||
|
||||
let string: String = {
|
||||
if consoleTextView.attributedText.string == "" {
|
||||
return "\(items)"
|
||||
} else {
|
||||
return "\(items)\n" + consoleTextView.text
|
||||
}
|
||||
}()
|
||||
|
||||
consoleTextView.attributedText = NSAttributedString(string: string, attributes: attributes)
|
||||
}
|
||||
|
||||
public func clear() {
|
||||
consoleTextView.text = ""
|
||||
}
|
||||
|
||||
func makeMenu() -> UIMenu {
|
||||
|
||||
let copy = UIAction(title: "Copy",
|
||||
image: UIImage(systemName: "doc.on.doc"), handler: { _ in
|
||||
self.copy()
|
||||
})
|
||||
|
||||
let resize = UIAction(title: "Resize Console",
|
||||
image: UIImage(systemName: "arrow.left.and.right.square"), handler: { _ in
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
ResizeController.shared.isActive.toggle()
|
||||
ResizeController.shared.platterView.reveal()
|
||||
}
|
||||
})
|
||||
|
||||
let clear = UIAction(title: "Clear Console",
|
||||
image: UIImage(systemName: "xmark.square"), handler: { _ in
|
||||
self.clear()
|
||||
})
|
||||
|
||||
let consoleActions = UIMenu(title: "", options: .displayInline, children: [clear, resize])
|
||||
|
||||
let viewFrames = UIAction(title: debugBordersEnabled ? "Hide View Frames" : "Show View Frames",
|
||||
image: UIImage(systemName: "rectangle.3.offgrid"), handler: { _ in
|
||||
self.debugBordersEnabled.toggle()
|
||||
@@ -253,8 +357,18 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
animator.startAnimation()
|
||||
})
|
||||
let debugActions = UIMenu(title: "", options: .displayInline, children: [viewFrames, respring])
|
||||
|
||||
return UIMenu(title: "", children: [viewFrames, respring])
|
||||
var menuContent: [UIMenuElement] = []
|
||||
|
||||
if consoleTextView.text != "" {
|
||||
menuContent.append(contentsOf: [copy, consoleActions])
|
||||
} else {
|
||||
menuContent.append(resize)
|
||||
}
|
||||
menuContent.append(debugActions)
|
||||
|
||||
return UIMenu(title: "", children: menuContent)
|
||||
}
|
||||
|
||||
@objc func longPressAction(recognizer: UILongPressGestureRecognizer) {
|
||||
@@ -386,9 +500,10 @@ class ConsoleWindow: UIWindow {
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
|
||||
let hitView = super.hitTest(point, with: event)!
|
||||
|
||||
return hitView.isKind(of: ConsoleWindow.self) ? nil : hitView
|
||||
if let hitView = super.hitTest(point, with: event) {
|
||||
return hitView.isKind(of: ConsoleWindow.self) ? nil : hitView
|
||||
}
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,608 @@
|
||||
//
|
||||
// ResizeController.swift
|
||||
//
|
||||
// Created by Duraid Abdul.
|
||||
// Copyright © 2021 Duraid Abdul. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class ResizeController {
|
||||
|
||||
public static let shared = ResizeController()
|
||||
|
||||
lazy var platterView = PlatterView(frame: .zero)
|
||||
|
||||
lazy var consoleCenterPoint = CGPoint(x: (UIScreen.main.nativeBounds.width / 2).rounded() / UIScreen.main.scale,
|
||||
y: (UIScreen.main.nativeBounds.height / 2).rounded() / UIScreen.main.scale
|
||||
+ (UIScreen.hasRoundedCorners ? 0 : 24))
|
||||
|
||||
lazy var consoleOutlineView: UIView = {
|
||||
|
||||
let consoleViewReference = LCManager.shared.consoleView
|
||||
|
||||
let view = UIView()
|
||||
view.layer.borderWidth = 2
|
||||
view.layer.borderColor = UIColor.systemGreen.resolvedColor(with: UITraitCollection(userInterfaceStyle: .light)).cgColor
|
||||
view.layer.cornerRadius = consoleViewReference.layer.cornerRadius + 6
|
||||
view.layer.cornerCurve = .continuous
|
||||
view.alpha = 0
|
||||
|
||||
consoleViewReference.addSubview(view)
|
||||
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
view.leadingAnchor.constraint(equalTo: consoleViewReference.leadingAnchor, constant: -6),
|
||||
view.trailingAnchor.constraint(equalTo: consoleViewReference.trailingAnchor, constant: 6),
|
||||
view.topAnchor.constraint(equalTo: consoleViewReference.topAnchor, constant: -6),
|
||||
view.bottomAnchor.constraint(equalTo: consoleViewReference.bottomAnchor, constant: 6)
|
||||
])
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var bottomGrabberPillView = UIView()
|
||||
|
||||
lazy var bottomGrabber: UIView = {
|
||||
let view = UIView()
|
||||
LCManager.shared.consoleWindow?.addSubview(view)
|
||||
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
view.widthAnchor.constraint(equalToConstant: 116),
|
||||
view.heightAnchor.constraint(equalToConstant: 46),
|
||||
view.centerXAnchor.constraint(equalTo: consoleOutlineView.centerXAnchor),
|
||||
view.topAnchor.constraint(equalTo: consoleOutlineView.bottomAnchor, constant: -18)
|
||||
])
|
||||
|
||||
bottomGrabberPillView.frame = CGRect(x: 58 - 18, y: 25, width: 36, height: 5)
|
||||
bottomGrabberPillView.backgroundColor = UIColor.label
|
||||
bottomGrabberPillView.alpha = 0.3
|
||||
bottomGrabberPillView.layer.cornerRadius = 2.5
|
||||
bottomGrabberPillView.layer.cornerCurve = .continuous
|
||||
view.addSubview(bottomGrabberPillView)
|
||||
|
||||
let verticalPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(verticalPanner(recognizer:)))
|
||||
verticalPanGestureRecognizer.maximumNumberOfTouches = 1
|
||||
view.addGestureRecognizer(verticalPanGestureRecognizer)
|
||||
|
||||
view.alpha = 0
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var rightGrabberPillView = UIView()
|
||||
|
||||
lazy var rightGrabber: UIView = {
|
||||
let view = UIView()
|
||||
LCManager.shared.consoleWindow?.addSubview(view)
|
||||
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
view.widthAnchor.constraint(equalToConstant: 46),
|
||||
view.heightAnchor.constraint(equalToConstant: 116),
|
||||
view.centerYAnchor.constraint(equalTo: consoleOutlineView.centerYAnchor),
|
||||
view.leftAnchor.constraint(equalTo: consoleOutlineView.rightAnchor, constant: -18)
|
||||
])
|
||||
|
||||
rightGrabberPillView.frame = CGRect(x: 25, y: 58 - 18, width: 5, height: 36)
|
||||
rightGrabberPillView.backgroundColor = UIColor.label
|
||||
rightGrabberPillView.alpha = 0.3
|
||||
rightGrabberPillView.layer.cornerRadius = 2.5
|
||||
rightGrabberPillView.layer.cornerCurve = .continuous
|
||||
view.addSubview(rightGrabberPillView)
|
||||
|
||||
let horizontalPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(horizontalPanner(recognizer:)))
|
||||
horizontalPanGestureRecognizer.maximumNumberOfTouches = 1
|
||||
view.addGestureRecognizer(horizontalPanGestureRecognizer)
|
||||
|
||||
view.alpha = 0
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
var isActive: Bool = false {
|
||||
didSet {
|
||||
guard isActive != oldValue else { return }
|
||||
|
||||
// Initialize views outside of animation.
|
||||
_ = platterView
|
||||
_ = consoleOutlineView
|
||||
_ = bottomGrabber
|
||||
_ = rightGrabber
|
||||
|
||||
// Ensure initial autolayout is performed unanimated.
|
||||
LCManager.shared.consoleWindow?.layoutIfNeeded()
|
||||
|
||||
if isActive {
|
||||
|
||||
if LCManager.shared.consoleView.traitCollection.userInterfaceStyle == .light {
|
||||
LCManager.shared.consoleView.layer.shadowOpacity = 0.25
|
||||
}
|
||||
|
||||
// Ensure background color animates in right the first time.
|
||||
LCManager.shared.consoleWindow?.backgroundColor = .clear
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) {
|
||||
LCManager.shared.consoleView.center = self.consoleCenterPoint
|
||||
|
||||
// Update grabbers (layout constraints)
|
||||
LCManager.shared.consoleWindow?.layoutIfNeeded()
|
||||
|
||||
LCManager.shared.menuButton.alpha = 0
|
||||
|
||||
LCManager.shared.consoleWindow?.backgroundColor = UIColor(dynamicProvider: { traitCollection in
|
||||
UIColor(white: 0, alpha: traitCollection.userInterfaceStyle == .light ? 0.1 : 0.3)
|
||||
})
|
||||
}.startAnimation()
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in
|
||||
consoleOutlineView.alpha = 1
|
||||
}.startAnimation(afterDelay: 0.3)
|
||||
|
||||
bottomGrabber.transform = .init(translationX: 0, y: -5)
|
||||
rightGrabber.transform = .init(translationX: -5, y: 0)
|
||||
|
||||
UIViewPropertyAnimator(duration: 1, dampingRatio: 1) { [self] in
|
||||
bottomGrabber.alpha = 1
|
||||
rightGrabber.alpha = 1
|
||||
|
||||
bottomGrabber.transform = .identity
|
||||
rightGrabber.transform = .identity
|
||||
}.startAnimation(afterDelay: 0.3)
|
||||
|
||||
LCManager.shared.panRecognizer.isEnabled = false
|
||||
LCManager.shared.longPressRecognizer.isEnabled = false
|
||||
|
||||
// Activate full screen button.
|
||||
consoleOutlineView.isUserInteractionEnabled = true
|
||||
} else {
|
||||
|
||||
LCManager.shared.consoleView.layer.shadowOpacity = 0.5
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) {
|
||||
LCManager.shared.consoleView.center = LCManager.shared.possibleEndpoints.first!
|
||||
|
||||
// Update grabbers (layout constraints)
|
||||
LCManager.shared.consoleWindow?.layoutIfNeeded()
|
||||
|
||||
LCManager.shared.menuButton.alpha = 1
|
||||
|
||||
LCManager.shared.consoleWindow?.backgroundColor = .clear
|
||||
}.startAnimation()
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.2, dampingRatio: 1) { [self] in
|
||||
consoleOutlineView.alpha = 0
|
||||
|
||||
bottomGrabber.alpha = 0
|
||||
rightGrabber.alpha = 0
|
||||
}.startAnimation()
|
||||
|
||||
LCManager.shared.panRecognizer.isEnabled = true
|
||||
LCManager.shared.longPressRecognizer.isEnabled = true
|
||||
|
||||
// Deactivate full screen button.
|
||||
consoleOutlineView.isUserInteractionEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var initialHeight = CGFloat.zero
|
||||
|
||||
@objc func verticalPanner(recognizer: UIPanGestureRecognizer) {
|
||||
|
||||
let translation = recognizer.translation(in: bottomGrabber.superview)
|
||||
|
||||
let maxHeight: CGFloat = 346
|
||||
let minHeight: CGFloat = 108
|
||||
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
initialHeight = LCManager.shared.consoleSize.height
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in
|
||||
bottomGrabberPillView.alpha = 0.6
|
||||
}.startAnimation()
|
||||
|
||||
case .changed:
|
||||
|
||||
let resolvedHeight: CGFloat = {
|
||||
let initialEstimate = initialHeight + 2 * translation.y
|
||||
if initialEstimate <= maxHeight && initialEstimate > minHeight {
|
||||
return initialEstimate
|
||||
} else if initialEstimate > maxHeight {
|
||||
|
||||
var excess = initialEstimate - maxHeight
|
||||
excess = 25 * log(1/25 * excess + 1)
|
||||
|
||||
return maxHeight + excess
|
||||
} else {
|
||||
var excess = minHeight - initialEstimate
|
||||
excess = 7 * log(1/7 * excess + 1)
|
||||
|
||||
return minHeight - excess
|
||||
}
|
||||
}()
|
||||
|
||||
LCManager.shared.consoleSize.height = resolvedHeight
|
||||
LCManager.shared.consoleView.center.y = consoleCenterPoint.y
|
||||
|
||||
case .ended, .cancelled:
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 0.7) {
|
||||
if LCManager.shared.consoleSize.height > maxHeight {
|
||||
LCManager.shared.consoleSize.height = maxHeight
|
||||
}
|
||||
if LCManager.shared.consoleSize.height < minHeight {
|
||||
LCManager.shared.consoleSize.height = minHeight
|
||||
}
|
||||
|
||||
LCManager.shared.consoleView.center.y = self.consoleCenterPoint.y
|
||||
|
||||
// Animate autolayout updates.
|
||||
LCManager.shared.consoleWindow?.layoutIfNeeded()
|
||||
}.startAnimation()
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in
|
||||
bottomGrabberPillView.alpha = 0.3
|
||||
}.startAnimation()
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
var initialWidth = CGFloat.zero
|
||||
|
||||
static let kMaxConsoleWidth: CGFloat = UIScreen.portraitSize.width - 56
|
||||
|
||||
@objc func horizontalPanner(recognizer: UIPanGestureRecognizer) {
|
||||
|
||||
let translation = recognizer.translation(in: bottomGrabber.superview)
|
||||
|
||||
let maxWidth: CGFloat = Self.kMaxConsoleWidth
|
||||
let minWidth: CGFloat = 112
|
||||
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
initialWidth = LCManager.shared.consoleSize.width
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in
|
||||
rightGrabberPillView.alpha = 0.6
|
||||
}.startAnimation()
|
||||
|
||||
case .changed:
|
||||
|
||||
let resolvedWidth: CGFloat = {
|
||||
let initialEstimate = initialWidth + 2 * translation.x
|
||||
if initialEstimate <= maxWidth && initialEstimate > minWidth {
|
||||
return initialEstimate
|
||||
} else if initialEstimate > maxWidth {
|
||||
|
||||
var excess = initialEstimate - maxWidth
|
||||
excess = 25 * log(1/25 * excess + 1)
|
||||
|
||||
return maxWidth + excess
|
||||
} else {
|
||||
var excess = minWidth - initialEstimate
|
||||
excess = 7 * log(1/7 * excess + 1)
|
||||
|
||||
return minWidth - excess
|
||||
}
|
||||
}()
|
||||
|
||||
LCManager.shared.consoleSize.width = resolvedWidth
|
||||
LCManager.shared.consoleView.center.x = (UIScreen.main.nativeBounds.width * 1/2).rounded() / UIScreen.main.scale
|
||||
|
||||
case .ended, .cancelled:
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 0.7) {
|
||||
if LCManager.shared.consoleSize.width > maxWidth {
|
||||
LCManager.shared.consoleSize.width = maxWidth
|
||||
}
|
||||
if LCManager.shared.consoleSize.width < minWidth {
|
||||
LCManager.shared.consoleSize.width = minWidth
|
||||
}
|
||||
|
||||
LCManager.shared.consoleView.center.x = (UIScreen.main.nativeBounds.width * 1/2).rounded() / UIScreen.main.scale
|
||||
|
||||
// Animate autolayout updates.
|
||||
LCManager.shared.consoleWindow?.layoutIfNeeded()
|
||||
}.startAnimation()
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in
|
||||
rightGrabberPillView.alpha = 0.3
|
||||
}.startAnimation()
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PlatterView: UIView {
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.frame.size = UIScreen.portraitSize
|
||||
// Make sure bottom doesn't show on upwards pan.
|
||||
self.frame.size.height += 50
|
||||
self.frame.origin = possibleEndpoints[1]
|
||||
autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
|
||||
layer.shadowRadius = 10
|
||||
layer.shadowOpacity = 0.125
|
||||
layer.shadowOffset = CGSize(width: 0, height: 0)
|
||||
|
||||
layer.borderColor = dynamicBorderColor.cgColor
|
||||
layer.borderWidth = 1 / UIScreen.main.scale
|
||||
layer.cornerRadius = 30
|
||||
layer.cornerCurve = .continuous
|
||||
|
||||
let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial))
|
||||
|
||||
blurView.layer.cornerRadius = 30
|
||||
blurView.layer.cornerCurve = .continuous
|
||||
blurView.clipsToBounds = true
|
||||
|
||||
blurView.frame = bounds
|
||||
|
||||
addSubview(blurView)
|
||||
|
||||
LCManager.shared.consoleWindow?.addSubview(self)
|
||||
LCManager.shared.consoleWindow?.sendSubviewToBack(self)
|
||||
|
||||
_ = backgroundButton
|
||||
|
||||
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(platterPanner(recognizer:)))
|
||||
panRecognizer.maximumNumberOfTouches = 1
|
||||
addGestureRecognizer(panRecognizer)
|
||||
|
||||
let grabber = UIView()
|
||||
grabber.frame.size = CGSize(width: 36, height: 5)
|
||||
grabber.frame.origin.y = 10
|
||||
grabber.center.x = bounds.width / 2
|
||||
grabber.backgroundColor = .label
|
||||
grabber.alpha = 0.1
|
||||
grabber.layer.cornerRadius = 2.5
|
||||
grabber.layer.cornerCurve = .continuous
|
||||
addSubview(grabber)
|
||||
|
||||
let titleLabel = UILabel()
|
||||
titleLabel.text = "Resize Console"
|
||||
titleLabel.font = .systemFont(ofSize: 30, weight: .bold)
|
||||
titleLabel.sizeToFit()
|
||||
titleLabel.center.x = bounds.width / 2
|
||||
titleLabel.frame.origin.y = 28
|
||||
titleLabel.roundOriginToPixel()
|
||||
addSubview(titleLabel)
|
||||
|
||||
let subtitleLabel = UILabel()
|
||||
subtitleLabel.text = "Use the grabbers to resize the console."
|
||||
subtitleLabel.sizeToFit()
|
||||
subtitleLabel.alpha = 0.5
|
||||
subtitleLabel.center.x = bounds.width / 2
|
||||
subtitleLabel.frame.origin.y = titleLabel.frame.maxY + 8
|
||||
subtitleLabel.roundOriginToPixel()
|
||||
addSubview(subtitleLabel)
|
||||
|
||||
addSubview(resetButton)
|
||||
resetButton.center = CGPoint(x: UIScreen.portraitSize.width / 2 - 74,
|
||||
y: UIScreen.portraitSize.height - possibleEndpoints[0].y * 2)
|
||||
resetButton.roundOriginToPixel()
|
||||
|
||||
addSubview(doneButton)
|
||||
doneButton.center = CGPoint(x: UIScreen.portraitSize.width / 2 + 74,
|
||||
y: UIScreen.portraitSize.height - possibleEndpoints[0].y * 2)
|
||||
doneButton.roundOriginToPixel()
|
||||
}
|
||||
|
||||
lazy var backgroundButton: UIButton = {
|
||||
let backgroundButton = UIButton(primaryAction: UIAction(handler: { _ in
|
||||
ResizeController.shared.isActive = false
|
||||
self.dismiss()
|
||||
}))
|
||||
backgroundButton.frame.size = CGSize(width: self.frame.size.width, height: possibleEndpoints[0].y + 30)
|
||||
LCManager.shared.consoleWindow?.addSubview(backgroundButton)
|
||||
LCManager.shared.consoleWindow?.sendSubviewToBack(backgroundButton)
|
||||
return backgroundButton
|
||||
}()
|
||||
|
||||
lazy var doneButton: UIButton = {
|
||||
let button = UIButton(type: .custom)
|
||||
button.backgroundColor = .systemBlue.resolvedColor(with: UITraitCollection(userInterfaceStyle: .dark))
|
||||
button.setTitle("Done", for: .normal)
|
||||
button.setTitleColor(.white, for: .normal)
|
||||
button.titleLabel?.font = .systemFont(ofSize: 17, weight: .medium)
|
||||
button.frame.size = CGSize(width: 116, height: 52)
|
||||
button.layer.cornerRadius = 20
|
||||
button.layer.cornerCurve = .continuous
|
||||
|
||||
button.addAction(UIAction(handler: { _ in
|
||||
ResizeController.shared.isActive = false
|
||||
self.dismiss()
|
||||
}), for: .touchUpInside)
|
||||
|
||||
button.addActions(highlightAction: UIAction(handler: { _ in
|
||||
UIViewPropertyAnimator(duration: 0.25, dampingRatio: 1) {
|
||||
button.alpha = 0.6
|
||||
}.startAnimation()
|
||||
}), unhighlightAction: UIAction(handler: { _ in
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) {
|
||||
button.alpha = 1
|
||||
}.startAnimation()
|
||||
}))
|
||||
|
||||
return button
|
||||
}()
|
||||
|
||||
lazy var resetButton: UIButton = {
|
||||
let button = UIButton(type: .custom)
|
||||
button.backgroundColor = UIColor(dynamicProvider: { traitCollection in
|
||||
if traitCollection.userInterfaceStyle == .dark {
|
||||
return UIColor(white: 1, alpha: 0.125)
|
||||
} else {
|
||||
return UIColor(white: 0, alpha: 0.1)
|
||||
}
|
||||
})
|
||||
|
||||
button.setTitle("Reset", for: .normal)
|
||||
button.setTitleColor(.label, for: .normal)
|
||||
button.titleLabel?.font = .systemFont(ofSize: 17, weight: .medium)
|
||||
button.frame.size = CGSize(width: 116, height: 52)
|
||||
button.layer.cornerRadius = 20
|
||||
button.layer.cornerCurve = .continuous
|
||||
|
||||
button.addAction(UIAction(handler: { _ in
|
||||
|
||||
// Resolves a text view frame animation bug that occurs when *decreasing* text view width.
|
||||
if LCManager.shared.consoleSize.width > LCManager.shared.defaultConsoleSize.width {
|
||||
LCManager.shared.consoleTextView.frame.size.width = LCManager.shared.defaultConsoleSize.width
|
||||
}
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) {
|
||||
LCManager.shared.consoleSize = LCManager.shared.defaultConsoleSize
|
||||
LCManager.shared.consoleView.center = ResizeController.shared.consoleCenterPoint
|
||||
LCManager.shared.consoleWindow?.layoutIfNeeded()
|
||||
}.startAnimation()
|
||||
|
||||
}), for: .touchUpInside)
|
||||
|
||||
button.addActions(highlightAction: UIAction(handler: { _ in
|
||||
UIViewPropertyAnimator(duration: 0.25, dampingRatio: 1) {
|
||||
button.alpha = 0.6
|
||||
}.startAnimation()
|
||||
}), unhighlightAction: UIAction(handler: { _ in
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) {
|
||||
button.alpha = 1
|
||||
}.startAnimation()
|
||||
}))
|
||||
|
||||
return button
|
||||
}()
|
||||
|
||||
func reveal() {
|
||||
UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) {
|
||||
self.frame.origin = self.possibleEndpoints[0]
|
||||
}.startAnimation()
|
||||
|
||||
backgroundButton.isHidden = false
|
||||
}
|
||||
|
||||
func dismiss() {
|
||||
UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) {
|
||||
self.frame.origin = self.possibleEndpoints[1]
|
||||
}.startAnimation()
|
||||
|
||||
backgroundButton.isHidden = true
|
||||
}
|
||||
|
||||
let dynamicBorderColor = UIColor(dynamicProvider: { traitCollection in
|
||||
if traitCollection.userInterfaceStyle == .dark {
|
||||
return UIColor(white: 1, alpha: 0.075)
|
||||
} else {
|
||||
return UIColor(white: 0, alpha: 0.125)
|
||||
}
|
||||
})
|
||||
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
layer.borderColor = dynamicBorderColor.cgColor
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
lazy var possibleEndpoints = [CGPoint(x: 0, y: (UIScreen.hasRoundedCorners ? 44 : -8) + 63), CGPoint(x: 0, y: UIScreen.portraitSize.height + 5)]
|
||||
|
||||
var initialPlatterOriginY = CGFloat.zero
|
||||
|
||||
@objc func platterPanner(recognizer: UIPanGestureRecognizer) {
|
||||
|
||||
let translation = recognizer.translation(in: superview)
|
||||
let velocity = recognizer.velocity(in: superview)
|
||||
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
initialPlatterOriginY = frame.origin.y
|
||||
case .changed:
|
||||
|
||||
let resolvedOriginY: CGFloat = {
|
||||
let initialEstimate = initialPlatterOriginY + translation.y
|
||||
if initialEstimate >= possibleEndpoints[0].y {
|
||||
|
||||
// Stick buttons to bottom.
|
||||
[doneButton, resetButton,
|
||||
ResizeController.shared.bottomGrabber, ResizeController.shared.rightGrabber,
|
||||
LCManager.shared.consoleView
|
||||
].forEach {
|
||||
$0.transform = .identity
|
||||
}
|
||||
|
||||
return initialEstimate
|
||||
} else {
|
||||
var excess = possibleEndpoints[0].y - initialEstimate
|
||||
excess = 10 * log(1/10 * excess + 1)
|
||||
|
||||
// Stick buttons to bottom.
|
||||
doneButton.transform = .init(translationX: 0, y: excess)
|
||||
resetButton.transform = .init(translationX: 0, y: excess)
|
||||
|
||||
ResizeController.shared.bottomGrabber.transform = .init(translationX: 0, y: -excess / 2.5)
|
||||
ResizeController.shared.rightGrabber.transform = .init(translationX: 0, y: -excess / 2)
|
||||
LCManager.shared.consoleView.transform = .init(translationX: 0, y: -excess / 2)
|
||||
|
||||
return possibleEndpoints[0].y - excess
|
||||
}
|
||||
}()
|
||||
|
||||
if frame.origin.y > possibleEndpoints[0].y + 40 {
|
||||
ResizeController.shared.isActive = false
|
||||
} else {
|
||||
ResizeController.shared.isActive = true
|
||||
}
|
||||
|
||||
frame.origin.y = resolvedOriginY
|
||||
|
||||
case .ended, .cancelled:
|
||||
|
||||
// After the PiP is thrown, determine the best corner and re-target it there.
|
||||
let decelerationRate = UIScrollView.DecelerationRate.normal.rawValue
|
||||
|
||||
let projectedPosition = CGPoint(
|
||||
x: 0,
|
||||
y: frame.origin.y + project(initialVelocity: velocity.y, decelerationRate: decelerationRate)
|
||||
)
|
||||
|
||||
let nearestTargetPosition = nearestTargetTo(projectedPosition, possibleTargets: possibleEndpoints)
|
||||
|
||||
let relativeInitialVelocity = CGVector(
|
||||
dx: 0,
|
||||
dy: frame.origin.y >= possibleEndpoints[0].y
|
||||
? relativeVelocity(forVelocity: velocity.y, from: frame.origin.y, to: nearestTargetPosition.y)
|
||||
: 0
|
||||
)
|
||||
|
||||
let timingParameters = UISpringTimingParameters(damping: 1, response: 0.4, initialVelocity: relativeInitialVelocity)
|
||||
let positionAnimator = UIViewPropertyAnimator(duration: 0, timingParameters: timingParameters)
|
||||
positionAnimator.addAnimations { [self] in
|
||||
frame.origin = nearestTargetPosition
|
||||
|
||||
[doneButton, resetButton,
|
||||
ResizeController.shared.bottomGrabber, ResizeController.shared.rightGrabber,
|
||||
LCManager.shared.consoleView
|
||||
].forEach {
|
||||
$0.transform = .identity
|
||||
}
|
||||
}
|
||||
positionAnimator.startAnimation()
|
||||
|
||||
if nearestTargetPosition == possibleEndpoints[1] {
|
||||
ResizeController.shared.isActive = false
|
||||
backgroundButton.isHidden = true
|
||||
} else {
|
||||
ResizeController.shared.isActive = true
|
||||
}
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user