适配iPad多窗口;去除对Inspire的依赖

This commit is contained in:
xaoxuu 2023-08-05 19:44:49 +08:00
parent e64976bb08
commit 27138a49e9
15 changed files with 232 additions and 128 deletions

View File

@ -12,13 +12,12 @@
CD6537C328C35E6200A5981B /* ToastVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6537C228C35E6200A5981B /* ToastVC.swift */; };
CD6537C528C35F2C00A5981B /* SheetVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6537C428C35F2C00A5981B /* SheetVC.swift */; };
CD6AE8A32A7CC1BA0044E53D /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = CD6AE8A22A7CC1BA0044E53D /* SnapKit */; };
CD6AE8A62A7CC1C70044E53D /* Inspire in Frameworks */ = {isa = PBXBuildFile; productRef = CD6AE8A52A7CC1C70044E53D /* Inspire */; };
CD8EEF3B28BC5C7200E660EA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD8EEF3A28BC5C7200E660EA /* AppDelegate.swift */; };
CD8EEF3D28BC5C7200E660EA /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD8EEF3C28BC5C7200E660EA /* SceneDelegate.swift */; };
CD8EEF4228BC5C7200E660EA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CD8EEF4028BC5C7200E660EA /* Main.storyboard */; };
CD8EEF4428BC5C7300E660EA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CD8EEF4328BC5C7300E660EA /* Assets.xcassets */; };
CD8EEF4728BC5C7300E660EA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CD8EEF4528BC5C7300E660EA /* LaunchScreen.storyboard */; };
CD9C7B1E28CB8972006190CD /* Scenes.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD9C7B1D28CB8972006190CD /* Scenes.swift */; };
CD9C7B1E28CB8972006190CD /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD9C7B1D28CB8972006190CD /* Extensions.swift */; };
CDA83DB928C601E60025F0DF /* TableHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA83DB828C601E60025F0DF /* TableHeaderView.swift */; };
CDB6A2A228BC5F4600DEC80D /* ProHUD in Frameworks */ = {isa = PBXBuildFile; productRef = CDB6A2A128BC5F4600DEC80D /* ProHUD */; };
CDB7A1D028C32A7400E034D8 /* AlertVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDB7A1CF28C32A7400E034D8 /* AlertVC.swift */; };
@ -36,7 +35,7 @@
CD8EEF4328BC5C7300E660EA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
CD8EEF4628BC5C7300E660EA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
CD8EEF4828BC5C7300E660EA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
CD9C7B1D28CB8972006190CD /* Scenes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scenes.swift; sourceTree = "<group>"; };
CD9C7B1D28CB8972006190CD /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
CDA83DB828C601E60025F0DF /* TableHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableHeaderView.swift; sourceTree = "<group>"; };
CDB6A29F28BC5F0F00DEC80D /* ProHUD */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = ProHUD; path = ..; sourceTree = "<group>"; };
CDB7A1CF28C32A7400E034D8 /* AlertVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertVC.swift; sourceTree = "<group>"; };
@ -47,7 +46,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
CD6AE8A62A7CC1C70044E53D /* Inspire in Frameworks */,
CDB6A2A228BC5F4600DEC80D /* ProHUD in Frameworks */,
CD6AE8A32A7CC1BA0044E53D /* SnapKit in Frameworks */,
);
@ -81,7 +79,7 @@
CD8EEF3C28BC5C7200E660EA /* SceneDelegate.swift */,
CD6537BE28C3311B00A5981B /* ListModel.swift */,
CDA83DB828C601E60025F0DF /* TableHeaderView.swift */,
CD9C7B1D28CB8972006190CD /* Scenes.swift */,
CD9C7B1D28CB8972006190CD /* Extensions.swift */,
CD6537C028C35E1C00A5981B /* ListVC.swift */,
CDB7A1CF28C32A7400E034D8 /* AlertVC.swift */,
CD6537C228C35E6200A5981B /* ToastVC.swift */,
@ -128,7 +126,6 @@
packageProductDependencies = (
CDB6A2A128BC5F4600DEC80D /* ProHUD */,
CD6AE8A22A7CC1BA0044E53D /* SnapKit */,
CD6AE8A52A7CC1C70044E53D /* Inspire */,
);
productName = PHDemo;
productReference = CD8EEF3728BC5C7200E660EA /* PHDemo.app */;
@ -160,7 +157,6 @@
mainGroup = CD8EEF2E28BC5C7200E660EA;
packageReferences = (
CD6AE8A12A7CC1BA0044E53D /* XCLocalSwiftPackageReference "../../SnapKit" */,
CD6AE8A42A7CC1C70044E53D /* XCLocalSwiftPackageReference "../../Inspire" */,
);
productRefGroup = CD8EEF3828BC5C7200E660EA /* Products */;
projectDirPath = "";
@ -196,7 +192,7 @@
CD6537C128C35E1C00A5981B /* ListVC.swift in Sources */,
CD8EEF3B28BC5C7200E660EA /* AppDelegate.swift in Sources */,
CD8EEF3D28BC5C7200E660EA /* SceneDelegate.swift in Sources */,
CD9C7B1E28CB8972006190CD /* Scenes.swift in Sources */,
CD9C7B1E28CB8972006190CD /* Extensions.swift in Sources */,
CD6537BF28C3311B00A5981B /* ListModel.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -423,10 +419,6 @@
isa = XCLocalSwiftPackageReference;
relativePath = ../../SnapKit;
};
CD6AE8A42A7CC1C70044E53D /* XCLocalSwiftPackageReference "../../Inspire" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = ../../Inspire;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
@ -434,10 +426,6 @@
isa = XCSwiftPackageProductDependency;
productName = SnapKit;
};
CD6AE8A52A7CC1C70044E53D /* Inspire */ = {
isa = XCSwiftPackageProductDependency;
productName = Inspire;
};
CDB6A2A128BC5F4600DEC80D /* ProHUD */ = {
isa = XCSwiftPackageProductDependency;
productName = ProHUD;

View File

@ -0,0 +1,121 @@
//
// File.swift
// PHDemo
//
// Created by xaoxuu on 2022/9/9.
//
import UIKit
import ProHUD
public extension ViewModel {
// static var plain: ViewModel {
// ViewModel()
// }
// static var note: ViewModel {
// ViewModel(icon: UIImage(named: "prohud.note"))
// }
// MARK: note
static var note: ViewModel {
.init(icon: UIImage(named: "demo.note"))
}
static func note(_ seconds: TimeInterval) -> ViewModel {
.init(icon: UIImage(named: "demo.note"), duration: seconds)
}
static var msg: ViewModel {
ViewModel(icon: UIImage(named: "demo.message"))
}
static func msg(_ seconds: TimeInterval) -> ViewModel {
ViewModel(icon: UIImage(named: "demo.message"), duration: seconds)
}
static var delete: ViewModel {
ViewModel(icon: UIImage(named: "demo.trash"))
}
// MARK: confirm
static var confirm: ViewModel {
.init(icon: UIImage(named: "demo.questionmark"))
}
static func confirm(_ seconds: TimeInterval) -> ViewModel {
.init(icon: UIImage(named: "demo.questionmark"), duration: seconds)
}
// static func loading(_ seconds: TimeInterval) -> ViewModel {
// let obj = ViewModel(icon: UIImage(named: "prohud.rainbow.circle"), duration: seconds)
// obj.rotation = .init(repeatCount: .infinity)
// return obj
// }
}
extension CALayer {
var cornerRadiusWithContinuous: CGFloat {
set {
cornerRadius = newValue
if #available(iOS 13.0, *) {
cornerCurve = .continuous
} else {
// Fallback on earlier versions
}
}
get { cornerRadius }
}
}
extension UIColor {
/// hex
///
/// - Parameter hex: hex
convenience init(_ hex: String) {
func filter(hex: String) -> NSString{
let set = NSCharacterSet.whitespacesAndNewlines
var str = hex.trimmingCharacters(in: set).lowercased()
if str.hasPrefix("#") {
str = String(str.suffix(str.count-1))
} else if str.hasPrefix("0x") {
str = String(str.suffix(str.count-2))
}
return NSString(string: str)
}
let hex = filter(hex: hex)
let length = hex.length
guard length == 3 || length == 4 || length == 6 || length == 8 else {
print("无效的hex")
self.init("000")
return
}
func floatValue(from hex: String) -> CGFloat {
var result = Float(0)
Scanner(string: "0x"+hex).scanHexFloat(&result)
var maxStr = "0xf"
if length > 5 {
maxStr = "0xff"
}
var max = Float(0)
Scanner(string: maxStr).scanHexFloat(&max)
result = result / max
return CGFloat(result)
}
func substring(of hex: NSString, loc: Int) -> String {
if length == 3 || length == 4 {
return hex.substring(with: NSRange.init(location: loc, length: 1))
} else if length == 6 || length == 8 {
return hex.substring(with: NSRange.init(location: 2*loc, length: 2))
} else {
return ""
}
}
let r = floatValue(from: substring(of: hex, loc: 0))
let g = floatValue(from: substring(of: hex, loc: 1))
let b = floatValue(from: substring(of: hex, loc: 2))
var a = CGFloat(1)
if length == 4 || length == 8 {
a = floatValue(from: substring(of: hex, loc: 3))
}
self.init(red: r, green: g, blue: b, alpha: a)
}
}

