Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1dcb6e3a57 | |||
| bfc3bc4fd2 | |||
| 74556927be | |||
| fee9e30df9 | |||
| 5e2b5feca4 | |||
| 3ffdde8904 | |||
| 63ebc8ed31 | |||
| 64e18b18fc | |||
| 9dcfa5accd | |||
| adbb2d763f | |||
| c08c26f4a7 | |||
| d995119198 |
@@ -13,7 +13,7 @@ Welcome to LocalConsole! This Swift Package makes on-device debugging easy with
|
||||
|
||||
2. Paste the following into the URL field: https://github.com/duraidabdul/LocalConsole/
|
||||
|
||||
3. Once the package dependancy has been added, import LocalConsole and create an easily accessible global instance of ```Console.shared```.
|
||||
3. Once the package dependancy has been added, import LocalConsole and create an easily accessible global instance of ```LCManager.shared```.
|
||||
```swift
|
||||
import LocalConsole
|
||||
|
||||
|
||||
@@ -136,11 +136,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
/// Strong reference keeps the window alive.
|
||||
var consoleWindow: ConsoleWindow?
|
||||
|
||||
/// Enables rotation.
|
||||
lazy var viewController = ConsoleViewController()
|
||||
lazy var consoleViewController = ConsoleViewController()
|
||||
|
||||
/// Note: The console always needs a parent view controller in order to display context menus. In this case, the parent controller will be the viewController.
|
||||
lazy var consoleView = UIView()
|
||||
@@ -155,46 +151,46 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
var scrollLocked = true
|
||||
|
||||
/// Feedback generator for the long press action.
|
||||
lazy var feedbackGenerator = UISelectionFeedbackGenerator()
|
||||
lazy var feedbackGenerator = UIImpactFeedbackGenerator(style: .soft)
|
||||
|
||||
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] {
|
||||
|
||||
let screenSize = viewController.view.frame.size
|
||||
let screenSize = consoleViewController.view.frame.size
|
||||
|
||||
// Must check for portrait mode manually here. UIDevice was reporting orientation incorrectly before.
|
||||
let isPortraitNotchedPhone = UIDevice.current.hasNotch && viewController.view.frame.size.width < viewController.view.frame.size.height
|
||||
let isPortraitNotchedPhone = UIDevice.current.hasNotch && consoleViewController.view.frame.size.width < consoleViewController.view.frame.size.height
|
||||
|
||||
// Fix incorrect reported orientation on phone.
|
||||
let isLandscapePhone = (UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight) && UIDevice.current.userInterfaceIdiom == .phone
|
||||
let isLandscapePhone = UIDevice.current.userInterfaceIdiom == .phone && consoleViewController.view.frame.width > consoleViewController.view.frame.height
|
||||
|
||||
let isLandscapeLeftNotchedPhone = UIDevice.current.orientation == .landscapeLeft
|
||||
&& UIDevice.current.userInterfaceIdiom == .phone
|
||||
&& UIDevice.current.hasNotch
|
||||
&& isLandscapePhone
|
||||
|
||||
let isLandscapeRightNotchedPhone = UIDevice.current.orientation == .landscapeRight
|
||||
&& UIDevice.current.userInterfaceIdiom == .phone
|
||||
&& UIDevice.current.hasNotch
|
||||
&& isLandscapePhone
|
||||
|
||||
let safeAreaInsets = consoleViewController.view.safeAreaInsets
|
||||
|
||||
let leftEndpointX = consoleSize.width / 2 + safeAreaInsets.left + (isLandscapePhone ? 4 : 12) + (isLandscapeRightNotchedPhone ? -16 : 0)
|
||||
let rightEndpointX = screenSize.width - (consoleSize.width / 2 + safeAreaInsets.right) - (isLandscapePhone ? 4 : 12) + (isLandscapeLeftNotchedPhone ? 16 : 0)
|
||||
let topEndpointY = consoleSize.height / 2 + safeAreaInsets.top + 12 + (isPortraitNotchedPhone ? -10 : 0)
|
||||
let bottomEndpointY = screenSize.height - consoleSize.height / 2 - (keyboardHeight ?? safeAreaInsets.bottom) - 12 + (isLandscapePhone ? 10 : 0)
|
||||
|
||||
if consoleSize.width < screenSize.width - 112 {
|
||||
|
||||
// Four endpoints, one for each corner.
|
||||
var endpoints = [
|
||||
|
||||
// Top endpoints.
|
||||
CGPoint(x: consoleSize.width / 2 + 12 + (isLandscapeLeftNotchedPhone ? 40 : isLandscapePhone ? 12 : 0),
|
||||
y: (isPortraitNotchedPhone ? 38 : isLandscapePhone ? 0 : 16) + consoleSize.height / 2 + 12),
|
||||
CGPoint(x: screenSize.width - consoleSize.width / 2 - 12 - (isLandscapeRightNotchedPhone ? 40 : isLandscapePhone ? 12 : 0),
|
||||
y: (isPortraitNotchedPhone ? 38 : isLandscapePhone ? 0 : 16) + consoleSize.height / 2 + 12),
|
||||
|
||||
// Bottom endpoints.
|
||||
CGPoint(x: consoleSize.width / 2 + 12 + (isLandscapeLeftNotchedPhone ? 40 : isLandscapePhone ? 12 : 0),
|
||||
y: screenSize.height - consoleSize.height / 2 - (keyboardHeight ?? consoleWindow?.safeAreaInsets.bottom ?? 0) - 12),
|
||||
CGPoint(x: screenSize.width - consoleSize.width / 2 - 12 - (isLandscapeRightNotchedPhone ? 40 : isLandscapePhone ? 12 : 02),
|
||||
y: screenSize.height - consoleSize.height / 2 - (keyboardHeight ?? consoleWindow?.safeAreaInsets.bottom ?? 0) - 12)]
|
||||
CGPoint(x: leftEndpointX, y: topEndpointY),
|
||||
CGPoint(x: rightEndpointX, y: topEndpointY),
|
||||
CGPoint(x: leftEndpointX, y: bottomEndpointY),
|
||||
CGPoint(x: rightEndpointX, y: bottomEndpointY),
|
||||
]
|
||||
|
||||
if consoleView.frame.minX <= 0 {
|
||||
|
||||
@@ -231,12 +227,11 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
return endpoints
|
||||
|
||||
} else {
|
||||
|
||||
// Two endpoints, one for the top, one for the bottom..
|
||||
var endpoints = [CGPoint(x: screenSize.width / 2,
|
||||
y: (UIScreen.hasRoundedCorners ? 38 : 16) + consoleSize.height / 2 + 12),
|
||||
CGPoint(x: screenSize.width / 2,
|
||||
y: screenSize.height - consoleSize.height / 2 - (keyboardHeight ?? consoleWindow?.safeAreaInsets.bottom ?? 0) - 12)]
|
||||
var endpoints = [
|
||||
CGPoint(x: screenSize.width / 2, y: topEndpointY),
|
||||
CGPoint(x: screenSize.width / 2, y: bottomEndpointY)
|
||||
]
|
||||
|
||||
if consoleView.frame.minX <= 0 {
|
||||
|
||||
@@ -281,10 +276,12 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
let _ = lumaView
|
||||
|
||||
borderView.frame = CGRect(x: -1, y: -1,
|
||||
width: consoleSize.width + 2,
|
||||
height: consoleSize.height + 2)
|
||||
borderView.layer.borderWidth = 1
|
||||
let borderWidth = 2 - 1 / consoleView.traitCollection.displayScale
|
||||
|
||||
borderView.frame = CGRect(x: -borderWidth, y: -borderWidth,
|
||||
width: consoleSize.width + 2 * borderWidth,
|
||||
height: consoleSize.height + 2 * borderWidth)
|
||||
borderView.layer.borderWidth = borderWidth
|
||||
borderView.layer.borderColor = UIColor(white: 1, alpha: 0.08).cgColor
|
||||
borderView.layer.cornerRadius = consoleView.layer.cornerRadius + 1
|
||||
borderView.layer.cornerCurve = .continuous
|
||||
@@ -356,15 +353,15 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
|
||||
}
|
||||
|
||||
/// Adds a LocalConsole window to the app's main scene.
|
||||
func configureWindow() {
|
||||
var windowSceneFound = false
|
||||
/// Adds a consoleViewController to the app's main window.
|
||||
func configureConsoleViewController() {
|
||||
var windowFound = false
|
||||
|
||||
// Update console cached based on last-cached origin.
|
||||
func updateConsoleOrigin() {
|
||||
snapToCachedEndpoint()
|
||||
|
||||
if consoleView.center.x < 0 || consoleView.center.x > viewController.view.frame.size.width {
|
||||
if consoleView.center.x < 0 || consoleView.center.x > consoleViewController.view.frame.size.width {
|
||||
grabberMode = true
|
||||
scrollLocked = !grabberMode
|
||||
|
||||
@@ -376,44 +373,48 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
// Configure console window.
|
||||
func fetchWindowScene() {
|
||||
let windowScene = UIApplication.shared
|
||||
.connectedScenes
|
||||
.filter { $0.activationState == .foregroundActive }
|
||||
.first
|
||||
|
||||
if let windowScene = windowScene as? UIWindowScene {
|
||||
func fetchWindow() -> UIWindow? {
|
||||
if #available(iOS 15.0, *) {
|
||||
let windowScene = UIApplication.shared
|
||||
.connectedScenes
|
||||
.filter { $0.activationState == .foregroundActive }
|
||||
.first
|
||||
|
||||
windowSceneFound = true
|
||||
|
||||
consoleWindow = ConsoleWindow(windowScene: windowScene)
|
||||
consoleWindow?.frame = UIScreen.main.bounds
|
||||
consoleWindow?.windowLevel = UIWindow.Level.statusBar
|
||||
consoleWindow?.isHidden = false
|
||||
|
||||
viewController.view = PassthroughView()
|
||||
|
||||
consoleWindow?.rootViewController = viewController
|
||||
|
||||
viewController.view.addSubview(consoleView)
|
||||
|
||||
UIWindow.swizzleStatusBarAppearanceOverride
|
||||
SwizzleTool().swizzleContextMenuReverseOrder()
|
||||
|
||||
updateConsoleOrigin()
|
||||
if let windowScene = windowScene as? UIWindowScene, let keyWindow = windowScene.keyWindow {
|
||||
return keyWindow
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return UIApplication.shared.windows.first
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func addConsoleToWindow(window: UIWindow) {
|
||||
SwizzleTool().swizzleContextMenuReverseOrder()
|
||||
|
||||
consoleViewController.view = PassthroughView()
|
||||
consoleViewController.view.addSubview(consoleView)
|
||||
|
||||
window.addSubview(consoleViewController.view)
|
||||
consoleViewController.view.frame = window.bounds
|
||||
consoleViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
|
||||
updateConsoleOrigin()
|
||||
}
|
||||
|
||||
/// Ensures the window is configured (i.e. scene has been found). If not, delay and wait for a scene to prepare itself, then try again.
|
||||
for i in 1...10 {
|
||||
|
||||
let delay = Double(i) / 10
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [self] in
|
||||
|
||||
guard !windowSceneFound else { return }
|
||||
guard !windowFound else { return }
|
||||
|
||||
fetchWindowScene()
|
||||
if let window = fetchWindow() {
|
||||
windowFound = true
|
||||
addConsoleToWindow(window: window)
|
||||
}
|
||||
|
||||
if isVisible {
|
||||
isVisible = false
|
||||
@@ -442,7 +443,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
if !isConsoleConfigured {
|
||||
DispatchQueue.main.async { [self] in
|
||||
configureWindow()
|
||||
configureConsoleViewController()
|
||||
configureConsole()
|
||||
isConsoleConfigured = true
|
||||
}
|
||||
@@ -483,7 +484,6 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
guard oldValue != grabberMode else { return }
|
||||
|
||||
if grabberMode {
|
||||
|
||||
lumaView.layer.cornerRadius = consoleView.layer.cornerRadius
|
||||
lumaHeightAnchor.constant = consoleView.frame.size.height
|
||||
consoleView.layoutIfNeeded()
|
||||
@@ -501,7 +501,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
lumaWidthAnchor.constant = -34
|
||||
lumaHeightAnchor.constant = 96
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in
|
||||
lumaView.layer.cornerRadius = 8
|
||||
lumaView.layer.cornerRadius = 9
|
||||
consoleView.layoutIfNeeded()
|
||||
}.startAnimation(afterDelay: 0.06)
|
||||
|
||||
@@ -533,6 +533,60 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
@objc func handleDeviceOrientationChange(previousSize: CGSize) {
|
||||
|
||||
// Cancel the panner console is being panned to allow for location manipulation.
|
||||
[LCManager.shared.panRecognizer, LCManager.shared.longPressRecognizer].forEach {
|
||||
$0.isEnabled.toggle(); $0.isEnabled.toggle()
|
||||
}
|
||||
|
||||
if UIDevice.current.userInterfaceIdiom != .pad && ResizeController.shared.isActive {
|
||||
ResizeController.shared.isActive = false
|
||||
ResizeController.shared.platterView.dismiss()
|
||||
}
|
||||
|
||||
if UIDevice.current.userInterfaceIdiom == .pad && ResizeController.shared.isActive {
|
||||
DispatchQueue.main.async {
|
||||
UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) {
|
||||
LCManager.shared.consoleView.center = ResizeController.shared.consoleCenterPoint
|
||||
}.startAnimation(afterDelay: 0.05)
|
||||
}
|
||||
} else {
|
||||
let consoleView = LCManager.shared.consoleView
|
||||
|
||||
let targetLocationEstimate: CGPoint = {
|
||||
var xPosition = consoleView.center.x
|
||||
var yPosition = consoleView.center.y
|
||||
|
||||
if consoleView.center.x > previousSize.width / 2 {
|
||||
xPosition += consoleViewController.view.frame.width - previousSize.width
|
||||
}
|
||||
|
||||
if consoleView.center.y > previousSize.height / 2 {
|
||||
yPosition += consoleViewController.view.frame.height - previousSize.height
|
||||
}
|
||||
|
||||
return CGPoint(x: xPosition, y: yPosition)
|
||||
}()
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) {
|
||||
consoleView.center = targetLocationEstimate
|
||||
}.startAnimation(afterDelay: 0.05)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
// Update portrait orientation menu option for resize controller.
|
||||
LCManager.shared.menuButton.menu = LCManager.shared.makeMenu()
|
||||
|
||||
// Reassess center of console based on target location estimate.
|
||||
UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) {
|
||||
consoleView.center = nearestTargetTo(consoleView.center, possibleTargets: LCManager.shared.possibleEndpoints)
|
||||
}.startAnimation(afterDelay: 0.05)
|
||||
|
||||
LCManager.shared.reassessGrabberMode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var hasShortened = false
|
||||
|
||||
public var isCharacterLimitDisabled = false
|
||||
@@ -581,10 +635,9 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
// MARK: Handle keyboard show/hide.
|
||||
private var keyboardHeight: CGFloat? = nil {
|
||||
didSet {
|
||||
|
||||
temporaryKeyboardHeightValueTracker = oldValue
|
||||
|
||||
if consoleView.center != possibleEndpoints[0] && consoleView.center != possibleEndpoints[1] {
|
||||
if possibleEndpoints.count > 2, consoleView.center != possibleEndpoints[0] && consoleView.center != possibleEndpoints[1] {
|
||||
let nearestTargetPosition = nearestTargetTo(consoleView.center, possibleTargets: possibleEndpoints.suffix(2))
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.55, dampingRatio: 1) {
|
||||
@@ -651,7 +704,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
if currentText != "" { print("\n") }
|
||||
|
||||
dynamicReportTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
|
||||
dynamicReportTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [self] timer in
|
||||
|
||||
guard consoleTextView.panGestureRecognizer.numberOfTouches == 0 else { return }
|
||||
|
||||
@@ -716,13 +769,20 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
if currentText != "" { print("\n") }
|
||||
|
||||
let safeAreaInsets = consoleViewController.view.safeAreaInsets ?? .zero
|
||||
|
||||
print(
|
||||
"""
|
||||
Screen Size: \(UIScreen.main.bounds.size)
|
||||
Screen Corner Radius: \(UIScreen.main.value(forKey: "_displ" + "ayCorn" + "erRa" + "dius") as! CGFloat)
|
||||
Screen Scale: \(UIScreen.main.scale)
|
||||
Max Frame Rate: \(UIScreen.main.maximumFramesPerSecond) Hz
|
||||
Brightness: \(String(format: "%.2f", UIScreen.main.brightness))
|
||||
Screen Size: \(UIScreen.main.bounds.size)
|
||||
Corner Radius: \(UIScreen.main.value(forKey: "_displ" + "ayCorn" + "erRa" + "dius") as! CGFloat)
|
||||
Screen Scale: \(UIScreen.main.scale)
|
||||
Max Frame Rate: \(UIScreen.main.maximumFramesPerSecond) Hz
|
||||
Brightness: \(String(format: "%.2f", UIScreen.main.brightness))
|
||||
|
||||
Safe Area Insets: top: \(String(describing: safeAreaInsets.top))
|
||||
left: \(String(describing: safeAreaInsets.left))
|
||||
bottom: \(String(describing: safeAreaInsets.bottom))
|
||||
right: \(String(describing: safeAreaInsets.right))
|
||||
"""
|
||||
)
|
||||
}
|
||||
@@ -764,36 +824,32 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
public var showAllUserDefaultsKeys = false
|
||||
|
||||
func makeMenu() -> UIMenu {
|
||||
let share = UIAction(title: "Share Text...", image: UIImage(systemName: "square.and.arrow.up")) { _ in
|
||||
let activityViewController = UIActivityViewController(
|
||||
activityItems: [self.consoleTextView.text ?? ""],
|
||||
applicationActivities: nil
|
||||
)
|
||||
self.consoleViewController.present(activityViewController, animated: true)
|
||||
}
|
||||
|
||||
let share = UIAction(title: "Share",
|
||||
image: UIImage(systemName: "square.and.arrow.up"), handler: { _ in
|
||||
let activityViewController = UIActivityViewController(activityItems: [self.consoleTextView.text ?? ""],
|
||||
applicationActivities: nil)
|
||||
self.viewController.present(activityViewController, animated: true)
|
||||
})
|
||||
|
||||
let resize = UIAction(title: "Resize Console",
|
||||
image: UIImage(systemName: "arrow.left.and.right.square"), handler: { _ in
|
||||
let resize = UIAction(title: "Resize Console", image: UIImage(systemName: "arrow.up.backward.and.arrow.down.forward")) { _ in
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
ResizeController.shared.isActive.toggle()
|
||||
ResizeController.shared.platterView.reveal()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// If device is phone in landscape, disable resize controller.
|
||||
if UIDevice.current.userInterfaceIdiom == .phone && viewController.view.frame.width > viewController.view.frame.height {
|
||||
if UIDevice.current.userInterfaceIdiom == .phone && consoleViewController.view.frame.width > consoleViewController.view.frame.height {
|
||||
resize.attributes = .disabled
|
||||
if #available(iOS 15, *) {
|
||||
resize.subtitle = "Portrait Orientation Only"
|
||||
}
|
||||
}
|
||||
|
||||
let clear = UIAction(title: "Clear Console",
|
||||
image: UIImage(systemName: "xmark.square"), handler: { _ in
|
||||
let clear = UIAction(title: "Clear Console", image: UIImage(systemName: "delete.backward"), attributes: .destructive) { _ in
|
||||
self.clear()
|
||||
})
|
||||
|
||||
let consoleActions = UIMenu(title: "", options: .displayInline, children: [clear, resize])
|
||||
}
|
||||
|
||||
var frameSymbol = "rectangle.3.offgrid"
|
||||
|
||||
@@ -888,7 +944,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in
|
||||
}))
|
||||
|
||||
self.viewController.present(alertController,
|
||||
self.consoleViewController.present(alertController,
|
||||
animated: true)
|
||||
}
|
||||
action.subtitle = "\(value)"
|
||||
@@ -917,16 +973,17 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
|
||||
let viewFrames = UIAction(title: debugBordersEnabled ? "Hide View Frames" : "Show View Frames",
|
||||
image: UIImage(systemName: frameSymbol), handler: { _ in
|
||||
let viewFrames = UIAction(
|
||||
title: debugBordersEnabled ? "Hide View Frames" : "Show View Frames",
|
||||
image: UIImage(systemName: frameSymbol)
|
||||
) { _ in
|
||||
self.debugBordersEnabled.toggle()
|
||||
self.menuButton.menu = self.makeMenu()
|
||||
})
|
||||
}
|
||||
|
||||
let systemReport = UIAction(title: "System Report",
|
||||
image: UIImage(systemName: "cpu"), handler: { _ in
|
||||
let systemReport = UIAction(title: "System Report", image: UIImage(systemName: "cpu")) { _ in
|
||||
self.systemReport()
|
||||
})
|
||||
}
|
||||
|
||||
// Show the right glyph for the current device being used.
|
||||
let deviceSymbol: String = {
|
||||
@@ -950,13 +1007,15 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
}()
|
||||
|
||||
let displayReport = UIAction(title: "Display Report",
|
||||
image: UIImage(systemName: deviceSymbol), handler: { _ in
|
||||
let displayReport = UIAction(title: "Display Report", image: UIImage(systemName: deviceSymbol)) { _ in
|
||||
self.displayReport()
|
||||
})
|
||||
}
|
||||
|
||||
let respring = UIAction(title: "Restart Spring" + "Board",
|
||||
image: UIImage(systemName: "apps.iphone"), handler: { _ in
|
||||
let terminateApplication = UIAction(title: "Terminate App", image: UIImage(systemName: "xmark"), attributes: .destructive) { _ in
|
||||
UIApplication.shared.perform(NSSelectorFromString("terminateWithSuccess"))
|
||||
}
|
||||
|
||||
let respring = UIAction(title: "Restart Spring" + "Board", image: UIImage(systemName: "arrowtriangle.backward"), attributes: .destructive) { _ in
|
||||
|
||||
guard let window = UIApplication.shared.windows.first else { return }
|
||||
|
||||
@@ -972,28 +1031,36 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
for _ in 0...1000 {
|
||||
DispatchQueue.global(qos: .default).async {
|
||||
|
||||
// This will cause jetsam to terminate SpringBoard.
|
||||
// This will cause jetsam to terminate backboardd.
|
||||
while true {
|
||||
window.snapshotView(afterScreenUpdates: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
debugActions.append(contentsOf: [viewFrames, systemReport, displayReport, respring])
|
||||
debugActions.append(contentsOf: [viewFrames, systemReport, displayReport])
|
||||
let destructActions = [terminateApplication , respring]
|
||||
|
||||
let debugMenu = UIMenu(title: "", options: .displayInline,
|
||||
children: [UIMenu(title: "Debug", image: UIImage(systemName: "ant"),
|
||||
children: debugActions)])
|
||||
let debugMenu = UIMenu(
|
||||
title: "Debug", image: UIImage(systemName: "ant"),
|
||||
children: [
|
||||
UIMenu(title: "", options: .displayInline, children: debugActions),
|
||||
UIMenu(title: "", options: .displayInline, children: destructActions),
|
||||
]
|
||||
)
|
||||
|
||||
var menuContent: [UIMenuElement] = []
|
||||
|
||||
if consoleTextView.text != "" {
|
||||
menuContent.append(contentsOf: [share, consoleActions])
|
||||
menuContent.append(contentsOf: [UIMenu(title: "", options: .displayInline, children: [share, resize])])
|
||||
} else {
|
||||
menuContent.append(resize)
|
||||
menuContent.append(UIMenu(title: "", options: .displayInline, children: [resize]))
|
||||
}
|
||||
menuContent.append(debugMenu)
|
||||
if consoleTextView.text != "" {
|
||||
menuContent.append(UIMenu(title: "", options: .displayInline, children: [clear]))
|
||||
}
|
||||
|
||||
return UIMenu(title: "", children: menuContent)
|
||||
}
|
||||
@@ -1006,7 +1073,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
guard !grabberMode else { return }
|
||||
|
||||
feedbackGenerator.selectionChanged()
|
||||
feedbackGenerator.impactOccurred(intensity: 1)
|
||||
|
||||
scrollLocked = false
|
||||
|
||||
@@ -1112,7 +1179,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
func reassessGrabberMode() {
|
||||
if consoleView.frame.maxX > 30 && consoleView.frame.minX < viewController.view.frame.size.width - 30 {
|
||||
if consoleView.frame.maxX > 30 && consoleView.frame.minX < consoleViewController.view.frame.size.width - 30 {
|
||||
grabberMode = false
|
||||
} else {
|
||||
grabberMode = true
|
||||
@@ -1171,31 +1238,14 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
// Custom window for the console to appear above other windows while passing touches down.
|
||||
class ConsoleWindow: UIWindow {
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
|
||||
if let hitView = super.hitTest(point, with: event) {
|
||||
return hitView.isKind(of: ConsoleWindow.self) ? nil : hitView
|
||||
}
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
}
|
||||
|
||||
// Custom view for the console to appear above other windows while passing touches down.
|
||||
// Custom view that is passes touches .
|
||||
class PassthroughView: UIView {
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
|
||||
if let hitView = super.hitTest(point, with: event) {
|
||||
return hitView.isKind(of: PassthroughView.self) ? nil : hitView
|
||||
}
|
||||
return super.hitTest(point, with: event)
|
||||
let hitView = super.hitTest(point, with: event)
|
||||
return hitView == self ? nil : hitView
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
import UIKit.UIGestureRecognizerSubclass
|
||||
|
||||
public class UITapStartEndGestureRecognizer: UITapGestureRecognizer {
|
||||
@@ -1228,21 +1278,6 @@ extension UIView {
|
||||
}
|
||||
}
|
||||
|
||||
extension UIWindow {
|
||||
|
||||
/// Make sure this window does not have control over the status bar appearance.
|
||||
static let swizzleStatusBarAppearanceOverride: Void = {
|
||||
guard let originalMethod = class_getInstanceMethod(UIWindow.self, NSSelectorFromString("_can" + "Affect" + "Status" + "Bar" + "Appearance")),
|
||||
let swizzledMethod = class_getInstanceMethod(UIWindow.self, #selector(swizzled_statusBarAppearance))
|
||||
else { return }
|
||||
method_exchangeImplementations(originalMethod, swizzledMethod)
|
||||
}()
|
||||
|
||||
@objc func swizzled_statusBarAppearance() -> Bool {
|
||||
return isKeyWindow
|
||||
}
|
||||
}
|
||||
|
||||
class SwizzleTool: NSObject {
|
||||
|
||||
/// Ensure context menus always show in a non reversed order.
|
||||
@@ -1273,41 +1308,56 @@ class SwizzleTool: NSObject {
|
||||
class LumaView: UIView {
|
||||
lazy var visualEffectView: UIView = {
|
||||
Bundle(path: "/Sys" + "tem/Lib" + "rary/Private" + "Framework" + "s/Material" + "Kit." + "framework")!.load()
|
||||
|
||||
let Pill = NSClassFromString("MT" + "Luma" + "Dodge" + "Pill" + "View") as! UIView.Type
|
||||
|
||||
let pillView = Pill.init()
|
||||
|
||||
enum Style: Int {
|
||||
case none = 0
|
||||
case thin = 1
|
||||
case gray = 2
|
||||
case black = 3
|
||||
case white = 4
|
||||
|
||||
if let Pill = NSClassFromString("MT" + "Luma" + "Dodge" + "Pill" + "View") as? UIView.Type {
|
||||
|
||||
let pillView = Pill.init()
|
||||
|
||||
enum Style: Int {
|
||||
case none = 0
|
||||
case thin = 1
|
||||
case gray = 2
|
||||
case black = 3
|
||||
case white = 4
|
||||
}
|
||||
|
||||
enum BackgroundLuminance: Int {
|
||||
case unknown = 0
|
||||
case dark = 1
|
||||
case light = 2
|
||||
}
|
||||
|
||||
pillView.setValue(2, forKey: "style")
|
||||
pillView.setValue(1, forKey: "background" + "Luminance")
|
||||
pillView.perform(NSSelectorFromString("_" + "update" + "Style"))
|
||||
|
||||
addSubview(pillView)
|
||||
|
||||
pillView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
pillView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
pillView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
pillView.topAnchor.constraint(equalTo: topAnchor),
|
||||
pillView.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||
])
|
||||
|
||||
return pillView
|
||||
} else {
|
||||
let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterialDark))
|
||||
addSubview(visualEffectView)
|
||||
|
||||
visualEffectView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
visualEffectView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
visualEffectView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
visualEffectView.topAnchor.constraint(equalTo: topAnchor),
|
||||
visualEffectView.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||
])
|
||||
|
||||
return visualEffectView
|
||||
}
|
||||
|
||||
enum BackgroundLuminance: Int {
|
||||
case unknown = 0
|
||||
case dark = 1
|
||||
case light = 2
|
||||
}
|
||||
|
||||
pillView.setValue(2, forKey: "style")
|
||||
pillView.setValue(1, forKey: "background" + "Luminance")
|
||||
pillView.perform(NSSelectorFromString("_" + "update" + "Style"))
|
||||
|
||||
addSubview(pillView)
|
||||
|
||||
pillView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
pillView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
pillView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
pillView.topAnchor.constraint(equalTo: topAnchor),
|
||||
pillView.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||
])
|
||||
|
||||
return pillView
|
||||
}()
|
||||
|
||||
lazy var foregroundView: UIView = {
|
||||
@@ -1403,60 +1453,16 @@ fileprivate func _debugPrint(_ items: Any) {
|
||||
|
||||
// Support for auto-rotate.
|
||||
class ConsoleViewController: UIViewController {
|
||||
var previousSize: CGSize?
|
||||
|
||||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
|
||||
// Cancel the panner console is being panned to allow for location manipulation.
|
||||
[LCManager.shared.panRecognizer, LCManager.shared.longPressRecognizer].forEach {
|
||||
$0.isEnabled.toggle(); $0.isEnabled.toggle()
|
||||
}
|
||||
|
||||
if UIDevice.current.userInterfaceIdiom != .pad && ResizeController.shared.isActive {
|
||||
ResizeController.shared.isActive = false
|
||||
ResizeController.shared.platterView.dismiss()
|
||||
}
|
||||
|
||||
if UIDevice.current.userInterfaceIdiom == .pad && ResizeController.shared.isActive {
|
||||
DispatchQueue.main.async {
|
||||
UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) {
|
||||
LCManager.shared.consoleView.center = ResizeController.shared.consoleCenterPoint
|
||||
}.startAnimation(afterDelay: 0.05)
|
||||
}
|
||||
|
||||
} else {
|
||||
let consoleView = LCManager.shared.consoleView
|
||||
let oldSize = LCManager.shared.viewController.view.frame.size
|
||||
|
||||
let targetLocationEstimate: CGPoint = {
|
||||
var xPosition = consoleView.center.x
|
||||
var yPosition = consoleView.center.y
|
||||
|
||||
if consoleView.center.x > oldSize.width / 2 {
|
||||
xPosition += size.width - oldSize.width
|
||||
}
|
||||
|
||||
if consoleView.center.y > oldSize.height / 2 {
|
||||
yPosition += size.height - oldSize.height
|
||||
}
|
||||
|
||||
return CGPoint(x: xPosition, y: yPosition)
|
||||
}()
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) {
|
||||
consoleView.center = targetLocationEstimate
|
||||
}.startAnimation(afterDelay: 0.05)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
// Update portrait orientation menu option for resize controller.
|
||||
LCManager.shared.menuButton.menu = LCManager.shared.makeMenu()
|
||||
|
||||
// Reassess center of console based on target location estimate.
|
||||
UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) {
|
||||
consoleView.center = nearestTargetTo(consoleView.center, possibleTargets: LCManager.shared.possibleEndpoints)
|
||||
}.startAnimation(afterDelay: 0.05)
|
||||
|
||||
LCManager.shared.reassessGrabberMode()
|
||||
}
|
||||
override func viewWillLayoutSubviews() {
|
||||
super.viewWillLayoutSubviews()
|
||||
|
||||
if let previousSize = previousSize, previousSize != view.bounds.size {
|
||||
LCManager.shared.handleDeviceOrientationChange(previousSize: previousSize)
|
||||
self.previousSize = view.bounds.size
|
||||
} else if previousSize == nil {
|
||||
previousSize = view.bounds.size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class ResizeController {
|
||||
lazy var platterView = PlatterView(frame: .zero)
|
||||
|
||||
var consoleCenterPoint: CGPoint {
|
||||
let containerViewSize = LCManager.shared.viewController.view.frame.size
|
||||
let containerViewSize = LCManager.shared.consoleViewController.view.frame.size
|
||||
|
||||
return CGPoint(x: (containerViewSize.width * UIScreen.main.scale / 2).rounded() / UIScreen.main.scale,
|
||||
y: (containerViewSize.height * UIScreen.main.scale / 2).rounded() / UIScreen.main.scale
|
||||
@@ -50,7 +50,7 @@ class ResizeController {
|
||||
|
||||
lazy var bottomGrabber: UIView = {
|
||||
let view = UIView()
|
||||
LCManager.shared.consoleWindow?.addSubview(view)
|
||||
LCManager.shared.consoleViewController.view.addSubview(view)
|
||||
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
@@ -80,7 +80,7 @@ class ResizeController {
|
||||
|
||||
lazy var rightGrabber: UIView = {
|
||||
let view = UIView()
|
||||
LCManager.shared.consoleWindow?.addSubview(view)
|
||||
LCManager.shared.consoleViewController.view.addSubview(view)
|
||||
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
@@ -117,7 +117,7 @@ class ResizeController {
|
||||
_ = rightGrabber
|
||||
|
||||
// Ensure initial autolayout is performed unanimated.
|
||||
LCManager.shared.consoleWindow?.layoutIfNeeded()
|
||||
LCManager.shared.consoleViewController.view.layoutIfNeeded()
|
||||
|
||||
if #available(iOS 15, *) {
|
||||
FrameRateRequest.shared.perform(duration: 1.5)
|
||||
@@ -138,17 +138,17 @@ class ResizeController {
|
||||
}
|
||||
|
||||
// Ensure background color animates in right the first time.
|
||||
LCManager.shared.consoleWindow?.backgroundColor = .clear
|
||||
LCManager.shared.consoleViewController.view.backgroundColor = .clear
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) {
|
||||
LCManager.shared.consoleView.center = self.consoleCenterPoint
|
||||
|
||||
// Update grabbers (layout constraints)
|
||||
LCManager.shared.consoleWindow?.layoutIfNeeded()
|
||||
LCManager.shared.consoleViewController.view.layoutIfNeeded()
|
||||
|
||||
LCManager.shared.menuButton.alpha = 0
|
||||
|
||||
LCManager.shared.consoleWindow?.backgroundColor = UIColor(dynamicProvider: { traitCollection in
|
||||
LCManager.shared.consoleViewController.view.backgroundColor = UIColor(dynamicProvider: { traitCollection in
|
||||
UIColor(white: 0, alpha: traitCollection.userInterfaceStyle == .light ? 0.1 : 0.3)
|
||||
})
|
||||
}.startAnimation()
|
||||
@@ -181,11 +181,11 @@ class ResizeController {
|
||||
LCManager.shared.snapToCachedEndpoint()
|
||||
|
||||
// Update grabbers (layout constraints)
|
||||
LCManager.shared.consoleWindow?.layoutIfNeeded()
|
||||
LCManager.shared.consoleViewController.view.layoutIfNeeded()
|
||||
|
||||
LCManager.shared.menuButton.alpha = 1
|
||||
|
||||
LCManager.shared.consoleWindow?.backgroundColor = .clear
|
||||
LCManager.shared.consoleViewController.view.backgroundColor = .clear
|
||||
}.startAnimation()
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.2, dampingRatio: 1) { [self] in
|
||||
@@ -277,7 +277,7 @@ class ResizeController {
|
||||
LCManager.shared.consoleView.center.y = self.consoleCenterPoint.y
|
||||
|
||||
// Animate autolayout updates.
|
||||
LCManager.shared.consoleWindow?.layoutIfNeeded()
|
||||
LCManager.shared.consoleViewController.view.layoutIfNeeded()
|
||||
}.startAnimation()
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in
|
||||
@@ -358,7 +358,7 @@ class ResizeController {
|
||||
LCManager.shared.consoleView.center.x = (UIScreen.main.nativeBounds.width * 1/2).rounded() / UIScreen.main.scale
|
||||
|
||||
// Animate autolayout updates.
|
||||
LCManager.shared.consoleWindow?.layoutIfNeeded()
|
||||
LCManager.shared.consoleViewController.view.layoutIfNeeded()
|
||||
}.startAnimation()
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in
|
||||
@@ -396,8 +396,8 @@ class PlatterView: UIView {
|
||||
|
||||
addSubview(blurView)
|
||||
|
||||
LCManager.shared.viewController.view.addSubview(self)
|
||||
LCManager.shared.viewController.view.sendSubviewToBack(self)
|
||||
LCManager.shared.consoleViewController.view.addSubview(self)
|
||||
LCManager.shared.consoleViewController.view.sendSubviewToBack(self)
|
||||
|
||||
_ = backgroundButton
|
||||
|
||||
@@ -466,8 +466,8 @@ class PlatterView: UIView {
|
||||
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)
|
||||
LCManager.shared.consoleViewController.view.addSubview(backgroundButton)
|
||||
LCManager.shared.consoleViewController.view.sendSubviewToBack(backgroundButton)
|
||||
return backgroundButton
|
||||
}()
|
||||
|
||||
@@ -525,7 +525,7 @@ class PlatterView: UIView {
|
||||
LCManager.shared.consoleSize = LCManager.shared.defaultConsoleSize
|
||||
LCManager.shared.lumaHeightAnchor.constant = LCManager.shared.defaultConsoleSize.height
|
||||
LCManager.shared.consoleView.center = ResizeController.shared.consoleCenterPoint
|
||||
LCManager.shared.consoleWindow?.layoutIfNeeded()
|
||||
LCManager.shared.consoleViewController.view.layoutIfNeeded()
|
||||
}.startAnimation()
|
||||
|
||||
}), for: .touchUpInside)
|
||||
@@ -544,7 +544,7 @@ class PlatterView: UIView {
|
||||
}()
|
||||
|
||||
func configureFrame() {
|
||||
self.frame.size = LCManager.shared.viewController.view.frame.size
|
||||
self.frame.size = LCManager.shared.consoleViewController.view.frame.size
|
||||
// Make sure bottom doesn't show on upwards pan.
|
||||
self.frame.size.height += 50
|
||||
self.frame.origin = possibleEndpoints[1]
|
||||
@@ -593,7 +593,7 @@ class PlatterView: UIView {
|
||||
}
|
||||
|
||||
var possibleEndpoints: [CGPoint] { return [CGPoint(x: 0, y: (UIScreen.hasRoundedCorners ? 44 : -8) + 63),
|
||||
CGPoint(x: 0, y: LCManager.shared.viewController.view.frame.size.height + 5)]
|
||||
CGPoint(x: 0, y: LCManager.shared.consoleViewController.view.frame.size.height + 5)]
|
||||
}
|
||||
|
||||
var initialPlatterOriginY = CGFloat.zero
|
||||
|
||||
Reference in New Issue
Block a user