Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 46 additions & 22 deletions Sources/FluidCore/SafeAreaFinder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,32 @@ import UIKit
public final class SafeAreaFinder: NSObject {

public static let notificationName = Notification.Name(rawValue: "app.muukii.fluid.SafeAreaInsetsManager")
@MainActor
public static let shared = SafeAreaFinder()

@available(iOS 13.0, *)
public weak var windowScene: UIWindowScene?

private var currentInsets: UIEdgeInsets? = nil

private var referenceCounter: Int = 0 {
didSet {
if referenceCounter > 0 {
currentDisplayLink.isPaused = false
} else {
currentDisplayLink.isPaused = true
}
}
}
private var isRunning: Bool = false

private nonisolated(unsafe) var currentDisplayLink: CADisplayLink!
private nonisolated(unsafe) var currentDisplayLink: CADisplayLink?

private override init() {
@available(iOS 13.0, *)
public init(windowScene: UIWindowScene?) {
self.windowScene = windowScene

super.init()
}

private func setUpDisplayLink() {
guard currentDisplayLink == nil else {
return
}

currentDisplayLink = .init(target: self, selector: #selector(handle))
currentDisplayLink.preferredFramesPerSecond = 1
currentDisplayLink.add(to: .main, forMode: .default)
currentDisplayLink.isPaused = true
currentDisplayLink?.preferredFramesPerSecond = 1
currentDisplayLink?.add(to: .main, forMode: .default)
currentDisplayLink?.isPaused = false
}

public func request() {
Expand All @@ -46,23 +46,47 @@ public final class SafeAreaFinder: NSObject {
}

public func start() {
referenceCounter += 1
guard isRunning == false else {
request()
return
}

isRunning = true
setUpDisplayLink()
request()
}

/// Stops polling and releases the display link so the finder can deallocate when its owner goes away.
public func stop() {
guard isRunning || currentDisplayLink != nil else {
return
}

isRunning = false
currentInsets = nil
currentDisplayLink?.invalidate()
currentDisplayLink = nil
}

/// Stops polling. Kept as a compatibility alias for older callers that used the reference-counted API.
public func pause() {
referenceCounter -= 1
stop()
}

deinit {
currentDisplayLink?.isPaused = true
currentDisplayLink?.invalidate()
}

@objc private dynamic func handle() {
guard let window = UIApplication.shared.delegate?.window ?? nil else {

guard let windowScene else {
return
}

guard let window = windowScene.windows.first(where: \.isKeyWindow) ?? windowScene.windows.first else {
return
}

_handle(in: window)
}

Expand Down Expand Up @@ -134,7 +158,7 @@ public final class SafeAreaFinder: NSObject {

if currentInsets != maximumInsets {
currentInsets = maximumInsets
NotificationCenter.default.post(name: Self.notificationName, object: maximumInsets)
NotificationCenter.default.post(name: Self.notificationName, object: maximumInsets, userInfo: ["finder": self])
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ extension FluidPictureInPictureController {
let containerView: ContainerView = .init()

let sizeForFloating = CGSize(width: 100, height: 140)
let safeAreaFinder: SafeAreaFinder

private(set) var state: State = .init() {
didSet {
Expand All @@ -123,6 +124,9 @@ extension FluidPictureInPictureController {
override init(
frame: CGRect
) {

self.safeAreaFinder = .init(windowScene: nil)

super.init(frame: frame)

let dragGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture))
Expand All @@ -145,6 +149,7 @@ extension FluidPictureInPictureController {
}

@objc private func handleInsetsUpdate(notification: Notification) {
guard notification.userInfo?["finder"] as? SafeAreaFinder === safeAreaFinder else { return }
let inset = notification.object as! UIEdgeInsets
state.inset = inset
setNeedsLayout()
Expand Down Expand Up @@ -229,11 +234,13 @@ extension FluidPictureInPictureController {

override func didMoveToWindow() {
super.didMoveToWindow()


safeAreaFinder.windowScene = window?.windowScene

if window != nil {
SafeAreaFinder.shared.start()
safeAreaFinder.start()
} else {
SafeAreaFinder.shared.pause()
safeAreaFinder.pause()
}
}

Expand Down
11 changes: 9 additions & 2 deletions Sources/FluidSnackbar/FloatingDisplayController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,15 @@ open class FloatingDisplayController {

// MARK: - Initializers

public init(edgeTargetSafeArea: FloatingDisplayTarget.EdgeTargetSafeArea) {
self.displayTarget = .init(edgeTargetSafeArea: edgeTargetSafeArea)
@available(iOS 13.0, *)
public init(
edgeTargetSafeArea: FloatingDisplayTarget.EdgeTargetSafeArea,
windowScene: UIWindowScene
) {
self.displayTarget = .init(
edgeTargetSafeArea: edgeTargetSafeArea,
windowScene: windowScene
)
}

// MARK: - Functions
Expand Down
98 changes: 59 additions & 39 deletions Sources/FluidSnackbar/FloatingDisplayTarget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,22 @@ public final class FloatingDisplayTarget {
left: .activeWindow
)
}

fileprivate var containsActiveWindowEdge: Bool {
top == .activeWindow
|| right == .activeWindow
|| bottom == .activeWindow
|| left == .activeWindow
}
}

public enum TargetSafeArea {
public enum TargetSafeArea: Equatable {
case notificationWindow
case activeWindow
}

private let notificationWindow: NotificationWindow
private let safeAreaFinder: SafeAreaFinder
private let notificationViewController: NotificationViewController

public var additionalSafeAreaInsets: UIEdgeInsets {
Expand All @@ -69,35 +77,31 @@ public final class FloatingDisplayTarget {
}

public func makeWindowVisible() {
_ = notificationViewController.view
notificationWindow.isHidden = false

if notificationViewController.needsActiveWindowSafeArea {
safeAreaFinder.start()
}
}

public func hideWindow() {
notificationWindow.isHidden = true
safeAreaFinder.stop()
}

public init(edgeTargetSafeArea: EdgeTargetSafeArea) {

self.notificationViewController = .init(edgeTargetSafeArea: edgeTargetSafeArea)

if #available(iOS 13, *) {

let windowScene = UIApplication.shared
.connectedScenes
.lazy
.filter { $0.activationState == .foregroundActive }
.compactMap { $0 as? UIWindowScene }
.first
@available(iOS 13.0, *)
public init(
edgeTargetSafeArea: EdgeTargetSafeArea,
windowScene: UIWindowScene
) {

if let windowScene = windowScene {
notificationWindow = .init(windowScene: windowScene)
} else {
notificationWindow = .init(frame: .zero)
}

} else {
notificationWindow = .init(frame: .zero)
}
self.safeAreaFinder = .init(windowScene: windowScene)
self.notificationViewController = .init(
edgeTargetSafeArea: edgeTargetSafeArea,
safeAreaFinder: safeAreaFinder
)
self.notificationWindow = .init(windowScene: windowScene)

notificationWindow.windowLevel = UIWindow.Level(rawValue: 5)
notificationWindow.isHidden = true
Expand All @@ -108,11 +112,11 @@ public final class FloatingDisplayTarget {
notificationViewController.endAppearanceTransition()
}

deinit {
Task { @MainActor [notificationWindow] in
deinit {
Task { @MainActor [safeAreaFinder, notificationWindow] in
safeAreaFinder.stop()
notificationWindow.isHidden = false
}

}

}
Expand Down Expand Up @@ -149,9 +153,18 @@ extension FloatingDisplayTarget {
fileprivate final class NotificationViewController: UIViewController {

private let edgeTargetSafeArea: EdgeTargetSafeArea
private let safeAreaFinder: SafeAreaFinder

init(edgeTargetSafeArea: EdgeTargetSafeArea) {
fileprivate var needsActiveWindowSafeArea: Bool {
edgeTargetSafeArea.containsActiveWindowEdge
}

init(
edgeTargetSafeArea: EdgeTargetSafeArea,
safeAreaFinder: SafeAreaFinder
) {
self.edgeTargetSafeArea = edgeTargetSafeArea
self.safeAreaFinder = safeAreaFinder
super.init(nibName: nil, bundle: nil)
}

Expand All @@ -160,7 +173,10 @@ extension FloatingDisplayTarget {
}

override fileprivate func loadView() {
view = View(edgeTargetSafeArea: edgeTargetSafeArea)
view = View(
edgeTargetSafeArea: edgeTargetSafeArea,
safeAreaFinder: safeAreaFinder
)
}

override fileprivate func viewDidLoad() {
Expand All @@ -172,6 +188,7 @@ extension FloatingDisplayTarget {
fileprivate class View: UIView {

private let edgeTargetSafeArea: EdgeTargetSafeArea
private let safeAreaFinder: SafeAreaFinder

private var _safeAreaLayoutGuide: UILayoutGuide = .init()
private var activeWindowSafeAreaLayoutGuideConstraintLeft: NSLayoutConstraint?
Expand All @@ -181,15 +198,19 @@ extension FloatingDisplayTarget {

private var hasSafeAreaFinderActivated: Bool = false

init(edgeTargetSafeArea: EdgeTargetSafeArea) {
init(
edgeTargetSafeArea: EdgeTargetSafeArea,
safeAreaFinder: SafeAreaFinder
) {

self.edgeTargetSafeArea = edgeTargetSafeArea
self.safeAreaFinder = safeAreaFinder

super.init(frame: .zero)

addLayoutGuide(_safeAreaLayoutGuide)

var containsActievWindowSafeAreaEdge: Bool = false
var containsActiveWindowSafeAreaEdge: Bool = false

switch edgeTargetSafeArea.top {
case .notificationWindow:
Expand All @@ -198,7 +219,7 @@ extension FloatingDisplayTarget {
case .activeWindow:
activeWindowSafeAreaLayoutGuideConstraintTop = topAnchor.constraint(
equalTo: _safeAreaLayoutGuide.topAnchor)
containsActievWindowSafeAreaEdge = true
containsActiveWindowSafeAreaEdge = true
}

switch edgeTargetSafeArea.right {
Expand All @@ -209,7 +230,7 @@ extension FloatingDisplayTarget {
case .activeWindow:
activeWindowSafeAreaLayoutGuideConstraintRight = rightAnchor.constraint(
equalTo: _safeAreaLayoutGuide.rightAnchor)
containsActievWindowSafeAreaEdge = true
containsActiveWindowSafeAreaEdge = true
}

switch edgeTargetSafeArea.bottom {
Expand All @@ -220,7 +241,7 @@ extension FloatingDisplayTarget {
case .activeWindow:
activeWindowSafeAreaLayoutGuideConstraintBottom = bottomAnchor.constraint(
equalTo: _safeAreaLayoutGuide.bottomAnchor)
containsActievWindowSafeAreaEdge = true
containsActiveWindowSafeAreaEdge = true
}

switch edgeTargetSafeArea.left {
Expand All @@ -230,10 +251,10 @@ extension FloatingDisplayTarget {
case .activeWindow:
activeWindowSafeAreaLayoutGuideConstraintLeft = leftAnchor.constraint(
equalTo: _safeAreaLayoutGuide.leftAnchor)
containsActievWindowSafeAreaEdge = true
containsActiveWindowSafeAreaEdge = true
}

if containsActievWindowSafeAreaEdge {
if containsActiveWindowSafeAreaEdge {

NSLayoutConstraint.activate(
[
Expand All @@ -248,13 +269,13 @@ extension FloatingDisplayTarget {
NotificationCenter.default.addObserver(
self, selector: #selector(handleInsetsUpdate), name: SafeAreaFinder.notificationName,
object: nil)
SafeAreaFinder.shared.start()
}
}

@objc private func handleInsetsUpdate(notification: Notification) {

guard hasSafeAreaFinderActivated else { return }
guard notification.userInfo?["finder"] as? SafeAreaFinder === safeAreaFinder else { return }

let insets = notification.object as! UIEdgeInsets
self.activeWindowSafeAreaLayoutGuideConstraintLeft?.constant = insets.left
Expand Down Expand Up @@ -287,10 +308,9 @@ extension FloatingDisplayTarget {
}

deinit {
Task { @MainActor [hasSafeAreaFinderActivated] in
if hasSafeAreaFinderActivated {
SafeAreaFinder.shared.pause()
}
NotificationCenter.default.removeObserver(self)
Task { @MainActor [safeAreaFinder] in
safeAreaFinder.stop()
}
}
}
Expand Down
Loading