View File

@ -1,47 +0,0 @@
//
// File.swift
// PHDemo
//
// Created by xaoxuu on 2022/9/9.
//
import UIKit
import ProHUD
public extension ViewModel {
// static var plain: ViewModel {
// ViewModel()
// }
// static var note: ViewModel {
// ViewModel(icon: UIImage(named: "prohud.note"))
// }
// MARK: note
static var note: ViewModel {
.init(icon: UIImage(named: "demo.note"))
}
static func note(_ seconds: TimeInterval) -> ViewModel {
.init(icon: UIImage(named: "demo.note"), duration: seconds)
}
static var msg: ViewModel {
ViewModel(icon: UIImage(named: "demo.message"))
}
static func msg(_ seconds: TimeInterval) -> ViewModel {
ViewModel(icon: UIImage(named: "demo.message"), duration: seconds)
}
static var delete: ViewModel {
ViewModel(icon: UIImage(named: "demo.trash"))
}
// MARK: confirm
static var confirm: ViewModel {
.init(icon: UIImage(named: "demo.questionmark"))
}
static func confirm(_ seconds: TimeInterval) -> ViewModel {
.init(icon: UIImage(named: "demo.questionmark"), duration: seconds)
}
// static func loading(_ seconds: TimeInterval) -> ViewModel {
// let obj = ViewModel(icon: UIImage(named: "prohud.rainbow.circle"), duration: seconds)
// obj.rotation = .init(repeatCount: .infinity)
// return obj
// }
}

