UTM/Services/UTMExtensions.swift

368 lines
9.5 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.

//
// Copyright © 2020 osy. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import SwiftUI
import UniformTypeIdentifiers
extension Optional where Wrapped == String {
var _bound: String? {
get {
return self
}
set {
self = newValue
}
}
public var bound: String {
get {
return _bound ?? ""
}
set {
_bound = newValue.isEmpty ? nil : newValue
}
}
}
extension Optional where Wrapped: FixedWidthInteger {
var _bound: Wrapped? {
get {
return self
}
set {
self = newValue
}
}
public var bound: Wrapped {
get {
return _bound ?? 0
}
set {
_bound = newValue == 0 ? nil : newValue
}
}
}
extension Optional where Wrapped == Bool {
var _bound: Wrapped? {
get {
return self
}
set {
self = newValue
}
}
public var bound: Wrapped {
get {
return _bound ?? false
}
set {
_bound = newValue
}
}
}
extension Binding where Value == Bool {
var inverted: Binding<Bool> {
Binding {
!wrappedValue
} set: { newValue in
wrappedValue = !newValue
}
}
}
extension LocalizedStringKey {
var localizedString: String {
let mirror = Mirror(reflecting: self)
var key: String? = nil
for property in mirror.children {
if property.label == "key" {
key = property.value as? String
}
}
guard let goodKey = key else {
logger.error("Failed to get localization key")
return ""
}
return NSLocalizedString(goodKey, comment: "LocalizedStringKey")
}
}
extension String: LocalizedError {
public var errorDescription: String? { return self }
}
extension String: Identifiable {
public var id: String { return self }
}
extension IndexSet: Identifiable {
public var id: Int {
self.hashValue
}
}
extension Array {
subscript(indicies: IndexSet) -> [Element] {
get {
var slice = [Element]()
for i in indicies {
slice.append(self[i])
}
return slice
}
}
}
extension View {
func onReceive(_ name: Notification.Name,
center: NotificationCenter = .default,
object: AnyObject? = nil,
perform action: @escaping (Notification) -> Void) -> some View {
self.onReceive(
center.publisher(for: name, object: object), perform: action
)
}
}
extension UTType {
static let UTM = UTType(exportedAs: "com.utmapp.utm")
// SwiftUI BUG: exportedAs: "com.utmapp.utm" doesn't work on macOS and older iOS
static let UTMextension = UTType(exportedAs: "utm")
static let appleLog = UTType(filenameExtension: "log")!
static let ipsw = UTType(filenameExtension: "ipsw")!
}
extension Sequence where Element: Hashable {
func uniqued() -> [Element] {
var set = Set<Element>()
return filter { set.insert($0).inserted }
}
}
extension Color {
init?(hexString hex: String) {
if hex.count != 7 { // The '#' included
return nil
}
let hexColor = String(hex.dropFirst())
let scanner = Scanner(string: hexColor)
var hexNumber: UInt64 = 0
if !scanner.scanHexInt64(&hexNumber) {
return nil
}
let r = CGFloat((hexNumber & 0xff0000) >> 16) / 255
let g = CGFloat((hexNumber & 0x00ff00) >> 8) / 255
let b = CGFloat(hexNumber & 0x0000ff) / 255
self.init(.displayP3, red: r, green: g, blue: b, opacity: 1.0)
}
}
extension CGColor {
var hexString: String? {
hexString(for: .init(name: CGColorSpace.displayP3)!)
}
var sRGBhexString: String? {
hexString(for: .init(name: CGColorSpace.sRGB)!)
}
private func hexString(for colorSpace: CGColorSpace) -> String? {
guard let rgbColor = self.converted(to: colorSpace, intent: .defaultIntent, options: nil),
let components = rgbColor.components else {
return nil
}
let red = Int(round(components[0] * 0xFF))
let green = Int(round(components[1] * 0xFF))
let blue = Int(round(components[2] * 0xFF))
return String(format: "#%02X%02X%02X", red, green, blue)
}
}
#if !os(macOS)
@objc extension UIView {
/// Adds constraints to this `UIView` instances `superview` object to make sure this always has the same size as the superview.
/// Please note that this has no effect if its `superview` is `nil` add this `UIView` instance as a subview before calling this.
func bindFrameToSuperviewBounds() {
guard let superview = self.superview else {
print("Error! `superview` was nil call `addSubview(view: UIView)` before calling `bindFrameToSuperviewBounds()` to fix this.")
return
}
self.translatesAutoresizingMaskIntoConstraints = false
self.topAnchor.constraint(equalTo: superview.topAnchor, constant: 0).isActive = true
self.bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: 0).isActive = true
self.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: 0).isActive = true
self.trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: 0).isActive = true
}
}
extension UIImage {
convenience init?(contentsOfURL: URL?) {
if let url = contentsOfURL {
self.init(contentsOfFile: url.path)
} else {
return nil
}
}
}
// Only used in hterm support
@objc extension UIColor {
convenience init?(hexString hex: String?) {
guard let hex = hex else {
return nil
}
if hex.count != 7 { // The '#' included
return nil
}
let hexColor = String(hex.dropFirst())
let scanner = Scanner(string: hexColor)
var hexNumber: UInt64 = 0
if !scanner.scanHexInt64(&hexNumber) {
return nil
}
let r = CGFloat((hexNumber & 0xff0000) >> 16) / 255
let g = CGFloat((hexNumber & 0x00ff00) >> 8) / 255
let b = CGFloat(hexNumber & 0x0000ff) / 255
self.init(displayP3Red: r, green: g, blue: b, alpha: 1.0)
}
var sRGBhexString: String? {
cgColor.sRGBhexString
}
}
#endif
#if canImport(AppKit)
typealias PlatformImage = NSImage
#elseif canImport(UIKit)
typealias PlatformImage = UIImage
#endif
#if os(macOS)
enum FakeKeyboardType : Int {
case asciiCapable
case decimalPad
case numberPad
}
struct EditButton {
}
extension View {
func keyboardType(_ type: FakeKeyboardType) -> some View {
return self
}
func navigationBarItems(trailing: EditButton) -> some View {
return self
}
}
extension NSImage {
convenience init?(contentsOfURL: URL?) {
if let url = contentsOfURL {
self.init(contentsOf: url)
} else {
return nil
}
}
}
#endif
@propertyWrapper
struct Setting<T> {
private(set) var keyName: String
private var defaultValue: T
var wrappedValue: T {
get {
let defaults = UserDefaults.standard
guard let value = defaults.value(forKey: keyName) else {
return defaultValue
}
return value as! T
}
set {
let defaults = UserDefaults.standard
defaults.set(newValue, forKey: keyName)
}
}
init(wrappedValue: T, _ keyName: String) {
self.defaultValue = wrappedValue
self.keyName = keyName
}
}
// MARK: - Bookmark handling
extension URL {
private static var defaultCreationOptions: BookmarkCreationOptions {
#if os(iOS) || os(visionOS)
return .minimalBookmark
#else
return .withSecurityScope
#endif
}
private static var defaultResolutionOptions: BookmarkResolutionOptions {
#if os(iOS) || os(visionOS)
return []
#else
return .withSecurityScope
#endif
}
func persistentBookmarkData(isReadyOnly: Bool = false) throws -> Data {
var options = Self.defaultCreationOptions
#if os(macOS)
if isReadyOnly {
options.insert(.securityScopeAllowOnlyReadAccess)
}
#endif
return try self.bookmarkData(options: options,
includingResourceValuesForKeys: nil,
relativeTo: nil)
}
init(resolvingPersistentBookmarkData bookmark: Data) throws {
var stale: Bool = false
try self.init(resolvingBookmarkData: bookmark,
options: Self.defaultResolutionOptions,
bookmarkDataIsStale: &stale)
}
}