-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
URL+Helpers.swift
176 lines (151 loc) · 5.57 KB
/
URL+Helpers.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
import Foundation
import MobileCoreServices
import UniformTypeIdentifiers
extension URL {
/// The URLResource fileSize of the file at the URL in bytes, if available.
///
var fileSize: Int64? {
guard isFileURL else {
return nil
}
let values = try? resourceValues(forKeys: [.fileSizeKey])
guard let fileSize = values?.allValues[.fileSizeKey] as? NSNumber else {
return nil
}
return fileSize.int64Value
}
/// The URLResource uniform type identifier for the file at the URL, if available.
///
var typeIdentifier: String? {
let values = try? resourceValues(forKeys: [.typeIdentifierKey])
return values?.typeIdentifier
}
var typeIdentifierFileExtension: String? {
contentType?.preferredFilenameExtension
}
/// Returns a URL with an incremental file name, if a file already exists at the given URL.
///
/// Previously seen in MediaService.m within urlForMediaWithFilename:andExtension:
///
func incrementalFilename() -> URL {
var url = self
let pathExtension = url.pathExtension
let filename = url.deletingPathExtension().lastPathComponent
var index = 1
let fileManager = FileManager.default
while fileManager.fileExists(atPath: url.path) {
let incrementedName = "\(filename)-\(index)"
url.deleteLastPathComponent()
url.appendPathComponent(incrementedName, isDirectory: false)
url.appendPathExtension(pathExtension)
index += 1
}
return url
}
var pixelSize: CGSize {
get {
if isVideo {
let asset = AVAsset(url: self as URL)
if let track = asset.tracks(withMediaType: .video).first {
return track.naturalSize.applying(track.preferredTransform)
}
} else if isImage {
let options: [NSString: NSObject] = [kCGImageSourceShouldCache: false as CFBoolean]
if
let imageSource = CGImageSourceCreateWithURL(self as NSURL, nil),
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, options as CFDictionary?) as NSDictionary?,
let pixelWidth = imageProperties[kCGImagePropertyPixelWidth as NSString] as? Int,
let pixelHeight = imageProperties[kCGImagePropertyPixelHeight as NSString] as? Int
{
return CGSize(width: pixelWidth, height: pixelHeight)
}
}
return CGSize.zero
}
}
var mimeType: String {
contentType?.preferredMIMEType ?? "application/octet-stream"
}
private var contentType: UTType? {
typeIdentifier.flatMap(UTType.init)
}
var isVideo: Bool {
contentType?.conforms(to: .movie) ?? false
}
var isImage: Bool {
contentType?.conforms(to: .image) ?? false
}
var isGif: Bool {
if let type = contentType {
return type.conforms(to: .gif)
} else {
return pathExtension.lowercased() == "gif"
}
}
var isHostedAtWPCom: Bool {
guard let host = host else {
return false
}
return host.hasSuffix(".wordpress.com")
}
var isWordPressDotComPost: Bool {
// year, month, day, slug
let components = pathComponents.filter({ $0 != "/" })
return components.count == 4 && isHostedAtWPCom
}
var isWPComEmoji: Bool {
absoluteString.contains(".wp.com/i/emojis")
}
/// Handle the common link protocols.
/// - tel: open a prompt to call the phone number
/// - sms: compose new message in iMessage app
/// - mailto: compose new email in Mail app
///
var isLinkProtocol: Bool {
guard let urlScheme = scheme else {
return false
}
let linkProtocols = ["tel", "sms", "mailto"]
if linkProtocols.contains(urlScheme) && UIApplication.shared.canOpenURL(self) {
return true
}
return false
}
/// Does a quick test to see if 2 urls are equal to each other by
/// using just the hosts and paths. This ignores any query items, or hashes
/// on the urls
func isHostAndPathEqual(to url: URL) -> Bool {
guard
let components1 = URLComponents(url: self, resolvingAgainstBaseURL: true),
let components2 = URLComponents(url: url, resolvingAgainstBaseURL: true)
else {
return false
}
let check1 = (components1.host ?? "") + components1.path
let check2 = (components2.host ?? "") + components2.path
return check1 == check2
}
}
extension NSURL {
@objc var isVideo: Bool {
return (self as URL).isVideo
}
@objc var fileSize: NSNumber? {
guard let fileSize = (self as URL).fileSize else {
return nil
}
return NSNumber(value: fileSize)
}
}
extension URL {
/// Appends query items to the URL.
/// - Parameter newQueryItems: The new query items to add to the URL. These will **not** overwrite any existing items but are appended to the existing list.
/// - Returns: The URL with added query items.
func appendingQueryItems(_ newQueryItems: [URLQueryItem]) -> URL {
var components = URLComponents(url: self, resolvingAgainstBaseURL: false)
var queryItems = components?.queryItems ?? []
queryItems.append(contentsOf: newQueryItems)
components?.queryItems = queryItems
return components?.url ?? self
}
}