View File

@ -295,7 +295,7 @@ class ToastVC: ListVC {
}
toast.add(action: "纯色") { toast in
toast.contentMaskView.effect = .none
toast.contentMaskView.backgroundColor = .red.lighten()
toast.contentMaskView.backgroundColor = .red
}
}

View File

@ -10,12 +10,11 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/SnapKit/SnapKit.git", "5.0.0" ..< "6.0.0"),
.package(url: "https://github.com/xaoxuu/Inspire.git", "3.0.0" ..< "4.0.0"),
],
targets: [
.target(
name: "ProHUD",
dependencies: ["SnapKit", "Inspire"],
dependencies: ["SnapKit"],
resources: [.process("Resources/ProHUD.xcassets")]
)
]

View File

@ -21,13 +21,13 @@ class AlertWindow: Window {
}
let w: AlertWindow
if #available(iOS 13.0, *) {
if let scene = config.windowScene ?? UIWindowScene.mainWindowScene {
if let scene = AppContext.windowScene {
w = .init(windowScene: scene)
} else {
w = .init(frame: UIScreen.main.bounds)
w = .init(frame: AppContext.appBounds)
}
} else {
w = .init(frame: UIScreen.main.bounds)
w = .init(frame: AppContext.appBounds)
}
AlertWindow.current = w
// alert

View File

