Compare commits

...

10 Commits

Author SHA1 Message Date
Duraid Abdul 63ebc8ed31 Fix border view frame 2022-09-18 23:39:48 -06:00
Duraid Abdul 64e18b18fc Fix keybaord avoidance 2022-09-18 23:31:00 -06:00
Duraid Abdul 9dcfa5accd iPhone 14 Pro support 2022-09-18 21:05:11 -06:00
Duraid Abdul adbb2d763f Update README.md 2022-05-14 19:15:47 -07:00
Duraid Abdul c08c26f4a7 Update README.md 2022-05-14 19:15:18 -07:00
Duraid Abdul d995119198 Update LCManager.swift 2022-02-01 12:26:52 -08:00
Duraid Abdul b4d7c06432 Update LCManager.swift 2022-01-26 21:02:54 -08:00
Duraid Abdul a7b95a4379 Update LCManager.swift 2022-01-26 02:18:26 -08:00
Duraid Abdul 3efe25f804 Update LCManager.swift 2022-01-25 17:15:30 -08:00
Duraid Abdul 0a3e28b28f Animation Improvements 2022-01-24 23:09:49 -08:00
2 changed files with 145 additions and 77 deletions
+1 -1
View File
@@ -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
+144 -76
View File
@@ -155,13 +155,14 @@ 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] {
guard let consoleWindow else { return [] }
let screenSize = viewController.view.frame.size
@@ -169,32 +170,31 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
let isPortraitNotchedPhone = UIDevice.current.hasNotch && viewController.view.frame.size.width < viewController.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 && viewController.view.frame.width > viewController.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 leftEndpointX = consoleSize.width / 2 + consoleWindow.safeAreaInsets.left + (isLandscapePhone ? 4 : 12) + (isLandscapeRightNotchedPhone ? -16 : 0)
let rightEndpointX = screenSize.width - (consoleSize.width / 2 + consoleWindow.safeAreaInsets.right) - (isLandscapePhone ? 4 : 12) + (isLandscapeLeftNotchedPhone ? 16 : 0)
let topEndpointY = consoleSize.height / 2 + consoleWindow.safeAreaInsets.top + 12 + (isPortraitNotchedPhone ? -10 : 0)
let bottomEndpointY = screenSize.height - consoleSize.height / 2 - (keyboardHeight ?? consoleWindow.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 +231,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 +280,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
@@ -312,7 +313,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
let tapRecognizer = UITapStartEndGestureRecognizer(target: self, action: #selector(consolePiPTapStartEnd(recognizer:)))
tapRecognizer.delegate = self
longPressRecognizer.minimumPressDuration = 0.1
longPressRecognizer.minimumPressDuration = 0.3
consoleView.addGestureRecognizer(panRecognizer)
consoleView.addGestureRecognizer(tapRecognizer)
@@ -383,9 +384,11 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
.first
if let windowScene = windowScene as? UIWindowScene {
windowSceneFound = true
UIWindow.swizzleStatusBarAppearanceOverride()
SwizzleTool().swizzleContextMenuReverseOrder()
consoleWindow = ConsoleWindow(windowScene: windowScene)
consoleWindow?.frame = UIScreen.main.bounds
consoleWindow?.windowLevel = UIWindow.Level.statusBar
@@ -397,8 +400,6 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
viewController.view.addSubview(consoleView)
UIWindow.swizzleStatusBarAppearanceOverride
updateConsoleOrigin()
}
}
@@ -482,7 +483,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()
@@ -500,7 +500,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)
@@ -637,15 +637,23 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
}
var dynamicReportTimer: Timer? {
willSet { dynamicReportTimer?.invalidate() }
willSet {
timerInvalidationCounter = 0
dynamicReportTimer?.invalidate()
}
}
var timerInvalidationCounter = 0
func systemReport() {
DispatchQueue.main.async { [self] in
if currentText != "" { print("\n") }
dynamicReportTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
guard consoleTextView.panGestureRecognizer.numberOfTouches == 0 else { return }
var _currentText = currentText
// To optimize performance, only scan the last 2500 characters of text for system report changes.
@@ -667,10 +675,19 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
if currentText != _currentText {
currentText = _currentText
timerInvalidationCounter = 0
} else {
// Invalidate the timer if there is no longer anything to update.
timer.invalidate()
timerInvalidationCounter += 1
// It has been 2 seconds and values have not changed.
if timerInvalidationCounter == 2 {
// Invalidate the timer if there is no longer anything to update.
dynamicReportTimer = nil
}
}
}
@@ -698,13 +715,20 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
if currentText != "" { print("\n") }
let safeAreaInsets = consoleWindow?.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))
"""
)
}
@@ -747,9 +771,11 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
func makeMenu() -> UIMenu {
let copy = UIAction(title: "Copy",
image: UIImage(systemName: "doc.on.doc"), handler: { _ in
self.copy()
let share = UIAction(title: "Share Text...",
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",
@@ -810,7 +836,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
image: nil, attributes: .disabled, handler: { _ in }
))
} else {
for key in keys.sorted(by: { $0 < $1 }) {
for key in keys.sorted(by: { $0.lowercased() < $1.lowercased() }) {
// Old LocalConsole Key Cleanup
guard !key.contains("LocalConsole_") else {
@@ -969,29 +995,33 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
var menuContent: [UIMenuElement] = []
if consoleTextView.text != "" {
menuContent.append(contentsOf: [copy, consoleActions])
menuContent.append(contentsOf: [share, consoleActions])
} else {
menuContent.append(resize)
}
menuContent.append(debugMenu)
return UIMenu(title: "", children: menuContent)
}
var consolePiPPopAnimator: UIViewPropertyAnimator?
@objc func longPressAction(recognizer: UILongPressGestureRecognizer) {
switch recognizer.state {
case .began:
guard !grabberMode else { return }
feedbackGenerator.selectionChanged()
feedbackGenerator.impactOccurred(intensity: 1)
scrollLocked = false
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in
consolePiPPopAnimator = UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in
consoleView.transform = .init(scaleX: 1.04, y: 1.04)
}
consolePiPPopAnimator?.startAnimation()
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in
consoleTextView.alpha = 0.5
menuButton.alpha = 0.5
}.startAnimation()
@@ -999,7 +1029,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
if !grabberMode { scrollLocked = true }
UIViewPropertyAnimator(duration: 0.8, dampingRatio: 0.5) { [self] in
UIViewPropertyAnimator(duration: 0.8, dampingRatio: 0.6) { [self] in
consoleView.transform = .identity
}.startAnimation()
@@ -1026,7 +1056,10 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
initialViewLocation = consoleView.center
}
guard !scrollLocked else { return }
guard !scrollLocked else {
isPressed = false
return
}
let translation = recognizer.translation(in: consoleView.superview)
let velocity = recognizer.velocity(in: consoleView.superview)
@@ -1092,29 +1125,37 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
}
}
// Animate touch down.
func consolePiPTouchDown() {
guard !grabberMode else { return }
UIViewPropertyAnimator(duration: 1.25, dampingRatio: 0.5) { [self] in
consoleView.transform = .init(scaleX: 0.95, y: 0.95)
}.startAnimation()
}
var consolePiPTouchDownAnimator: UIViewPropertyAnimator?
// Animate touch up.
func consolePiPTouchUp() {
UIViewPropertyAnimator(duration: 0.8, dampingRatio: 0.4) { [self] in
consoleView.transform = .init(scaleX: 1, y: 1)
}.startAnimation()
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in
if !grabberMode {
consoleTextView.alpha = 1
if !ResizeController.shared.isActive {
menuButton.alpha = 1
var isPressed: Bool = false {
didSet {
guard oldValue != isPressed else { return }
if isPressed {
guard !grabberMode else { return }
consolePiPTouchDownAnimator = UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) { [self] in
consoleView.transform = .init(scaleX: 0.96, y: 0.96)
}
consolePiPTouchDownAnimator?.startAnimation(afterDelay: 0.1)
} else {
consolePiPTouchDownAnimator?.stopAnimation(true)
consolePiPPopAnimator?.stopAnimation(true)
UIViewPropertyAnimator(duration: scrollLocked ? 0.4 : 0.7, dampingRatio: scrollLocked ? 1 : 0.45) { [self] in
consoleView.transform = .identity
}.startAnimation()
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in
if !grabberMode {
consoleTextView.alpha = 1
if !ResizeController.shared.isActive {
menuButton.alpha = 1
}
}
}.startAnimation()
}
}.startAnimation()
}
}
// Simulataneously listen to all gesture recognizers.
@@ -1125,11 +1166,11 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
@objc func consolePiPTapStartEnd(recognizer: UITapStartEndGestureRecognizer) {
switch recognizer.state {
case .began:
consolePiPTouchDown()
isPressed = true
case .changed:
break
case .ended, .cancelled, .possible, .failed:
consolePiPTouchUp()
isPressed = false
@unknown default:
break
}
@@ -1196,21 +1237,49 @@ 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" + "Sta" + "tus" + "Bar" + "Appe" + "arance")),
static func swizzleStatusBarAppearanceOverride() {
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.
func swizzleContextMenuReverseOrder() {
guard let originalMethod = class_getInstanceMethod(NSClassFromString("_" + "UI" + "Context" + "Menu" + "List" + "View").self, NSSelectorFromString("reverses" + "Action" + "Order")),
let swizzledMethod = class_getInstanceMethod(SwizzleTool.self, #selector(swizzled_reverses_Action_Order))
else { Swift.print("Swizzle Error Occurred"); return }
method_exchangeImplementations(originalMethod, swizzledMethod)
}
@objc func swizzled_reverses_Action_Order() -> Bool {
if let menu = self.value(forKey: "displayed" + "Menu") as? UIMenu,
menu.title == "Debug" || menu.title == "User" + "Defaults" {
return false
}
if let orig = self.value(forKey: "_" + "reverses" + "Action" + "Order") as? Bool {
return orig
}
return false
}
}
class LumaView: UIView {
lazy var visualEffectView: UIView = {
Bundle(path: "/Sys" + "tem/Lib" + "rary/Private" + "Frameworks/Material" + "Kit." + "framework")!.load()
Bundle(path: "/Sys" + "tem/Lib" + "rary/Private" + "Framework" + "s/Material" + "Kit." + "framework")!.load()
let Pill = NSClassFromString("MT" + "Luma" + "Dodge" + "Pill" + "View") as! UIView.Type
@@ -1342,7 +1411,6 @@ fileprivate func _debugPrint(_ items: Any) {
// Support for auto-rotate.
class ConsoleViewController: UIViewController {
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
// Cancel the panner console is being panned to allow for location manipulation.