Files
Pulse/Sources/PulseUI/Extensions/SwiftUI+Extensions.swift
T
2023-04-15 14:23:12 -04:00

238 lines
6.0 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// The MIT License (MIT)
//
// Copyright (c) 20202023 Alexander Grebenyuk (github.com/kean).
import SwiftUI
import Combine
#if os(iOS) || os(macOS)
extension Color {
static var separator: Color { Color(UXColor.separator) }
static var indigo: Color { Color(UXColor.systemIndigo) }
static var secondaryFill: Color { Color(UXColor.secondarySystemFill) }
}
#endif
#if os(watchOS) || os(tvOS)
extension Color {
static var indigo: Color { .purple }
static var separator: Color { Color.secondary.opacity(0.3) }
static var secondaryFill: Color { Color.secondary.opacity(0.3) }
}
#endif
extension View {
func invisible() -> some View {
self.hidden().accessibilityHidden(true)
}
}
extension ContentSizeCategory {
var scale: CGFloat {
switch self {
case .extraSmall: return 0.7
case .small: return 0.8
case .medium: return 1.0
case .large: return 1.0
case .extraLarge: return 1.0
case .extraExtraLarge: return 1.2
case .extraExtraExtraLarge: return 1.3
case .accessibilityMedium: return 1.4
case .accessibilityLarge: return 1.6
case .accessibilityExtraLarge: return 1.9
case .accessibilityExtraExtraLarge: return 2.1
case .accessibilityExtraExtraExtraLarge: return 2.4
@unknown default: return 1.0
}
}
}
#if os(iOS)
enum Keyboard {
static var isHidden: AnyPublisher<Bool, Never> {
Publishers.Merge(
NotificationCenter.default
.publisher(for: UIResponder.keyboardWillShowNotification)
.map { _ in false },
NotificationCenter.default
.publisher(for: UIResponder.keyboardWillHideNotification)
.map { _ in true }
)
.eraseToAnyPublisher()
}
}
#endif
// MARK: - Backport
struct Backport<Content: View> {
let content: Content
}
extension View {
var backport: Backport<Self> { Backport(content: self) }
}
enum SwipeActionEdge {
case leading
case trailing
#if os(iOS) || os(tvOS) || os(macOS)
@available(iOS 15, tvOS 15, *)
var native: HorizontalEdge {
switch self {
case .leading: return .leading
case .trailing: return .trailing
}
}
#endif
}
extension Backport {
@ViewBuilder
func tint(_ color: Color) -> some View {
if #available(iOS 15, tvOS 15, *) {
content.tint(color)
} else {
content.foregroundColor(color)
}
}
@ViewBuilder
func swipeActions<T: View>(edge: SwipeActionEdge = .trailing, allowsFullSwipe: Bool = true, @ViewBuilder closure: () -> T) -> some View {
#if os(iOS) || os(tvOS) || os(macOS)
if #available(iOS 15, tvOS 15, *) {
content.swipeActions(edge: edge.native, allowsFullSwipe: allowsFullSwipe, content: closure)
} else {
content
}
#else
content
#endif
}
@ViewBuilder
func searchable(text: Binding<String>) -> some View {
if #available(iOS 15, tvOS 15, *) {
content.searchable(text: text)
} else {
content
}
}
@ViewBuilder
func contextMenu<M: View, P: View>(@ViewBuilder menuItems: () -> M, @ViewBuilder preview: () -> P) -> some View {
#if !os(watchOS)
if #available(iOS 16, tvOS 16, macOS 13, *) {
self.content.contextMenu(menuItems: menuItems, preview: preview)
} else {
self.content.contextMenu(menuItems: menuItems)
}
#else
self.content
#endif
}
@ViewBuilder
func presentationDetents(_ detents: Set<PresentationDetent>) -> some View {
#if os(iOS)
if #available(iOS 16, *) {
let detents = detents.map { (detent)-> SwiftUI.PresentationDetent in
switch detent {
case .large: return .large
case .medium: return .medium
}
}
self.content.presentationDetents(Set(detents))
} else {
self.content
}
#else
self.content
#endif
}
@ViewBuilder
func monospacedDigit() -> some View {
if #available(iOS 15, tvOS 15, *) {
self.content.monospacedDigit()
} else {
self.content
}
}
@ViewBuilder
func hideListContentBackground() -> some View {
#if os(macOS)
if #available(macOS 13, *) {
self.content.scrollContentBackground(.hidden)
} else {
self.content
}
#else
self.content
#endif
}
#if os(macOS)
@ViewBuilder
func showListSeparators() -> some View {
if #available(macOS 13, *) {
self.content.listRowSeparator(.visible)
} else {
self.content
}
}
#endif
enum PresentationDetent {
case large
case medium
}
}
extension Button {
@ViewBuilder
static func destructive(action: @escaping () -> Void, label: () -> Label) -> some View {
if #available(iOS 15.0, tvOS 15, *) {
Button(role: .destructive, action: action, label: label)
} else {
Button(action: action, label: label)
}
}
}
extension View {
func inlineNavigationTitle(_ title: String) -> some View {
self.navigationTitle(title)
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
#endif
}
#if os(macOS)
func showInWindow() {
let window = NSWindow()
window.isOpaque = false
window.center()
window.isReleasedWhenClosed = false
window.hidesOnDeactivate = true
window.styleMask = window.styleMask.union([.resizable, .closable, .miniaturizable])
window.toolbarStyle = .unified
window.titleVisibility = .hidden
window.titlebarSeparatorStyle = .none
window.titlebarAppearsTransparent = true
window.contentViewController = NSHostingController(rootView: self)
window.makeKeyAndOrderFront(nil)
}
#endif
func apply<T>(_ closure: (Self) -> T) -> T {
closure(self)
}
}