@ -11,24 +11,6 @@ public class Configuration: NSObject {
/// log
public static var enablePrint = true
///
public var rootViewController: UIViewController?
/// iOS13
@available(iOS 13.0, *)
private static var sharedWindowScene: UIWindowScene?
/// iOS13
@available(iOS 13.0, *)
public var windowScene: UIWindowScene? {
set {
Self.sharedWindowScene = newValue
}
get {
return Self.sharedWindowScene
}
}
public lazy var dynamicBackgroundColor: UIColor = {
if #available(iOS 13.0, *) {
@ -78,12 +60,12 @@ public class Configuration: NSObject {
/// iPad
public var cardMaxWidth: CGFloat?
var cardMaxWidthByDefault: CGFloat {
cardMaxWidth ?? .minimum(UIScreen.main.bounds.width * 0.72, 400)
cardMaxWidth ?? .minimum(AppContext.appBounds.width * 0.72, 400)
}
public var cardMaxHeight: CGFloat?
var cardMaxHeightByDefault: CGFloat {
cardMaxHeight ?? (UIScreen.main.bounds.height - 100)
cardMaxHeight ?? (AppContext.appBounds.height - 100)
}
///
public var cardMinWidth = CGFloat(32)

View File

@ -0,0 +1,78 @@
//
// AppContext.swift
//
//
// Created by xaoxuu on 2023/8/5.
//
import UIKit
public struct AppContext {
@available(iOS 13.0, *)
private static var storedAppWindowScene: UIWindowScene?
private static var storedAppWindow: UIWindow?
private init() {}
}
public extension AppContext {
@available(iOS 13.0, *)
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
}
}
}
///
static var windows: [UIWindow] {
if #available(iOS 13.0, *) {
return windowScene?.windows ?? UIApplication.shared.windows
} else {
return UIApplication.shared.windows
}
}
///
static var visibleWindows: [UIWindow] {
windows.filter { $0.isHidden == false }
}
/// 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 }
}
/// App
static var appBounds: CGRect {
if #available(iOS 13.0, *) {
return appWindow?.bounds ?? UIScreen.main.bounds
} else {
return UIScreen.main.bounds
}
}
/// App
static var safeAreaInsets: UIEdgeInsets { appWindow?.safeAreaInsets ?? .zero }
}

View File

