适配iPad多窗口

This commit is contained in:
xaoxuu 2023-08-06 14:36:41 +08:00
parent 27138a49e9
commit b53a09ad75
17 changed files with 218 additions and 128 deletions

View File

@ -51,10 +51,8 @@ extension CALayer {
var cornerRadiusWithContinuous: CGFloat {
set {
cornerRadius = newValue
if #available(iOS 13.0, *) {
if cornerCurve != .continuous {
cornerCurve = .continuous
} else {
// Fallback on earlier versions
}
}
get { cornerRadius }

View File

@ -5,7 +5,7 @@
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>

View File

@ -6,6 +6,7 @@
//
import UIKit
import ProHUD
class ListVC: UITableViewController {
@ -46,6 +47,7 @@ class ListVC: UITableViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
AppContext.workspace = self
list.sections[indexPath.section].rows[indexPath.row].action()
}

View File

@ -4,7 +4,7 @@ import PackageDescription
let package = Package(
name: "ProHUD",
platforms: [.iOS(.v10)],
platforms: [.iOS(.v13)],
products: [
.library(name: "ProHUD", targets: ["ProHUD"]),
],

View File

@ -68,7 +68,8 @@ extension Alert: DefaultLayout {
if contentView.superview != view {
view.insertSubview(contentView, at: 0)
}
if config.enableShadow && AlertWindow.alerts.count > 0 {
let alerts = window?.alerts ?? []
if config.enableShadow && alerts.count > 0 {
contentView.clipsToBounds = false
contentView.layer.shadowRadius = 4
contentView.layer.shadowOpacity = 0.08
@ -242,7 +243,8 @@ extension Alert {
public override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if config.enableShadow && AlertWindow.alerts.count > 1 {
let alerts = window?.alerts ?? []
if config.enableShadow && alerts.count > 1 {
contentView.layer.shadowPath = UIBezierPath.init(rect: contentView.bounds).cgPath
}
}

View File

@ -8,12 +8,14 @@
import UIKit
extension Alert: HUD {
public func push(scene: UIWindowScene?) {
push()
}
public func push() {
guard AlertWindow.alerts.contains(self) == false else {
let window = createAttachedWindowIfNotExists()
guard window.alerts.contains(self) == false else {
return
}
let window = attachedWindow
view.transform = .init(scaleX: 1.2, y: 1.2)
view.alpha = 0
navEvents[.onViewWillAppear]?(self)
@ -32,13 +34,13 @@ extension Alert: HUD {
} completion: { done in
self.navEvents[.onViewDidAppear]?(self)
}
AlertWindow.alerts.append(self)
Alert.updateAlertsLayout()
window.alerts.append(self)
Alert.updateAlertsLayout(alerts: window.alerts)
}
public func pop() {
navEvents[.onViewWillDisappear]?(self)
let window = attachedWindow
let window = window ?? createAttachedWindowIfNotExists()
Alert.removeAlert(alert: self)
let duration = config.animateDurationForBuildOut ?? config.animateDurationForBuildOutByDefault
UIView.animateEaseOut(duration: duration) {
@ -50,13 +52,14 @@ extension Alert: HUD {
self.navEvents[.onViewDidDisappear]?(self)
}
// hide window
let count = AlertWindow.alerts.count
if count == 0 && AlertWindow.current != nil {
let count = window.alerts.count
if count == 0 {
UIView.animateEaseOut(duration: duration) {
window.backgroundView.alpha = 0
} completion: { done in
if AlertWindow.alerts.count == 0 {
AlertWindow.current = nil
// self.windowalert
if window.alerts.count == 0, let scene = window.windowScene {
AppContext.alertWindow[scene] = nil
}
}
}
@ -72,7 +75,7 @@ public extension Alert {
/// - handler:
static func lazyPush(identifier: String? = nil, file: String = #file, line: Int = #line, handler: @escaping (_ alert: Alert) -> Void, onExists: ((_ alert: Alert) -> Void)? = nil) {
let id = identifier ?? (file + "#\(line)")
if let vc = AlertWindow.alerts.last(where: { $0.identifier == id }) {
if let vc = find(identifier: id).last {
vc.update(handler: onExists ?? handler)
} else {
Alert { alert in
@ -96,7 +99,7 @@ public extension Alert {
/// - Parameter identifier:
/// - Returns: HUD
@discardableResult static func find(identifier: String, update handler: ((_ alert: Alert) -> Void)? = nil) -> [Alert] {
let arr = AlertWindow.alerts.filter({ $0.identifier == identifier })
let arr = AppContext.alertWindow.values.flatMap({ $0.alerts }).filter({ $0.identifier == identifier })
if let handler = handler {
arr.forEach({ $0.update(handler: handler) })
}
@ -106,8 +109,8 @@ public extension Alert {
}
fileprivate extension Alert {
static func updateAlertsLayout() {
for (i, a) in AlertWindow.alerts.reversed().enumerated() {
static func updateAlertsLayout(alerts: [Alert]) {
for (i, a) in alerts.reversed().enumerated() {
let scale = CGFloat(pow(0.9, CGFloat(i)))
UIView.animate(withDuration: 1.8, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.5, options: [.allowUserInteraction, .curveEaseInOut], animations: {
let y = 0 - a.config.stackDepth * CGFloat(i) * CGFloat(pow(0.85, CGFloat(i)))
@ -118,25 +121,29 @@ fileprivate extension Alert {
}
}
var attachedWindow: AlertWindow {
AlertWindow.attachedWindow(config: config)
func createAttachedWindowIfNotExists() -> AlertWindow {
AlertWindow.createAttachedWindowIfNotExists(config: config)
}
static func removeAlert(alert: Alert) {
if AlertWindow.alerts.count > 1 {
for (i, a) in AlertWindow.alerts.enumerated() {
guard var alerts = alert.window?.alerts else {
return
}
if alerts.count > 1 {
for (i, a) in alerts.enumerated() {
if a == alert {
if i < AlertWindow.alerts.count {
AlertWindow.alerts.remove(at: i)
if i < alerts.count {
alerts.remove(at: i)
}
}
}
updateAlertsLayout()
} else if AlertWindow.alerts.count == 1 {
AlertWindow.alerts.removeAll()
updateAlertsLayout(alerts: alerts)
} else if alerts.count == 1 {
alerts.removeAll()
} else {
print("代码漏洞已经没有alert了")
}
alert.window?.alerts = alerts
}
}

View File

@ -7,29 +7,43 @@
import UIKit
extension Alert {
var window: AlertWindow? {
get {
guard let windowScene = windowScene else {
return nil
}
return AppContext.alertWindow[windowScene]
}
set {
guard let windowScene = windowScene else {
return
}
AppContext.alertWindow[windowScene] = newValue
}
}
}
class AlertWindow: Window {
static var current: AlertWindow?
static var alerts = [Alert]()
var alerts: [Alert] = []
override var usingBackground: Bool { true }
static func attachedWindow(config: Configuration) -> AlertWindow {
if let w = AlertWindow.current {
static func createAttachedWindowIfNotExists(config: Configuration) -> AlertWindow {
let windowScene = AppContext.windowScene
if let windowScene = windowScene, let w = AppContext.alertWindow[windowScene] {
return w
}
let w: AlertWindow
if #available(iOS 13.0, *) {
if let scene = AppContext.windowScene {
w = .init(windowScene: scene)
} else {
w = .init(frame: AppContext.appBounds)
}
if let scene = windowScene {
w = .init(windowScene: scene)
} else {
w = .init(frame: AppContext.appBounds)
}
AlertWindow.current = w
if let windowScene = windowScene {
AppContext.alertWindow[windowScene] = w
}
// alert
w.windowLevel = .init(rawValue: UIWindow.Level.alert.rawValue - 1)
return w

View File

@ -13,36 +13,26 @@ public class Configuration: NSObject {
public static var enablePrint = true
public lazy var dynamicBackgroundColor: UIColor = {
if #available(iOS 13.0, *) {
let color = UIColor { (traitCollection: UITraitCollection) -> UIColor in
if traitCollection.userInterfaceStyle == .dark {
return .init(white: 0.15, alpha: 1)
} else {
return .init(white: 1, alpha: 1)
}
}
return color
let color = UIColor { (traitCollection: UITraitCollection) -> UIColor in
if traitCollection.userInterfaceStyle == .dark {
return .init(white: 0.15, alpha: 1)
} else {
// Fallback on earlier versions
return .init(white: 1, alpha: 1)
}
}
return .init(white: 1, alpha: 1)
return color
}()
/// iOS13
public lazy var dynamicTextColor: UIColor = {
if #available(iOS 13.0, *) {
let color = UIColor { (traitCollection: UITraitCollection) -> UIColor in
if traitCollection.userInterfaceStyle == .dark {
return .init(white: 1, alpha: 1)
} else {
return .init(white: 0.1, alpha: 1)
}
let color = UIColor { (traitCollection: UITraitCollection) -> UIColor in
if traitCollection.userInterfaceStyle == .dark {
return .init(white: 1, alpha: 1)
} else {
return .init(white: 0.1, alpha: 1)
}
return color
} else {
// Fallback on earlier versions
}
return .init(white: 0.1, alpha: 1)
return color
}()
///

View File

@ -5,9 +5,17 @@
// Created by xaoxuu on 2022/8/29.
//
import Foundation
import UIKit
public protocol HUD {
func push()
func push(workspace: Workspace?)
func pop()
}
public extension HUD {
func push(workspace: Workspace?) {
AppContext.workspace = workspace
push()
}
}

View File

@ -7,41 +7,85 @@
import UIKit
public protocol Workspace {}
extension UIWindowScene: Workspace {}
extension UIView: Workspace {}
extension UIViewController: Workspace {}
extension Workspace {
var windowScene: UIWindowScene? {
if let self = self as? UIWindowScene {
return self
} else if let self = self as? UIWindow {
return self.windowScene
} else if let self = self as? UIView {
return self.window?.windowScene
} else if let self = self as? UIViewController {
return self.view.window?.windowScene
}
return nil
}
}
public struct AppContext {
@available(iOS 13.0, *)
private static var storedAppWindowScene: UIWindowScene?
private static var storedAppWindow: UIWindow?
/// scenetoast
static var toastWindows: [UIWindowScene: [ToastWindow]] = [:]
static var alertWindow: [UIWindowScene: AlertWindow] = [:]
static var sheetWindows: [UIWindowScene: [SheetWindow]] = [:]
private init() {}
static var current: AppContext? {
guard let windowScene = windowScene else { return nil }
if let ctx = allContexts[windowScene] {
return ctx
} else {
let ctx: AppContext = .init(windowScene: windowScene)
allContexts[windowScene] = ctx
return ctx
}
}
static var allContexts = [UIWindowScene: AppContext]()
private let windowScene: UIWindowScene
private init(windowScene: UIWindowScene) {
self.windowScene = windowScene
}
/// windowScene
/// workspacewindowScene/window/view/viewController
public static var workspace: Workspace? {
get { windowScene }
set {
windowScene = newValue?.windowScene
}
}
}
public extension AppContext {
extension AppContext {
@available(iOS 13.0, *)
static var foregroundActiveWindowScenes: [UIWindowScene] {
return UIApplication.shared.connectedScenes.compactMap({ $0 as? UIWindowScene }).filter({ $0.activationState == .foregroundActive })
}
/// workspaceworkspacewindowScenewindowScene
static var windowScene: UIWindowScene? {
set { storedAppWindowScene = newValue }
get {
if let ws = storedAppWindowScene {
return ws
} else {
return UIApplication.shared.connectedScenes.first(where: { scene in
guard let ws = scene as? UIWindowScene else { return false }
return ws.activationState == .foregroundActive
}) as? UIWindowScene
return foregroundActiveWindowScenes.last
}
}
}
///
static var windows: [UIWindow] {
if #available(iOS 13.0, *) {
return windowScene?.windows ?? UIApplication.shared.windows
} else {
return UIApplication.shared.windows
}
windowScene?.windows ?? UIApplication.shared.windows
}
///
@ -51,28 +95,31 @@ public extension AppContext {
/// App
static var appWindow: UIWindow? {
get {
if let w = storedAppWindow {
return w
} else {
return visibleWindows.filter { window in
return "\(type(of: window))" == "UIWindow" && window.windowLevel == .normal
}.first
}
}
set { storedAppWindow = newValue }
visibleWindows.filter { window in
return "\(type(of: window))" == "UIWindow" && window.windowLevel == .normal
}.first
}
/// App
static var appBounds: CGRect {
if #available(iOS 13.0, *) {
return appWindow?.bounds ?? UIScreen.main.bounds
} else {
return UIScreen.main.bounds
}
appWindow?.bounds ?? UIScreen.main.bounds
}
/// App
static var safeAreaInsets: UIEdgeInsets { appWindow?.safeAreaInsets ?? .zero }
}
// MARK: - instance manage
extension AppContext {
var sheetWindows: [SheetWindow] {
Self.sheetWindows[windowScene] ?? []
}
}
extension AppContext {
var toastWindows: [ToastWindow] {
Self.toastWindows[windowScene] ?? []
}
}

View File

@ -13,10 +13,8 @@ extension CALayer {
var cornerRadiusWithContinuous: CGFloat {
set {
cornerRadius = newValue
if #available(iOS 13.0, *) {
if cornerCurve != .continuous {
cornerCurve = .continuous
} else {
// Fallback on earlier versions
}
}
get { cornerRadius }

View File

@ -9,11 +9,7 @@ import UIKit
extension UIImage {
public convenience init?(inProHUD named: String) {
if #available(iOS 13.0, *) {
self.init(named: named, in: .module, with: .none)
} else {
self.init(named: named)
}
self.init(named: named, in: .module, with: .none)
}
}
@ -24,7 +20,7 @@ internal var isPortrait: Bool {
return true
}
if UIDevice.current.userInterfaceIdiom == .phone {
if UIApplication.shared.statusBarOrientation.isPortrait {
if AppContext.windowScene?.interfaceOrientation.isPortrait == true {
return true
}
}

View File

@ -41,7 +41,6 @@ class Window: UIWindow {
rootViewController = vc
}
@available(iOS 13.0, *)
override init(windowScene: UIWindowScene) {
super.init(windowScene: windowScene)
setup()

View File

@ -51,7 +51,7 @@ public extension Sheet {
/// - Parameter identifier:
/// - Returns: HUD
@discardableResult static func find(identifier: String, update handler: ((_ sheet: Sheet) -> Void)? = nil) -> [Sheet] {
let arr = SheetWindow.windows.compactMap({ $0.sheet }).filter({ $0.identifier == identifier })
let arr = AppContext.sheetWindows.values.flatMap({ $0 }).compactMap({ $0.sheet }).filter({ $0.identifier == identifier })
if let handler = handler {
arr.forEach({ $0.update(handler: handler) })
}

View File

@ -1,5 +1,5 @@
//
// File.swift
// SheetWindow.swift
//
//
// Created by xaoxuu on 2022/9/8.
@ -7,20 +7,30 @@
import UIKit
class SheetWindow: Window {
static var windows = [SheetWindow]()
private extension Sheet {
func getContextWindows() -> [SheetWindow] {
guard let windowScene = windowScene else {
return []
}
return AppContext.sheetWindows[windowScene] ?? []
}
func setContextWindows(_ windows: [SheetWindow]) {
guard let windowScene = windowScene else {
return
}
AppContext.sheetWindows[windowScene] = windows
}
}
class SheetWindow: Window {
var sheet: Sheet
init(sheet: Sheet) {
self.sheet = sheet
if #available(iOS 13.0, *) {
if let scene = AppContext.windowScene {
super.init(windowScene: scene)
} else {
super.init(frame: AppContext.appBounds)
}
if let scene = AppContext.windowScene {
super.init(windowScene: scene)
} else {
super.init(frame: AppContext.appBounds)
}
@ -36,6 +46,7 @@ class SheetWindow: Window {
static func push(sheet: Sheet) {
let isNew: Bool
let window: SheetWindow
var windows = AppContext.current?.sheetWindows ?? []
if let w = windows.first(where: { $0.sheet == sheet }) {
isNew = false
window = w
@ -46,6 +57,7 @@ class SheetWindow: Window {
window.rootViewController = sheet
if windows.contains(window) == false {
windows.append(window)
sheet.setContextWindows(windows)
}
if isNew {
sheet.navEvents[.onViewWillAppear]?(sheet)
@ -58,6 +70,7 @@ class SheetWindow: Window {
}
static func pop(sheet: Sheet) {
var windows = sheet.getContextWindows()
guard let window = windows.first(where: { $0.sheet == sheet }) else {
return
}
@ -72,6 +85,7 @@ class SheetWindow: Window {
} else {
consolePrint("代码漏洞已经没有sheet了")
}
sheet.setContextWindows(windows)
}
}
}

View File

@ -51,7 +51,7 @@ public extension Toast {
/// - Parameter identifier:
/// - Returns: HUD
@discardableResult static func find(identifier: String, update handler: ((_ toast: Toast) -> Void)? = nil) -> [Toast] {
let arr = ToastWindow.windows.compactMap({ $0.toast }).filter({ $0.identifier == identifier })
let arr = AppContext.toastWindows.values.flatMap({ $0 }).compactMap({ $0.toast }).filter({ $0.identifier == identifier })
if let handler = handler {
arr.forEach({ $0.update(handler: handler) })
}

View File

@ -7,9 +7,22 @@
import UIKit
class ToastWindow: Window {
private extension Toast {
func getContextWindows() -> [ToastWindow] {
guard let windowScene = windowScene else {
return []
}
return AppContext.toastWindows[windowScene] ?? []
}
func setContextWindows(_ windows: [ToastWindow]) {
guard let windowScene = windowScene else {
return
}
AppContext.toastWindows[windowScene] = windows
}
}
static var windows = [ToastWindow]()
class ToastWindow: Window {
var toast: Toast
@ -18,9 +31,7 @@ class ToastWindow: Window {
init(toast: Toast) {
self.toast = toast
super.init(frame: .zero)
if #available(iOS 13.0, *) {
windowScene = AppContext.windowScene
}
windowScene = AppContext.windowScene
toast.window = self
windowLevel = .init(rawValue: UIWindow.Level.alert.rawValue + 1000)
layer.shadowRadius = 8
@ -41,6 +52,7 @@ class ToastWindow: Window {
static func push(toast: Toast) {
let isNew: Bool
let window: ToastWindow
var windows = AppContext.current?.toastWindows ?? []
if let w = windows.first(where: { $0.toast == toast }) {
isNew = false
window = w
@ -64,8 +76,9 @@ class ToastWindow: Window {
window.rootViewController = toast // toast.view.frame.sizewindow.frame.size
if windows.contains(window) == false {
windows.append(window)
toast.setContextWindows(windows)
}
updateToastWindowsLayout()
updateToastWindowsLayout(windows: windows)
if isNew {
window.transform = .init(translationX: 0, y: -window.frame.maxY)
UIView.animateEaseOut(duration: config.animateDurationForBuildInByDefault) {
@ -80,24 +93,26 @@ class ToastWindow: Window {
}
static func pop(toast: Toast) {
var windows = toast.getContextWindows()
guard let window = windows.first(where: { $0.toast == toast }) else {
return
}
if windows.count > 1 {
windows.removeAll { $0 == window }
updateToastWindowsLayout()
updateToastWindowsLayout(windows: windows)
} else if windows.count == 1 {
windows.removeAll()
} else {
consolePrint("代码漏洞已经没有toast了")
}
toast.vm.duration = nil
toast.setContextWindows(windows)
UIView.animateEaseOut(duration: toast.config.animateDurationForBuildOutByDefault) {
window.transform = .init(translationX: 0, y: 0-20-window.maxY)
} completion: { done in
window.toast.view.removeFromSuperview()
window.toast.removeFromParent()
window.toast.navEvents[.onViewDidDisappear]?(window.toast)
toast.view.removeFromSuperview()
toast.removeFromParent()
toast.navEvents[.onViewDidDisappear]?(toast)
}
}
@ -108,7 +123,7 @@ fileprivate var updateToastsLayoutWorkItem: DispatchWorkItem?
fileprivate extension ToastWindow {
static func setToastWindowsLayout() {
static func setToastWindowsLayout(windows: [ToastWindow]) {
for (i, window) in windows.enumerated() {
let config = window.toast.config
var y = window.frame.origin.y
@ -128,10 +143,10 @@ fileprivate extension ToastWindow {
}
}
static func updateToastWindowsLayout() {
static func updateToastWindowsLayout(windows: [ToastWindow]) {
updateToastsLayoutWorkItem?.cancel()
updateToastsLayoutWorkItem = DispatchWorkItem {
setToastWindowsLayout()
setToastWindowsLayout(windows: windows)
}
DispatchQueue.main.asyncAfter(deadline: .now()+0.001, execute: updateToastsLayoutWorkItem!)
}