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
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import SwiftUI

@MainActor
public final class GlobalConfigCenter: GlobalConfig { required public init() {}
// MARK: Active Variables
public var popupPadding: EdgeInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import SwiftUI

@MainActor
public final class GlobalConfigVertical: GlobalConfig { required public init() {}
// MARK: Content
public var popupPadding: EdgeInsets = .init()
Expand Down
1 change: 1 addition & 0 deletions Sources/Internal/Configurables/Global/GlobalConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import SwiftUI

@MainActor
public protocol GlobalConfig: LocalConfig {
var dragThreshold: CGFloat { get set }
var isStackingEnabled: Bool { get set }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import SwiftUI

@MainActor
public class LocalConfigCenter: LocalConfig { required public init() {}
// MARK: Active Variables
public var popupPadding: EdgeInsets = GlobalConfigContainer.center.popupPadding
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import SwiftUI

@MainActor
public class LocalConfigVertical: LocalConfig { required public init() {}
// MARK: Content
public var popupPadding: EdgeInsets = GlobalConfigContainer.vertical.popupPadding
Expand Down
1 change: 1 addition & 0 deletions Sources/Internal/Configurables/Local/LocalConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import SwiftUI

@MainActor
public protocol LocalConfig { init()
// MARK: Content
var popupPadding: EdgeInsets { get set }
Expand Down
8 changes: 4 additions & 4 deletions Sources/Internal/Containers/GlobalConfigContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
//
// Copyright ©2023 Mijick. All rights reserved.


public actor GlobalConfigContainer {
nonisolated(unsafe) static var center: GlobalConfigCenter = .init()
nonisolated(unsafe) static var vertical: GlobalConfigVertical = .init()
@MainActor
public class GlobalConfigContainer {
static var center: GlobalConfigCenter = .init()
static var vertical: GlobalConfigVertical = .init()
}
1 change: 0 additions & 1 deletion Sources/Internal/Containers/PopupStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ private extension PopupStack {
await AnyView.hideKeyboard()
}
}

nonisolated func getNewPopups(_ operation: StackOperation) async -> [AnyPopup] { switch operation {
case .insertPopup(let popup): await insertedPopup(popup)
case .removeLastPopup: await removedLastPopup()
Expand Down
2 changes: 1 addition & 1 deletion Sources/Internal/Models/AnyPopup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import SwiftUI

struct AnyPopup: Popup {
struct AnyPopup: Popup, Sendable {
private(set) var id: PopupID
private(set) var config: AnyPopupConfig
private(set) var height: CGFloat? = nil
Expand Down
1 change: 1 addition & 0 deletions Sources/Internal/Utilities/PopupActionScheduler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import Foundation

@MainActor
class PopupActionScheduler {
private var time: Double = 0
private var action: DispatchSourceTimer?
Expand Down
2 changes: 1 addition & 1 deletion Sources/Public/Popup/Public+Popup+Main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import SwiftUI
- ``onFocus()-6krqs``
- ``onDismiss()-254h8``
*/
public protocol Popup: View {
public protocol Popup: View, Sendable {
associatedtype Config: LocalConfig

/**
Expand Down
4 changes: 2 additions & 2 deletions Sources/Public/Setup/Public+Setup+ConfigContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ public extension GlobalConfigContainer {
Use the ``Popup/configurePopup(config:)-98ha0`` method to change the configuration for a specific popup.
See the list of available methods in ``GlobalConfig``.
*/
nonisolated func center(_ builder: (GlobalConfigCenter) -> GlobalConfigCenter) -> Self { Self.center = builder(.init()); return self }
func center(_ builder: (GlobalConfigCenter) -> GlobalConfigCenter) -> Self { Self.center = builder(.init()); return self }

/**
Default configuration for all top and bottom popups.
Use the ``Popup/configurePopup(config:)-98ha0`` method to change the configuration for a specific popup.
See the list of available methods in ``GlobalConfig`` and ``GlobalConfig/Vertical``.
*/
nonisolated func vertical(_ builder: (GlobalConfigVertical) -> GlobalConfigVertical) -> Self { Self.vertical = builder(.init()); return self }
func vertical(_ builder: (GlobalConfigVertical) -> GlobalConfigVertical) -> Self { Self.vertical = builder(.init()); return self }
}
31 changes: 23 additions & 8 deletions Sources/Public/Setup/Public+Setup+SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,21 +80,26 @@ extension PopupSceneDelegate {
// MARK: Implementation
fileprivate class Window: UIWindow {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
if #available(iOS 18, *) { point_iOS18(inside: point, with: event) }
if #available(iOS 26, *) { point_iOS26(inside: point, with: event) }
else if #available(iOS 18, *) { point_iOS18(inside: point, with: event) }
else { point_iOS17(inside: point, with: event) }
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if #available(iOS 18, *) { hitTest_iOS18(point, with: event) }
if #available(iOS 26, *) { hitTest_iOS26(point, with: event) }
else if #available(iOS 18, *) { hitTest_iOS18(point, with: event) }
else { hitTest_iOS17(point, with: event) }
}
}

// MARK: Point
private extension Window {
@available(iOS 26, *)
func point_iOS26(inside point: CGPoint, with event: UIEvent?) -> Bool {
super.point(inside: point, with: event)
}
@available(iOS 18, *)
func point_iOS18(inside point: CGPoint, with event: UIEvent?) -> Bool {
guard let view = rootViewController?.view else { return false }

let hit = hitTestHelper(point, with: event, view: subviews.count > 1 ? self : view)
return hit != nil
}
Expand All @@ -105,25 +110,35 @@ private extension Window {

// MARK: Hit Test
private extension Window {
@available(iOS 18, *)
func hitTest_iOS18(_ point: CGPoint, with event: UIEvent?) -> UIView? {
super.hitTest(point, with: event)
@available(iOS 26, *)
func hitTest_iOS26(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let rootView = self.rootViewController?.view else { return nil }

let pointInRootView = self.convert(point, to: rootView)
let hitView = rootView.hitTest(pointInRootView, with: event)
let isTapOutsideToDismissEnabled = PopupStackContainer.stacks.first?.popups.last?.config.isTapOutsideToDismissEnabled ?? false

if hitView == rootView || hitView == nil { return isTapOutsideToDismissEnabled ? rootView : nil }
return hitView
}
@available(iOS 18, *)
func hitTest_iOS18(_ point: CGPoint, with event: UIEvent?) -> UIView? {
super.hitTest(point, with: event)
}
func hitTest_iOS17(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let hit = super.hitTest(point, with: event) else { return nil }
return rootViewController?.view == hit ? nil : hit
}
}


// MARK: Hit Test Helper
// Based on philip_trauner solution: https://forums.developer.apple.com/forums/thread/762292?answerId=803885022#803885022
@available(iOS 18, *)
private extension Window {
func hitTestHelper(_ point: CGPoint, with event: UIEvent?, view: UIView, depth: Int = 0) -> HitTestResult? {
view.subviews.reversed().reduce(nil) { deepest, subview in let convertedPoint = view.convert(point, to: subview)
guard shouldCheckSubview(subview, convertedPoint: convertedPoint, event: event) else { return deepest }

let result = calculateHitTestSubviewResult(convertedPoint, with: event, subview: subview, depth: depth)
return getDeepestHitTestResult(candidate: result, current: deepest)
}
Expand Down
Loading