@ -6,11 +6,6 @@
//
import UIKit
import Inspire
var screenSafeAreaInsets: UIEdgeInsets {
Inspire.shared.screen.safeAreaInsets
}
extension UIImage {
public convenience init?(inProHUD named: String) {
@ -23,8 +18,11 @@ extension UIImage {
}
///
/// ()
internal var isPortrait: Bool {
if AppContext.appBounds.width < 450 {
return true
}
if UIDevice.current.userInterfaceIdiom == .phone {
if UIApplication.shared.statusBarOrientation.isPortrait {
return true

View File

@ -52,15 +52,3 @@ class Window: UIWindow {
}
}
@available(iOS 13.0, *)
extension UIWindowScene {
static var mainWindowScene: UIWindowScene? {
UIApplication.shared.connectedScenes.first(where: { scene in
guard let ws = scene as? UIWindowScene else { return false }
return ws.activationState == .foregroundActive
}) as? UIWindowScene
}
}

View File

@ -43,7 +43,7 @@ public extension Sheet {
override var cardMaxWidthByDefault: CGFloat { cardMaxWidth ?? 500 }
override var cardMaxHeightByDefault: CGFloat { cardMaxHeight ?? (UIScreen.main.bounds.height - 50) }
override var cardMaxHeightByDefault: CGFloat { cardMaxHeight ?? (AppContext.appBounds.height - 50) }
override var animateDurationForBuildInByDefault: CGFloat {
animateDurationForBuildIn ?? 0.5

View File

@ -44,7 +44,7 @@ extension Sheet: DefaultLayout {
loadContentMaskViewIfNeeded()
// layout
let maxWidth = config.cardMaxWidthByDefault
var width = UIScreen.main.bounds.width - config.screenEdgeInset * 2
var width = AppContext.appBounds.width - config.screenEdgeInset * 2
if width > maxWidth {
// landscape iPhone or iPad
width = maxWidth
@ -54,14 +54,15 @@ extension Sheet: DefaultLayout {
make.edges.equalToSuperview()
} else {
make.centerX.equalToSuperview()
if UIDevice.current.userInterfaceIdiom == .phone {
if width < maxWidth {
if UIDevice.current.userInterfaceIdiom == .pad && width >= maxWidth {
// iPad
make.centerY.equalToSuperview()
} else {
if isPortrait {
make.bottom.equalToSuperview().inset(config.screenEdgeInset)
} else {
make.bottom.equalToSuperview().inset(screenSafeAreaInsets.bottom)
make.bottom.equalToSuperview().inset(AppContext.safeAreaInsets.bottom)
}
} else if UIDevice.current.userInterfaceIdiom == .pad {
make.centerY.equalToSuperview()
}
make.width.equalTo(width)
make.height.greaterThanOrEqualTo(config.cardCornerRadiusByDefault * 2)
@ -80,7 +81,7 @@ extension Sheet: DefaultLayout {
contentStack.snp.remakeConstraints { make in
let safeArea: UIEdgeInsets
if config.isFullScreen {
safeArea = screenSafeAreaInsets
safeArea = AppContext.safeAreaInsets
} else {
safeArea = .zero
}

View File

@ -16,13 +16,13 @@ class SheetWindow: Window {
init(sheet: Sheet) {
self.sheet = sheet
if #available(iOS 13.0, *) {
if let scene = sheet.config.windowScene ?? UIWindowScene.mainWindowScene {
if let scene = AppContext.windowScene {
super.init(windowScene: scene)
} else {
super.init(frame: UIScreen.main.bounds)
super.init(frame: AppContext.appBounds)
}
} else {
super.init(frame: UIScreen.main.bounds)
super.init(frame: AppContext.appBounds)
}
sheet.window = self
windowLevel = .init(rawValue: UIWindow.Level.alert.rawValue - 2)

View File

@ -34,7 +34,7 @@ public extension Toast {
}
override var cardMaxHeightByDefault: CGFloat {
cardMaxHeight ?? (UIScreen.main.bounds.height / 3)
cardMaxHeight ?? (AppContext.appBounds.height / 3)
}
override var animateDurationForBuildInByDefault: CGFloat {

View File

@ -19,7 +19,7 @@ class ToastWindow: Window {
self.toast = toast
super.init(frame: .zero)
if #available(iOS 13.0, *) {
windowScene = toast.config.windowScene ?? UIWindowScene.mainWindowScene
windowScene = AppContext.windowScene
}
toast.window = self
windowLevel = .init(rawValue: UIWindow.Level.alert.rawValue + 1000)
@ -49,8 +49,9 @@ class ToastWindow: Window {
isNew = true
}
let config = toast.config
// frame
let width = CGFloat.minimum(UIScreen.main.bounds.width - config.cardEdgeInsets.left - config.cardEdgeInsets.right, config.cardMaxWidthByDefault)
let width = CGFloat.minimum(AppContext.appBounds.width - config.cardEdgeInsets.left - config.cardEdgeInsets.right, config.cardMaxWidthByDefault)
toast.view.frame.size = CGSize(width: width, height: config.cardMaxHeightByDefault)
toast.titleLabel.sizeToFit()
toast.bodyLabel.sizeToFit()
@ -59,7 +60,7 @@ class ToastWindow: Window {
let height = toast.calcHeight()
toast.view.frame.size = CGSize(width: width, height: height)
// frame
window.frame = CGRect(x: (UIScreen.main.bounds.width - width) / 2, y: 0, width: width, height: height)
window.frame = CGRect(x: (AppContext.appBounds.width - width) / 2, y: 0, width: width, height: height)
window.rootViewController = toast // toast.view.frame.sizewindow.frame.size
if windows.contains(window) == false {
windows.append(window)
@ -108,16 +109,11 @@ fileprivate var updateToastsLayoutWorkItem: DispatchWorkItem?
fileprivate extension ToastWindow {
static func setToastWindowsLayout() {
let top = screenSafeAreaInsets.top
for (i, window) in windows.enumerated() {
let config = window.toast.config
var y = window.frame.origin.y
if i == 0 {
if isPortrait {
y = top
} else {
y = config.margin
}
y = max(AppContext.appWindow?.layoutMargins.top ?? config.margin, config.margin)
} else {
if i - 1 < windows.count && i > 0 {
y = config.margin + windows[i-1].frame.maxY