UTMExtensions.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. //
  2. // Copyright © 2020 osy. All rights reserved.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. import SwiftUI
  17. import UniformTypeIdentifiers
  18. import Network
  19. extension Optional where Wrapped == String {
  20. var _bound: String? {
  21. get {
  22. return self
  23. }
  24. set {
  25. self = newValue
  26. }
  27. }
  28. public var bound: String {
  29. get {
  30. return _bound ?? ""
  31. }
  32. set {
  33. _bound = newValue.isEmpty ? nil : newValue
  34. }
  35. }
  36. }
  37. extension Optional where Wrapped: FixedWidthInteger {
  38. var _bound: Wrapped? {
  39. get {
  40. return self
  41. }
  42. set {
  43. self = newValue
  44. }
  45. }
  46. public var bound: Wrapped {
  47. get {
  48. return _bound ?? 0
  49. }
  50. set {
  51. _bound = newValue == 0 ? nil : newValue
  52. }
  53. }
  54. }
  55. extension Optional where Wrapped == Bool {
  56. var _bound: Wrapped? {
  57. get {
  58. return self
  59. }
  60. set {
  61. self = newValue
  62. }
  63. }
  64. public var bound: Wrapped {
  65. get {
  66. return _bound ?? false
  67. }
  68. set {
  69. _bound = newValue
  70. }
  71. }
  72. }
  73. extension Binding where Value == Bool {
  74. var inverted: Binding<Bool> {
  75. Binding {
  76. !wrappedValue
  77. } set: { newValue in
  78. wrappedValue = !newValue
  79. }
  80. }
  81. }
  82. extension LocalizedStringKey {
  83. var localizedString: String {
  84. let mirror = Mirror(reflecting: self)
  85. var key: String? = nil
  86. for property in mirror.children {
  87. if property.label == "key" {
  88. key = property.value as? String
  89. }
  90. }
  91. guard let goodKey = key else {
  92. logger.error("Failed to get localization key")
  93. return ""
  94. }
  95. return NSLocalizedString(goodKey, comment: "LocalizedStringKey")
  96. }
  97. }
  98. extension String: LocalizedError {
  99. public var errorDescription: String? { return self }
  100. }
  101. extension String: Identifiable {
  102. public var id: String { return self }
  103. }
  104. extension IndexSet: Identifiable {
  105. public var id: Int {
  106. self.hashValue
  107. }
  108. }
  109. extension Array {
  110. subscript(indicies: IndexSet) -> [Element] {
  111. get {
  112. var slice = [Element]()
  113. for i in indicies {
  114. slice.append(self[i])
  115. }
  116. return slice
  117. }
  118. }
  119. }
  120. extension View {
  121. func onReceive(_ name: Notification.Name,
  122. center: NotificationCenter = .default,
  123. object: AnyObject? = nil,
  124. perform action: @escaping (Notification) -> Void) -> some View {
  125. self.onReceive(
  126. center.publisher(for: name, object: object), perform: action
  127. )
  128. }
  129. }
  130. extension UTType {
  131. static let UTM = UTType(exportedAs: "com.utmapp.utm")
  132. // SwiftUI BUG: exportedAs: "com.utmapp.utm" doesn't work on macOS and older iOS
  133. static let UTMextension = UTType(exportedAs: "utm")
  134. static let appleLog = UTType(filenameExtension: "log")!
  135. static let ipsw = UTType(filenameExtension: "ipsw")!
  136. }
  137. extension Sequence where Element: Hashable {
  138. func uniqued() -> [Element] {
  139. var set = Set<Element>()
  140. return filter { set.insert($0).inserted }
  141. }
  142. }
  143. extension Color {
  144. init?(hexString hex: String) {
  145. if hex.count != 7 { // The '#' included
  146. return nil
  147. }
  148. let hexColor = String(hex.dropFirst())
  149. let scanner = Scanner(string: hexColor)
  150. var hexNumber: UInt64 = 0
  151. if !scanner.scanHexInt64(&hexNumber) {
  152. return nil
  153. }
  154. let r = CGFloat((hexNumber & 0xff0000) >> 16) / 255
  155. let g = CGFloat((hexNumber & 0x00ff00) >> 8) / 255
  156. let b = CGFloat(hexNumber & 0x0000ff) / 255
  157. self.init(.displayP3, red: r, green: g, blue: b, opacity: 1.0)
  158. }
  159. }
  160. extension CGColor {
  161. var hexString: String? {
  162. hexString(for: .init(name: CGColorSpace.displayP3)!)
  163. }
  164. var sRGBhexString: String? {
  165. hexString(for: .init(name: CGColorSpace.sRGB)!)
  166. }
  167. private func hexString(for colorSpace: CGColorSpace) -> String? {
  168. guard let rgbColor = self.converted(to: colorSpace, intent: .defaultIntent, options: nil),
  169. let components = rgbColor.components else {
  170. return nil
  171. }
  172. let red = Int(round(components[0] * 0xFF))
  173. let green = Int(round(components[1] * 0xFF))
  174. let blue = Int(round(components[2] * 0xFF))
  175. return String(format: "#%02X%02X%02X", red, green, blue)
  176. }
  177. }
  178. #if !os(macOS)
  179. @objc extension UIView {
  180. /// Adds constraints to this `UIView` instances `superview` object to make sure this always has the same size as the superview.
  181. /// Please note that this has no effect if its `superview` is `nil` – add this `UIView` instance as a subview before calling this.
  182. func bindFrameToSuperviewBounds() {
  183. guard let superview = self.superview else {
  184. print("Error! `superview` was nil – call `addSubview(view: UIView)` before calling `bindFrameToSuperviewBounds()` to fix this.")
  185. return
  186. }
  187. self.translatesAutoresizingMaskIntoConstraints = false
  188. self.topAnchor.constraint(equalTo: superview.topAnchor, constant: 0).isActive = true
  189. self.bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: 0).isActive = true
  190. self.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: 0).isActive = true
  191. self.trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: 0).isActive = true
  192. }
  193. }
  194. extension UIImage {
  195. convenience init?(contentsOfURL: URL?) {
  196. if let url = contentsOfURL {
  197. let scoped = url.startAccessingSecurityScopedResource()
  198. defer {
  199. if scoped {
  200. url.stopAccessingSecurityScopedResource()
  201. }
  202. }
  203. self.init(contentsOfFile: url.path)
  204. } else {
  205. return nil
  206. }
  207. }
  208. }
  209. // Only used in hterm support
  210. @objc extension UIColor {
  211. convenience init?(hexString hex: String?) {
  212. guard let hex = hex else {
  213. return nil
  214. }
  215. if hex.count != 7 { // The '#' included
  216. return nil
  217. }
  218. let hexColor = String(hex.dropFirst())
  219. let scanner = Scanner(string: hexColor)
  220. var hexNumber: UInt64 = 0
  221. if !scanner.scanHexInt64(&hexNumber) {
  222. return nil
  223. }
  224. let r = CGFloat((hexNumber & 0xff0000) >> 16) / 255
  225. let g = CGFloat((hexNumber & 0x00ff00) >> 8) / 255
  226. let b = CGFloat(hexNumber & 0x0000ff) / 255
  227. self.init(displayP3Red: r, green: g, blue: b, alpha: 1.0)
  228. }
  229. var sRGBhexString: String? {
  230. cgColor.sRGBhexString
  231. }
  232. }
  233. #endif
  234. #if canImport(AppKit)
  235. typealias PlatformImage = NSImage
  236. #elseif canImport(UIKit)
  237. typealias PlatformImage = UIImage
  238. #endif
  239. #if os(macOS)
  240. enum FakeKeyboardType : Int {
  241. case asciiCapable
  242. case decimalPad
  243. case numberPad
  244. }
  245. struct EditButton {
  246. }
  247. extension View {
  248. func keyboardType(_ type: FakeKeyboardType) -> some View {
  249. return self
  250. }
  251. func navigationBarItems(trailing: EditButton) -> some View {
  252. return self
  253. }
  254. }
  255. extension NSImage {
  256. convenience init?(contentsOfURL: URL?) {
  257. if let url = contentsOfURL {
  258. let scoped = url.startAccessingSecurityScopedResource()
  259. defer {
  260. if scoped {
  261. url.stopAccessingSecurityScopedResource()
  262. }
  263. }
  264. self.init(contentsOf: url)
  265. } else {
  266. return nil
  267. }
  268. }
  269. }
  270. #endif
  271. @propertyWrapper
  272. struct Setting<T> {
  273. private(set) var keyName: String
  274. private var defaultValue: T
  275. var wrappedValue: T {
  276. get {
  277. let defaults = UserDefaults.standard
  278. guard let value = defaults.value(forKey: keyName) else {
  279. return defaultValue
  280. }
  281. return value as! T
  282. }
  283. set {
  284. let defaults = UserDefaults.standard
  285. defaults.set(newValue, forKey: keyName)
  286. }
  287. }
  288. init(wrappedValue: T, _ keyName: String) {
  289. self.defaultValue = wrappedValue
  290. self.keyName = keyName
  291. }
  292. }
  293. // MARK: - Bookmark handling
  294. extension URL {
  295. private static var defaultCreationOptions: BookmarkCreationOptions {
  296. #if os(iOS) || os(visionOS)
  297. return .minimalBookmark
  298. #else
  299. return .withSecurityScope
  300. #endif
  301. }
  302. private static var defaultResolutionOptions: BookmarkResolutionOptions {
  303. #if os(iOS) || os(visionOS)
  304. return []
  305. #else
  306. return .withSecurityScope
  307. #endif
  308. }
  309. func persistentBookmarkData(isReadyOnly: Bool = false) throws -> Data {
  310. var options = Self.defaultCreationOptions
  311. #if os(macOS)
  312. if isReadyOnly {
  313. options.insert(.securityScopeAllowOnlyReadAccess)
  314. }
  315. #endif
  316. let scopedAccess = startAccessingSecurityScopedResource()
  317. defer {
  318. if scopedAccess {
  319. stopAccessingSecurityScopedResource()
  320. }
  321. }
  322. return try self.bookmarkData(options: options,
  323. includingResourceValuesForKeys: nil,
  324. relativeTo: nil)
  325. }
  326. init(resolvingPersistentBookmarkData bookmark: Data) throws {
  327. var stale: Bool = false
  328. try self.init(resolvingBookmarkData: bookmark,
  329. options: Self.defaultResolutionOptions,
  330. bookmarkDataIsStale: &stale)
  331. }
  332. }
  333. extension String {
  334. func integerPrefix() -> Int? {
  335. var numeric = ""
  336. for char in self {
  337. if !char.isNumber {
  338. break
  339. }
  340. numeric.append(char)
  341. }
  342. return Int(numeric)
  343. }
  344. static func random(length: Int) -> String {
  345. let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  346. return String((0..<length).map{ _ in letters.randomElement()! })
  347. }
  348. }
  349. extension Encodable {
  350. func propertyList() throws -> Any {
  351. let encoder = PropertyListEncoder()
  352. encoder.outputFormat = .xml
  353. let xml = try encoder.encode(self)
  354. return try PropertyListSerialization.propertyList(from: xml, format: nil)
  355. }
  356. }
  357. extension Decodable {
  358. init(fromPropertyList propertyList: Any) throws {
  359. let data = try PropertyListSerialization.data(fromPropertyList: propertyList, format: .xml, options: 0)
  360. let decoder = PropertyListDecoder()
  361. self = try decoder.decode(Self.self, from: data)
  362. }
  363. }
  364. extension NWEndpoint {
  365. var hostname: String? {
  366. if case .hostPort(let host, _) = self {
  367. switch host {
  368. case .name(let hostname, _):
  369. return hostname
  370. case .ipv4(let address):
  371. return "\(address)"
  372. case .ipv6(let address):
  373. return "\(address)"
  374. @unknown default:
  375. break
  376. }
  377. }
  378. return nil
  379. }
  380. }