Compare commits

..

14 Commits

Author SHA1 Message Date
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
Duraid Abdul 009fab95be UserDefaults Support
Signed-off-by: Duraid Abdul <duraidabdul@users.noreply.github.com>
2022-01-24 19:06:49 -08:00
Duraid Abdul a1275a7f49 Update LCManager.swift 2022-01-22 00:09:49 -08:00
Duraid Abdul e08d439d2b Various Improvements 2022-01-22 00:06:35 -08:00
Duraid Abdul da9d78f559 Merge branch 'main' of https://github.com/duraidabdul/LocalConsole 2022-01-21 12:47:53 -08:00
Duraid Abdul 50e4ce4e03 Update LCManager.swift 2022-01-21 12:47:50 -08:00
Duraid Abdul ae73be37b4 Update README.md 2022-01-17 12:08:39 -08:00
Duraid Abdul eeece2fda8 Update LCManager.swift
Fix for touch passthrough.
2021-12-26 00:39:38 -08:00
Duraid Abdul 01ee69b8c5 iPad Support, Landscape Support on iPhone
Full support for iPad screen sizes and screen rotation on all devices.
2021-12-26 00:27:32 -08:00
Duraid Abdul 72d9b1fbd5 Update LCManager.swift 2021-11-20 01:06:15 -07:00
3 changed files with 587 additions and 161 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ let consoleManager = LCManager.shared
```
## **Usage**
Once prepared, the localConsole can be used throughout your project.
Once prepared, the consoleManager can be used throughout your project.
```swift
// Activate the console view.
+493 -118
View File
@@ -82,8 +82,8 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
}.startAnimation()
grabberMode = false
UserDefaults.standard.set(consoleView.center.x, forKey: "LocalConsole_X")
UserDefaults.standard.set(consoleView.center.y, forKey: "LocalConsole_Y")
UserDefaults.standard.set(consoleView.center.x, forKey: "LocalConsole.X")
UserDefaults.standard.set(consoleView.center.y, forKey: "LocalConsole.Y")
}), for: .touchUpInside)
consoleView.addSubview(button)
@@ -131,20 +131,22 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
// TODO: Snap to nearest position.
UserDefaults.standard.set(consoleSize.width, forKey: "LocalConsole_Width")
UserDefaults.standard.set(consoleSize.height, forKey: "LocalConsole_Height")
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 parent view controller in order to display context menus.
lazy var viewController = UIViewController()
lazy var consoleView = viewController.view!
/// Enables rotation.
lazy var viewController = 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()
/// Text view that displays printed items.
lazy var consoleTextView = InvertedTextView()
lazy var consoleTextView = InvertedTextView()
/// Button that reveals menu.
lazy var menuButton = UIButton()
@@ -161,22 +163,38 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
/// Gesture endpoints. Each point represents a corner of the screen. TODO: Handle screen rotation.
var possibleEndpoints: [CGPoint] {
if consoleSize.width < UIScreen.portraitSize.width - 112 {
let screenSize = viewController.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
// Fix incorrect reported orientation on phone.
let isLandscapePhone = (UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight) && UIDevice.current.userInterfaceIdiom == .phone
let isLandscapeLeftNotchedPhone = UIDevice.current.orientation == .landscapeLeft
&& UIDevice.current.userInterfaceIdiom == .phone
&& UIDevice.current.hasNotch
let isLandscapeRightNotchedPhone = UIDevice.current.orientation == .landscapeRight
&& UIDevice.current.userInterfaceIdiom == .phone
&& UIDevice.current.hasNotch
if consoleSize.width < screenSize.width - 112 {
// Four endpoints, one for each corner.
var endpoints = [
// Top endpoints.
CGPoint(x: consoleSize.width / 2 + 12,
y: (UIScreen.hasRoundedCorners ? 38 : 16) + consoleSize.height / 2 + 12),
CGPoint(x: UIScreen.portraitSize.width - consoleSize.width / 2 - 12,
y: (UIScreen.hasRoundedCorners ? 38 : 16) + consoleSize.height / 2 + 12),
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,
y: UIScreen.portraitSize.height - consoleSize.height / 2 - (keyboardHeight ?? consoleWindow?.safeAreaInsets.bottom ?? 0) - 12),
CGPoint(x: UIScreen.portraitSize.width - consoleSize.width / 2 - 12,
y: UIScreen.portraitSize.height - consoleSize.height / 2 - (keyboardHeight ?? consoleWindow?.safeAreaInsets.bottom ?? 0) - 12)]
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)]
if consoleView.frame.minX <= 0 {
@@ -184,25 +202,29 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
endpoints = [endpoints[0], endpoints[2]]
// Left edge hiding endpoints.
if consoleView.center.y < (UIScreen.portraitSize.height - (temporaryKeyboardHeightValueTracker ?? 0)) / 2 {
endpoints.append(CGPoint(x: -consoleSize.width / 2 + 28,
y: endpoints[0].y))
} else {
endpoints.append(CGPoint(x: -consoleSize.width / 2 + 28,
y: endpoints[1].y))
if !isLandscapeLeftNotchedPhone {
if consoleView.center.y < (screenSize.height - (temporaryKeyboardHeightValueTracker ?? 0)) / 2 {
endpoints.append(CGPoint(x: -consoleSize.width / 2 + 28,
y: endpoints[0].y))
} else {
endpoints.append(CGPoint(x: -consoleSize.width / 2 + 28,
y: endpoints[1].y))
}
}
} else if consoleView.frame.maxX >= UIScreen.portraitSize.width {
} else if consoleView.frame.maxX >= screenSize.width {
// Right edge endpoints.
endpoints = [endpoints[1], endpoints[3]]
// Right edge hiding endpoints.
if consoleView.center.y < (UIScreen.portraitSize.height - (temporaryKeyboardHeightValueTracker ?? 0)) / 2 {
endpoints.append(CGPoint(x: UIScreen.portraitSize.width + consoleSize.width / 2 - 28,
y: endpoints[0].y))
} else {
endpoints.append(CGPoint(x: UIScreen.portraitSize.width + consoleSize.width / 2 - 28,
y: endpoints[1].y))
if !isLandscapeRightNotchedPhone {
if consoleView.center.y < (screenSize.height - (temporaryKeyboardHeightValueTracker ?? 0)) / 2 {
endpoints.append(CGPoint(x: screenSize.width + consoleSize.width / 2 - 28,
y: endpoints[0].y))
} else {
endpoints.append(CGPoint(x: screenSize.width + consoleSize.width / 2 - 28,
y: endpoints[1].y))
}
}
}
@@ -211,29 +233,29 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
} else {
// Two endpoints, one for the top, one for the bottom..
var endpoints = [CGPoint(x: UIScreen.portraitSize.width / 2,
var endpoints = [CGPoint(x: screenSize.width / 2,
y: (UIScreen.hasRoundedCorners ? 38 : 16) + consoleSize.height / 2 + 12),
CGPoint(x: UIScreen.portraitSize.width / 2,
y: UIScreen.portraitSize.height - consoleSize.height / 2 - (keyboardHeight ?? consoleWindow?.safeAreaInsets.bottom ?? 0) - 12)]
CGPoint(x: screenSize.width / 2,
y: screenSize.height - consoleSize.height / 2 - (keyboardHeight ?? consoleWindow?.safeAreaInsets.bottom ?? 0) - 12)]
if consoleView.frame.minX <= 0 {
// Left edge hiding endpoints.
if consoleView.center.y < (UIScreen.portraitSize.height - (temporaryKeyboardHeightValueTracker ?? 0)) / 2 {
if consoleView.center.y < (screenSize.height - (temporaryKeyboardHeightValueTracker ?? 0)) / 2 {
endpoints.append(CGPoint(x: -consoleSize.width / 2 + 28,
y: endpoints[0].y))
} else {
endpoints.append(CGPoint(x: -consoleSize.width / 2 + 28,
y: endpoints[1].y))
}
} else if consoleView.frame.maxX >= UIScreen.portraitSize.width {
} else if consoleView.frame.maxX >= screenSize.width {
// Right edge hiding endpoints.
if consoleView.center.y < (UIScreen.portraitSize.height - (temporaryKeyboardHeightValueTracker ?? 0)) / 2 {
endpoints.append(CGPoint(x: UIScreen.portraitSize.width + consoleSize.width / 2 - 28,
if consoleView.center.y < (screenSize.height - (temporaryKeyboardHeightValueTracker ?? 0)) / 2 {
endpoints.append(CGPoint(x: screenSize.width + consoleSize.width / 2 - 28,
y: endpoints[0].y))
} else {
endpoints.append(CGPoint(x: UIScreen.portraitSize.width + consoleSize.width / 2 - 28,
endpoints.append(CGPoint(x: screenSize.width + consoleSize.width / 2 - 28,
y: endpoints[1].y))
}
}
@@ -245,8 +267,8 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
lazy var initialViewLocation: CGPoint = .zero
func configureConsole() {
consoleSize = CGSize(width: UserDefaults.standard.object(forKey: "LocalConsole_Width") as? CGFloat ?? consoleSize.width,
height: UserDefaults.standard.object(forKey: "LocalConsole_Height") as? CGFloat ?? consoleSize.height)
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.layer.shadowRadius = 16
@@ -290,7 +312,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)
@@ -342,7 +364,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
func updateConsoleOrigin() {
snapToCachedEndpoint()
if consoleView.center.x < 0 || consoleView.center.x > UIScreen.portraitSize.width {
if consoleView.center.x < 0 || consoleView.center.x > viewController.view.frame.size.width {
grabberMode = true
scrollLocked = !grabberMode
@@ -361,16 +383,21 @@ 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
consoleWindow?.isHidden = false
consoleWindow?.addSubview(consoleView)
UIWindow.swizzleStatusBarAppearanceOverride
viewController.view = PassthroughView()
consoleWindow?.rootViewController = viewController
viewController.view.addSubview(consoleView)
updateConsoleOrigin()
}
@@ -397,8 +424,8 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
}
func snapToCachedEndpoint() {
let cachedConsolePosition = CGPoint(x: UserDefaults.standard.object(forKey: "LocalConsole_X") as? CGFloat ?? possibleEndpoints.first!.x,
y: UserDefaults.standard.object(forKey: "LocalConsole_Y") as? CGFloat ?? possibleEndpoints.first!.y)
let cachedConsolePosition = CGPoint(x: UserDefaults.standard.object(forKey: "LocalConsole.X") as? CGFloat ?? possibleEndpoints.first!.x,
y: UserDefaults.standard.object(forKey: "LocalConsole.Y") as? CGFloat ?? possibleEndpoints.first!.y)
consoleView.center = cachedConsolePosition // Update console center so possibleEndpoints are calculated correctly.
consoleView.center = nearestTargetTo(cachedConsolePosition, possibleTargets: possibleEndpoints)
@@ -505,12 +532,34 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
}
}
var hasShortened = false
public var isCharacterLimitDisabled = false
public var isCharacterLimitWarningDisabled = false
/// Print items to the console view.
public func print(_ items: Any) {
if currentText == "" {
currentText = "\(items)"
let _currentText: String = {
if currentText == "" {
return "\(items)"
} else {
return currentText + "\n\(items)"
}
}()
// Cut down string if it exceeds 50,000 characters to keep text view running smoothly.
if _currentText.count > 50000 && !isCharacterLimitDisabled {
if !hasShortened && !isCharacterLimitWarningDisabled {
hasShortened = true
Swift.print("LocalConsole's content has exceeded 50,000 characters.\nTo maintain performance, LCManager cuts down the beginning of the printed content. To disable this behaviour, set LCManager.shared.isCharacterLimitDisabled to true.\nTo disable this warning, set LCManager.shared.isCharacterLimitWarningDisabled = true.")
}
let shortenedString = String(_currentText.suffix(50000))
currentText = shortenedString.stringAfterFirstOccurenceOf(delimiter: "\n") ?? shortenedString
} else {
currentText = currentText + "\n\(items)"
currentText = _currentText
}
}
@@ -537,8 +586,6 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
if consoleView.center != possibleEndpoints[0] && consoleView.center != possibleEndpoints[1] {
let nearestTargetPosition = nearestTargetTo(consoleView.center, possibleTargets: possibleEndpoints.suffix(2))
Swift.print(possibleEndpoints.suffix(2))
UIViewPropertyAnimator(duration: 0.55, dampingRatio: 1) {
self.consoleView.center = nearestTargetPosition
}.startAnimation()
@@ -590,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.
@@ -620,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
}
}
}
@@ -695,11 +759,16 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
consoleTextView.attributedText = NSAttributedString(string: string, attributes: attributes)
}
// Displays all UserDefaults keys, including unneeded keys that are included by default.
public var showAllUserDefaultsKeys = false
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",
@@ -710,6 +779,14 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
}
})
// If device is phone in landscape, disable resize controller.
if UIDevice.current.userInterfaceIdiom == .phone && viewController.view.frame.width > viewController.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
self.clear()
@@ -718,10 +795,127 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
let consoleActions = UIMenu(title: "", options: .displayInline, children: [clear, resize])
var frameSymbol = "rectangle.3.offgrid"
var debugActions: [UIMenuElement] = []
if #available(iOS 15, *) {
frameSymbol = "square.inset.filled"
let deferredUserDefaultsList = UIDeferredMenuElement.uncached { completion in
var actions: [UIAction] = []
let keys: [String] = {
if self.showAllUserDefaultsKeys {
return UserDefaults.standard.dictionaryRepresentation().map { $0.key }
}
// Show keys the developer has added to the app (+ LocalConsole keys), excluding all of Apple's keys.
if let bundle: String = Bundle.main.bundleIdentifier {
let preferencePath: String = NSHomeDirectory() + "/Library/Preferences/\(bundle).plist"
let _keys = NSDictionary(contentsOfFile: preferencePath)?.allKeys as! [String]
return _keys.filter {
!$0.contains("LocalConsole.")
}
}
return []
}()
if keys.isEmpty {
actions.append(UIAction(title: "No Entries",
image: nil, attributes: .disabled, handler: { _ in }
))
} else {
for key in keys.sorted(by: { $0.lowercased() < $1.lowercased() }) {
// Old LocalConsole Key Cleanup
guard !key.contains("LocalConsole_") else {
UserDefaults.standard.removeObject(forKey: key)
continue
}
if let value = UserDefaults.standard.value(forKey: key) {
let action = UIAction(title: key, image: nil) { _ in
let alertController = UIAlertController(title: key,
message: nil,
preferredStyle: .alert)
let headerParagraphStyle = NSMutableParagraphStyle()
headerParagraphStyle.paragraphSpacing = 6
let contentParagraphStyle = NSMutableParagraphStyle()
let attributes: [NSAttributedString.Key: Any] = [
.paragraphStyle: contentParagraphStyle,
.foregroundColor: UIColor.label,
.font: UIFont.systemFont(ofSize: 13, weight: .semibold, design: .monospaced)
]
let attributedTitle: NSMutableAttributedString = {
let attributedString = NSMutableAttributedString(string: "Key\n" + key, attributes: attributes)
attributedString.addAttributes(
[NSAttributedString.Key.foregroundColor : UIColor.label.withAlphaComponent(0.5),
NSAttributedString.Key.paragraphStyle : headerParagraphStyle],
range: NSRange(location: 0, length: 3))
return attributedString
}()
let attributedMessage: NSMutableAttributedString = {
let attributedString = NSMutableAttributedString(string: "\nValue\n" + "\(value)", attributes: attributes)
attributedString.addAttributes(
[NSAttributedString.Key.foregroundColor : UIColor.label.withAlphaComponent(0.5),
NSAttributedString.Key.paragraphStyle : headerParagraphStyle],
range: NSRange(location: 0, length: 7))
return attributedString
}()
alertController.setValue(attributedTitle, forKey: "attributedTitle")
alertController.setValue(attributedMessage, forKey: "attributedMessage")
alertController.addAction(UIAlertAction(title: "Copy Value", style: .default, handler: { _ in
UIPasteboard.general.string = "\(value)"
}))
alertController.addAction(UIAlertAction(title: "Clear Value", style: .destructive, handler: { _ in
UserDefaults.standard.removeObject(forKey: key)
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in
}))
self.viewController.present(alertController,
animated: true)
}
action.subtitle = "\(value)"
actions.append(action)
}
}
actions.append(
UIAction(title: "Clear Defaults",
image: UIImage(systemName: "trash"), attributes: .destructive, handler: { _ in
keys.forEach {
UserDefaults.standard.removeObject(forKey: $0)
}
})
)
}
completion(actions)
}
let userDefaults = UIMenu(title: "UserDefaults", image: UIImage(systemName: "doc.badge.gearshape"), children: [deferredUserDefaultsList])
debugActions.append(userDefaults)
}
let viewFrames = UIAction(title: debugBordersEnabled ? "Hide View Frames" : "Show View Frames",
image: UIImage(systemName: frameSymbol), handler: { _ in
self.debugBordersEnabled.toggle()
@@ -785,22 +979,26 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
}
})
let debugActions = UIMenu(title: "", options: .displayInline,
debugActions.append(contentsOf: [viewFrames, systemReport, displayReport, respring])
let debugMenu = UIMenu(title: "", options: .displayInline,
children: [UIMenu(title: "Debug", image: UIImage(systemName: "ant"),
children: [viewFrames, systemReport, displayReport, respring])])
children: debugActions)])
var menuContent: [UIMenuElement] = []
if consoleTextView.text != "" {
menuContent.append(contentsOf: [copy, consoleActions])
menuContent.append(contentsOf: [share, consoleActions])
} else {
menuContent.append(resize)
}
menuContent.append(debugActions)
menuContent.append(debugMenu)
return UIMenu(title: "", children: menuContent)
}
var consolePiPPopAnimator: UIViewPropertyAnimator?
@objc func longPressAction(recognizer: UILongPressGestureRecognizer) {
switch recognizer.state {
case .began:
@@ -811,8 +1009,12 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
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()
@@ -820,7 +1022,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()
@@ -834,17 +1036,23 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
}
}
let consolePiPPanner_frameRateRequest = FrameRateRequest()
var consolePiPPanner_frameRateRequestID: UUID?
@objc func consolePiPPanner(recognizer: UIPanGestureRecognizer) {
if recognizer.state == .began {
consolePiPPanner_frameRateRequest.isActive = true
if #available(iOS 15, *) {
consolePiPPanner_frameRateRequestID = UUID()
FrameRateRequest.shared.activate(id: consolePiPPanner_frameRateRequestID!)
}
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)
@@ -857,16 +1065,16 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
y: initialViewLocation.y + translation.y)
}.startAnimation()
if consoleView.frame.maxX > 30 && consoleView.frame.minX < UIScreen.portraitSize.width - 30 {
grabberMode = false
} else {
grabberMode = true
}
reassessGrabberMode()
case .ended, .cancelled:
consolePiPPanner_frameRateRequest.isActive = true
FrameRateRequest().perform(duration: 0.5)
if #available(iOS 15, *), let id = consolePiPPanner_frameRateRequestID {
consolePiPPanner_frameRateRequestID = nil
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
FrameRateRequest.shared.deactivate(id: id)
}
}
// After the PiP is thrown, determine the best corner and re-target it there.
let decelerationRate = UIScrollView.DecelerationRate.normal.rawValue
@@ -890,11 +1098,11 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
}
positionAnimator.startAnimation()
UserDefaults.standard.set(nearestTargetPosition.x, forKey: "LocalConsole_X")
UserDefaults.standard.set(nearestTargetPosition.y, forKey: "LocalConsole_Y")
UserDefaults.standard.set(nearestTargetPosition.x, forKey: "LocalConsole.X")
UserDefaults.standard.set(nearestTargetPosition.y, forKey: "LocalConsole.Y")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
self.grabberMode = nearestTargetPosition.x < 0 || nearestTargetPosition.x > UIScreen.portraitSize.width
self.reassessGrabberMode()
self.scrollLocked = !self.grabberMode
}
@@ -902,29 +1110,45 @@ 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()
func reassessGrabberMode() {
if consoleView.frame.maxX > 30 && consoleView.frame.minX < viewController.view.frame.size.width - 30 {
grabberMode = false
} else {
grabberMode = true
}
}
// 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 consolePiPTouchDownAnimator: UIViewPropertyAnimator?
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.
@@ -935,11 +1159,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
}
@@ -958,6 +1182,18 @@ class ConsoleWindow: UIWindow {
}
}
// Custom view for the console to appear above other windows while passing touches down.
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)
}
}
import UIKit.UIGestureRecognizerSubclass
@@ -994,21 +1230,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
@@ -1085,7 +1349,7 @@ class InvertedTextView: UITextView {
var pendingOffsetChange = false
// Thanks to WWDC21 Lab!
// Thanks to WWDC21 UIKit Lab!
override func layoutSubviews() {
super.layoutSubviews()
@@ -1111,6 +1375,20 @@ class InvertedTextView: UITextView {
}
}
extension UIDevice {
var hasNotch: Bool {
return UIApplication.shared.windows[0].safeAreaInsets.bottom > 0
}
}
extension String {
func stringAfterFirstOccurenceOf(delimiter: String) -> String? {
guard let upperIndex = (self.range(of: delimiter)?.upperBound) else { return nil }
let trailingString: String = .init(self.suffix(from: upperIndex))
return trailingString
}
}
extension TimeInterval {
var formattedString: String? {
let formatter = DateComponentsFormatter()
@@ -1123,6 +1401,66 @@ fileprivate func _debugPrint(_ items: Any) {
print(items)
}
// 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.
[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()
}
}
}
}
// MARK: Frame Rate Request
/**
An object that allows you to manually request an increased display refresh rate on ProMotion devices.
@@ -1133,40 +1471,77 @@ An object that allows you to manually request an increased display refresh rate
```
// Example
let request = FrameRateRequest(preferredFrameRate: 120,
duration: 0.4)
FrameRateRequest.shared.perform(duration: 0.5)
request.perform()
```
*/
class FrameRateRequest {
@available(iOS 15, *)
final class FrameRateRequest {
static let shared = FrameRateRequest()
lazy private var displayLink = CADisplayLink(target: self, selector: #selector(dummyFunction))
var isActive: Bool = false {
private var requestIdentifiers: [UUID] = [] {
didSet {
guard #available(iOS 15, *) else { return }
guard isActive != oldValue else { return }
isActive = requestIdentifiers.count > 0
}
}
private var isActive: Bool = false {
didSet {
guard isActive != oldValue, UIScreen.main.maximumFramesPerSecond > 60 else { return }
if isActive {
displayLink.add(to: .current, forMode: .common)
} else {
displayLink.remove(from: .current, forMode: .common)
displayLink.invalidate()
}
}
}
/// Prepares your frame rate request parameters.
init(preferredFrameRate: Float = Float(UIScreen.main.maximumFramesPerSecond)) {
if #available(iOS 15, *) {
displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: 30, maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: preferredFrameRate)
}
private init() {
guard UIScreen.main.maximumFramesPerSecond > 60 else { return }
displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: 90, maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond))
// Ensure the DisplayLink stops when the app enters the background, or else the system will shut high frame rate capabilities until the app is suspended and relaunched.
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground),
name: UIApplication.willEnterForegroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground),
name: UIApplication.didEnterBackgroundNotification, object: nil)
}
/// Perform frame rate request.
func perform(duration: Double) {
isActive = true
public func perform(duration: Double) {
guard UIScreen.main.maximumFramesPerSecond > 60 else { return }
let id = UUID()
requestIdentifiers.append(id)
DispatchQueue.main.asyncAfter(deadline: .now() + duration) { [self] in
isActive = false
requestIdentifiers = requestIdentifiers.filter { $0 != id }
}
}
public func activate(id: UUID) {
requestIdentifiers.append(id)
}
public func deactivate(id: UUID) {
requestIdentifiers = requestIdentifiers.filter { $0 != id }
}
@objc private func willEnterForeground() {
if isActive {
displayLink.add(to: .current, forMode: .common)
}
}
@objc private func didEnterBackground() {
if isActive {
displayLink.invalidate()
}
}
+93 -42
View File
@@ -14,9 +14,13 @@ class 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))
var consoleCenterPoint: CGPoint {
let containerViewSize = LCManager.shared.viewController.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
+ (UIScreen.hasRoundedCorners ? 0 : 24))
}
lazy var consoleOutlineView: UIView = {
@@ -115,7 +119,9 @@ class ResizeController {
// Ensure initial autolayout is performed unanimated.
LCManager.shared.consoleWindow?.layoutIfNeeded()
FrameRateRequest().perform(duration: 1.5)
if #available(iOS 15, *) {
FrameRateRequest.shared.perform(duration: 1.5)
}
if isActive {
@@ -203,7 +209,7 @@ class ResizeController {
static let kMinConsoleHeight: CGFloat = 108
static let kMaxConsoleHeight: CGFloat = 346
let verticalPanner_frameRateRequest = FrameRateRequest()
var verticalPanner_frameRateRequestID: UUID?
@objc func verticalPanner(recognizer: UIPanGestureRecognizer) {
@@ -214,7 +220,10 @@ class ResizeController {
switch recognizer.state {
case .began:
verticalPanner_frameRateRequest.isActive = true
if #available(iOS 15, *) {
verticalPanner_frameRateRequestID = UUID()
FrameRateRequest.shared.activate(id: verticalPanner_frameRateRequestID!)
}
initialHeight = LCManager.shared.consoleSize.height
@@ -247,9 +256,13 @@ class ResizeController {
LCManager.shared.consoleView.center.y = consoleCenterPoint.y
case .ended, .cancelled:
verticalPanner_frameRateRequest.isActive = false
FrameRateRequest().perform(duration: 0.4)
if #available(iOS 15, *), let id = verticalPanner_frameRateRequestID {
verticalPanner_frameRateRequestID = nil
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
FrameRateRequest.shared.deactivate(id: id)
}
}
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 0.7) {
if LCManager.shared.consoleSize.height > maxHeight {
@@ -278,9 +291,9 @@ class ResizeController {
var initialWidth = CGFloat.zero
static let kMinConsoleWidth: CGFloat = 112
static let kMaxConsoleWidth: CGFloat = UIScreen.portraitSize.width - 56
static let kMaxConsoleWidth: CGFloat = [UIScreen.portraitSize.width, UIScreen.portraitSize.height].min()! - 56
let horizontalPanner_frameRateRequest = FrameRateRequest()
var horizontalPanner_frameRateRequestID: UUID?
@objc func horizontalPanner(recognizer: UIPanGestureRecognizer) {
@@ -291,7 +304,10 @@ class ResizeController {
switch recognizer.state {
case .began:
horizontalPanner_frameRateRequest.isActive = true
if #available(iOS 15, *) {
horizontalPanner_frameRateRequestID = UUID()
FrameRateRequest.shared.activate(id: horizontalPanner_frameRateRequestID!)
}
initialWidth = LCManager.shared.consoleSize.width
@@ -324,9 +340,12 @@ class ResizeController {
case .ended, .cancelled:
horizontalPanner_frameRateRequest.isActive = false
FrameRateRequest().perform(duration: 0.4)
if #available(iOS 15, *), let id = horizontalPanner_frameRateRequestID {
horizontalPanner_frameRateRequestID = nil
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
FrameRateRequest.shared.deactivate(id: id)
}
}
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 0.7) {
if LCManager.shared.consoleSize.width > maxWidth {
@@ -357,12 +376,6 @@ 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)
@@ -379,11 +392,12 @@ class PlatterView: UIView {
blurView.clipsToBounds = true
blurView.frame = bounds
blurView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(blurView)
LCManager.shared.consoleWindow?.addSubview(self)
LCManager.shared.consoleWindow?.sendSubviewToBack(self)
LCManager.shared.viewController.view.addSubview(self)
LCManager.shared.viewController.view.sendSubviewToBack(self)
_ = backgroundButton
@@ -394,7 +408,7 @@ class PlatterView: UIView {
let grabber = UIView()
grabber.frame.size = CGSize(width: 36, height: 5)
grabber.frame.origin.y = 10
grabber.center.x = bounds.width / 2
grabber.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin]
grabber.backgroundColor = .label
grabber.alpha = 0.1
grabber.layer.cornerRadius = 2.5
@@ -405,9 +419,8 @@ class PlatterView: UIView {
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()
titleLabel.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin]
addSubview(titleLabel)
let subtitleLabel = UILabel()
@@ -415,20 +428,36 @@ class PlatterView: UIView {
subtitleLabel.font = .systemFont(ofSize: 17, weight: .medium)
subtitleLabel.sizeToFit()
subtitleLabel.alpha = 0.5
subtitleLabel.center.x = bounds.width / 2
subtitleLabel.frame.origin.y = titleLabel.frame.maxY + 8
subtitleLabel.roundOriginToPixel()
subtitleLabel.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin]
addSubview(subtitleLabel)
addSubview(resetButton)
resetButton.center = CGPoint(x: UIScreen.portraitSize.width / 2 - 74,
y: UIScreen.portraitSize.height - possibleEndpoints[0].y * 2)
resetButton.roundOriginToPixel()
let buttonContainerView = UIView()
buttonContainerView.addSubview(resetButton)
buttonContainerView.addSubview(doneButton)
addSubview(buttonContainerView)
addSubview(doneButton)
doneButton.center = CGPoint(x: UIScreen.portraitSize.width / 2 + 74,
y: UIScreen.portraitSize.height - possibleEndpoints[0].y * 2)
doneButton.roundOriginToPixel()
buttonContainerView.translatesAutoresizingMaskIntoConstraints = false
resetButton.translatesAutoresizingMaskIntoConstraints = false
doneButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
buttonContainerView.widthAnchor.constraint(equalToConstant: 264),
buttonContainerView.heightAnchor.constraint(equalToConstant: 52),
buttonContainerView.centerXAnchor.constraint(equalTo: centerXAnchor),
buttonContainerView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -possibleEndpoints[0].y * 2),
resetButton.widthAnchor.constraint(equalToConstant: 116),
resetButton.heightAnchor.constraint(equalToConstant: 52),
resetButton.leadingAnchor.constraint(equalTo: buttonContainerView.leadingAnchor),
resetButton.topAnchor.constraint(equalTo: buttonContainerView.topAnchor),
doneButton.widthAnchor.constraint(equalToConstant: 116),
doneButton.heightAnchor.constraint(equalToConstant: 52),
doneButton.trailingAnchor.constraint(equalTo: buttonContainerView.trailingAnchor),
doneButton.topAnchor.constraint(equalTo: buttonContainerView.topAnchor)
])
}
lazy var backgroundButton: UIButton = {
@@ -448,7 +477,6 @@ class PlatterView: UIView {
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
@@ -483,7 +511,6 @@ class PlatterView: UIView {
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
@@ -516,18 +543,35 @@ class PlatterView: UIView {
return button
}()
func configureFrame() {
self.frame.size = LCManager.shared.viewController.view.frame.size
// Make sure bottom doesn't show on upwards pan.
self.frame.size.height += 50
self.frame.origin = possibleEndpoints[1]
autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
func reveal() {
configureFrame()
UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) {
self.frame.origin = self.possibleEndpoints[0]
}.startAnimation()
backgroundButton.isHidden = false
isHidden = false
}
func dismiss() {
UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) {
let animator = UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) {
self.frame.origin = self.possibleEndpoints[1]
}.startAnimation()
}
animator.addCompletion { _ in
self.isHidden = true
}
animator.startAnimation()
backgroundButton.isHidden = true
}
@@ -548,7 +592,9 @@ class PlatterView: UIView {
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 possibleEndpoints: [CGPoint] { return [CGPoint(x: 0, y: (UIScreen.hasRoundedCorners ? 44 : -8) + 63),
CGPoint(x: 0, y: LCManager.shared.viewController.view.frame.size.height + 5)]
}
var initialPlatterOriginY = CGFloat.zero
@@ -630,15 +676,20 @@ class PlatterView: UIView {
$0.transform = .identity
}
}
positionAnimator.startAnimation()
if nearestTargetPosition == possibleEndpoints[1] {
ResizeController.shared.isActive = false
backgroundButton.isHidden = true
positionAnimator.addCompletion { _ in
self.isHidden = true
}
} else {
ResizeController.shared.isActive = true
}
positionAnimator.startAnimation()
default: break
}
}