├── urlprotocol-example
├── Assets.xcassets
│ ├── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── FoundationExt.swift
├── AppDelegate.swift
├── ImageCell.swift
├── MapURLBuilder.swift
├── ImageURLCache.swift
├── Info.plist
├── Base.lproj
│ └── LaunchScreen.storyboard
├── URLImageView.swift
├── ViewController.swift
├── SampleData.swift
├── MapURLProtocol.swift
└── MapKitExt.swift
└── urlprotocol-example.xcodeproj
├── project.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
└── project.pbxproj
/urlprotocol-example/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/urlprotocol-example.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/urlprotocol-example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/urlprotocol-example/FoundationExt.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension Thread {
4 | func dispatch(block: @escaping () -> Void) {
5 | if Thread.current == self {
6 | block()
7 | } else {
8 | perform(#selector(execute(block:)), on: self, with: block, waitUntilDone: false)
9 | }
10 | }
11 |
12 | @objc func execute(block: () -> Void) {
13 | block()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/urlprotocol-example/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | @UIApplicationMain
4 | class AppDelegate: UIResponder, UIApplicationDelegate {
5 | let window = UIWindow(frame: UIScreen.main.bounds)
6 |
7 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
8 | let controller = ViewController(data: sampleMapsData)
9 | controller.title = "Maps"
10 | let navController = UINavigationController(rootViewController: controller)
11 |
12 | window.rootViewController = navController
13 | window.makeKeyAndVisible()
14 |
15 | return true
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/urlprotocol-example/ImageCell.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | class ImageCell: UICollectionViewCell {
4 | let urlImageView: URLImageView
5 |
6 | override init(frame: CGRect) {
7 | urlImageView = URLImageView(frame: frame)
8 |
9 | super.init(frame: frame)
10 |
11 | addSubview(urlImageView)
12 | }
13 |
14 | required init?(coder aDecoder: NSCoder) { fatalError() }
15 |
16 | override func layoutSubviews() {
17 | super.layoutSubviews()
18 |
19 | urlImageView.frame = bounds
20 | }
21 |
22 | override func prepareForReuse() {
23 | super.prepareForReuse()
24 |
25 | urlImageView.prepareForReuse()
26 | }
27 |
28 | func render(url: String) {
29 | urlImageView.render(url: url)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/urlprotocol-example/MapURLBuilder.swift:
--------------------------------------------------------------------------------
1 | import MapKit
2 |
3 | class MapURLBuilder {
4 | func buildURL(latitude: CLLocationDegrees, longitude: CLLocationDegrees, size: CGSize) -> URL {
5 | func item(_ key: MKMapSnapshotter.Options.Key, _ value: String) -> URLQueryItem {
6 | return URLQueryItem(name: key.rawValue, value: value)
7 | }
8 |
9 | var components = URLComponents()
10 | components.scheme = "map"
11 | components.queryItems = [
12 | item(.width, String(describing: size.width)),
13 | item(.height, String(describing: size.height)),
14 | item(.latitude, String(latitude)),
15 | item(.longitude, String(longitude)),
16 | item(.latitudeDelta, String(0.003)),
17 | item(.longitudeDelta, String(0.003)),
18 | item(.scale, String(describing: UIScreen.main.scale))
19 | ]
20 |
21 | return components.url!
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/urlprotocol-example/ImageURLCache.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | class ImageURLCache: URLCache {
4 | static var current = ImageURLCache()
5 |
6 | override init() {
7 | let MB = 1024 * 1024
8 | super.init(
9 | memoryCapacity: 2 * MB,
10 | diskCapacity: 100 * MB,
11 | diskPath: "imageCache"
12 | )
13 | }
14 |
15 | private static let accessQueue = DispatchQueue(
16 | label: "image-urlcache-access"
17 | )
18 |
19 | public override func cachedResponse(for request: URLRequest) -> CachedURLResponse? {
20 | return ImageURLCache.accessQueue.sync {
21 | return super.cachedResponse(for: request)
22 | }
23 | }
24 |
25 | public override func storeCachedResponse(_ response: CachedURLResponse, for request: URLRequest) {
26 | ImageURLCache.accessQueue.sync {
27 | super.storeCachedResponse(response, for: request)
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/urlprotocol-example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIRequiredDeviceCapabilities
26 |
27 | armv7
28 |
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 |
33 | UISupportedInterfaceOrientations~ipad
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationPortraitUpsideDown
37 | UIInterfaceOrientationLandscapeLeft
38 | UIInterfaceOrientationLandscapeRight
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/urlprotocol-example/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/urlprotocol-example/URLImageView.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | fileprivate let config: URLSessionConfiguration = {
4 | let c = URLSessionConfiguration.ephemeral
5 | c.urlCache = ImageURLCache.current
6 | c.protocolClasses = [
7 | MapURLProtocol.self
8 | ]
9 | return c
10 | }()
11 |
12 | fileprivate let session = URLSession(
13 | configuration: config,
14 | delegate: nil,
15 | delegateQueue: nil
16 | )
17 |
18 | class URLImageView: UIImageView, URLSessionDataDelegate {
19 | var task: URLSessionDataTask? = nil
20 | var taskId: Int? = nil
21 |
22 | func prepareForReuse() {
23 | task?.cancel()
24 | taskId = nil
25 | image = nil
26 | }
27 |
28 | private func complete(taskId: Int?, data: Data?, response: URLResponse?, error: Error?) {
29 | if self.taskId == taskId,
30 | let data = data,
31 | let image = UIImage(data: data, scale: UIScreen.main.scale) {
32 | didLoadRemote(image: image)
33 | }
34 | }
35 |
36 | func didLoadRemote(image: UIImage) {
37 | DispatchQueue.main.async {
38 | self.image = image
39 | }
40 | }
41 |
42 | func render(url: String) {
43 | assert(task == nil || task?.taskIdentifier != taskId)
44 | if let url = URL(string: url) {
45 | var id: Int? = nil
46 |
47 | let request = URLRequest(
48 | url: url,
49 | cachePolicy: .returnCacheDataElseLoad,
50 | timeoutInterval: 30
51 | )
52 |
53 | task = session.dataTask(with: request, completionHandler: { [weak self] data, response, error in
54 | self?.complete(taskId: id, data: data, response: response, error: error)
55 | })
56 |
57 | id = task?.taskIdentifier
58 | taskId = id
59 | task?.resume()
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/urlprotocol-example/ViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | class ViewController: UICollectionViewController {
4 | let data: [String]
5 | let cellId = "cell"
6 |
7 | init(data: [String]) {
8 | let inset: CGFloat = 8
9 | let rowCount = 3
10 | let itemSize = Int((UIScreen.main.bounds.width - inset * CGFloat(rowCount + 1)) / CGFloat(rowCount))
11 |
12 | let layout = UICollectionViewFlowLayout()
13 | layout.sectionInset = UIEdgeInsets(top: 0, left: inset, bottom: 0, right: inset)
14 | layout.minimumLineSpacing = inset
15 | layout.minimumInteritemSpacing = 0
16 | layout.itemSize = CGSize(width: itemSize, height: itemSize)
17 |
18 | self.data = data
19 |
20 | super.init(collectionViewLayout: layout)
21 | }
22 |
23 | required public init?(coder aDecoder: NSCoder) { fatalError() }
24 |
25 | override var preferredStatusBarStyle: UIStatusBarStyle {
26 | return .lightContent
27 | }
28 |
29 | override public func loadView() {
30 | super.loadView()
31 |
32 | view.backgroundColor = .white
33 |
34 | if let collectionView = collectionView {
35 | collectionView.backgroundColor = .clear
36 | collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
37 | collectionView.register(ImageCell.self, forCellWithReuseIdentifier: cellId)
38 | }
39 | }
40 |
41 | override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
42 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! ImageCell
43 | cell.render(url: data[indexPath.row])
44 | return cell
45 | }
46 |
47 | override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
48 | return data.count
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/urlprotocol-example/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/urlprotocol-example/SampleData.swift:
--------------------------------------------------------------------------------
1 | import MapKit
2 |
3 | fileprivate let builder = MapURLBuilder()
4 |
5 | fileprivate func buildURL(_ latitude: CLLocationDegrees, _ longitude: CLLocationDegrees) -> String {
6 | return builder.buildURL(latitude: latitude, longitude: longitude, size: CGSize(width: 100, height: 100)).absoluteString
7 | }
8 |
9 | let sampleMapsData: [String] = [
10 | buildURL(59.9396975, 29.5303037),
11 | buildURL(55.5815244, 36.8251256),
12 | buildURL(41.9102415, 12.395914),
13 | buildURL(51.5287718, -0.2416813),
14 | buildURL(52.354775, 4.7585405),
15 | buildURL(48.8589507, 2.2770202),
16 | buildURL(40.6976701, -74.2598656),
17 | buildURL(37.757815, -122.50764),
18 | buildURL(41.8339037, -87.8720478),
19 | buildURL(59.9394953, 30.3164115),
20 | buildURL(59.9396975, 29.5303037),
21 | buildURL(55.5815244, 36.8251256),
22 | buildURL(41.9102415, 12.395914),
23 | buildURL(51.5287718, -0.2416813),
24 | buildURL(52.354775, 4.7585405),
25 | buildURL(48.8589507, 2.2770202),
26 | buildURL(40.6976701, -74.2598656),
27 | buildURL(37.757815, -122.50764),
28 | buildURL(41.8339037, -87.8720478),
29 | buildURL(59.9394953, 30.3164115),
30 | buildURL(59.9396975, 29.5303037),
31 | buildURL(55.5815244, 36.8251256),
32 | buildURL(41.9102415, 12.395914),
33 | buildURL(51.5287718, -0.2416813),
34 | buildURL(52.354775, 4.7585405),
35 | buildURL(48.8589507, 2.2770202),
36 | buildURL(40.6976701, -74.2598656),
37 | buildURL(37.757815, -122.50764),
38 | buildURL(41.8339037, -87.8720478),
39 | buildURL(59.9394953, 30.3164115),
40 | buildURL(59.9396975, 29.5303037),
41 | buildURL(55.5815244, 36.8251256),
42 | buildURL(41.9102415, 12.395914),
43 | buildURL(51.5287718, -0.2416813),
44 | buildURL(52.354775, 4.7585405),
45 | buildURL(48.8589507, 2.2770202),
46 | buildURL(40.6976701, -74.2598656),
47 | buildURL(37.757815, -122.50764),
48 | buildURL(41.8339037, -87.8720478),
49 | buildURL(59.9394953, 30.3164115),
50 | buildURL(59.9396975, 29.5303037),
51 | buildURL(55.5815244, 36.8251256)
52 | ]
53 |
--------------------------------------------------------------------------------
/urlprotocol-example/MapURLProtocol.swift:
--------------------------------------------------------------------------------
1 | import MapKit
2 |
3 | class MapURLProtocol: URLProtocol {
4 | override class func canInit(with request: URLRequest) -> Bool {
5 | return request.url?.scheme == "map"
6 | }
7 |
8 | override class func canonicalRequest(for request: URLRequest) -> URLRequest {
9 | return request
10 | }
11 |
12 | var thread: Thread!
13 |
14 | override func startLoading() {
15 | guard let url = request.url,
16 | let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
17 | let queryItems = components.queryItems else {
18 | fail(with: .badURL)
19 | return
20 | }
21 |
22 | thread = Thread.current
23 |
24 | if let cachedResponse = cachedResponse {
25 | complete(with: cachedResponse)
26 | } else {
27 | load(with: queryItems)
28 | }
29 | }
30 |
31 | override func stopLoading() {
32 | isCancelled = true
33 | }
34 |
35 | var isCancelled: Bool = false
36 |
37 | func load(with queryItems: [URLQueryItem]) {
38 | let snapshotter = MKMapSnapshotter(queryItems: queryItems)
39 | snapshotter.start(
40 | with: DispatchQueue.global(qos: .background),
41 | completionHandler: handle
42 | )
43 | }
44 |
45 | func handle(snapshot: MKMapSnapshotter.Snapshot?, error: Error?) {
46 | thread.execute {
47 | if let snapshot = snapshot,
48 | let data = snapshot.image.jpegData(compressionQuality: 0.7) {
49 | self.complete(with: data)
50 | } else if let error = error {
51 | self.fail(with: error)
52 | }
53 | }
54 | }
55 |
56 | func complete(with cachedResponse: CachedURLResponse) {
57 | complete(with: cachedResponse.data)
58 | }
59 |
60 | func complete(with data: Data) {
61 | guard let url = request.url, let client = client else {
62 | return
63 | }
64 |
65 | let response = URLResponse(
66 | url: url,
67 | mimeType: "image/jpeg",
68 | expectedContentLength: data.count,
69 | textEncodingName: nil
70 | )
71 |
72 | client.urlProtocol(self, didReceive: response, cacheStoragePolicy: .allowed)
73 | client.urlProtocol(self, didLoad: data)
74 | client.urlProtocolDidFinishLoading(self)
75 | }
76 |
77 | func fail(with errorCode: URLError.Code) {
78 | let error = URLError(errorCode, userInfo: [:])
79 | fail(with: error)
80 | }
81 |
82 | func fail(with error: Error) {
83 | client?.urlProtocol(self, didFailWithError: error)
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/urlprotocol-example/MapKitExt.swift:
--------------------------------------------------------------------------------
1 | import MapKit
2 |
3 | extension MKMapSnapshotter.Options {
4 | public enum Key: String {
5 | case width = "width"
6 | case height = "height"
7 | case latitude = "latitude"
8 | case longitude = "longitude"
9 | case latitudeDelta = "latitude_delta"
10 | case longitudeDelta = "longitude_delta"
11 | case scale = "scale"
12 | }
13 | }
14 |
15 | extension MKMapSnapshotter.Options {
16 | public convenience init(items: [(Key, String?)]) {
17 | self.init()
18 |
19 | var width: CGFloat?
20 | var height: CGFloat?
21 | var lat: CLLocationDegrees?
22 | var lng: CLLocationDegrees?
23 | var latDelta: CLLocationDegrees?
24 | var lngDelta: CLLocationDegrees?
25 | var mapScale: CGFloat?
26 |
27 | func float(_ string: NSString) -> CGFloat {
28 | return CGFloat(string.floatValue)
29 | }
30 |
31 | for item in items {
32 | if let value = item.1 {
33 | let valueStr = NSString(string: value)
34 | switch item.0 {
35 | case .width:
36 | width = float(valueStr)
37 | case .height:
38 | height = float(valueStr)
39 | case .latitude:
40 | lat = valueStr.doubleValue
41 | case .longitude:
42 | lng = valueStr.doubleValue
43 | case .latitudeDelta:
44 | latDelta = valueStr.doubleValue
45 | case .longitudeDelta:
46 | lngDelta = valueStr.doubleValue
47 | case .scale:
48 | mapScale = float(valueStr)
49 | }
50 | }
51 | }
52 |
53 | if let width = width, let height = height {
54 | size = CGSize(width: width, height: height)
55 | }
56 |
57 | if let lat = lat, let lng = lng, let latDelta = latDelta, let lngDelta = lngDelta {
58 | let center = CLLocationCoordinate2D(latitude: lat, longitude: lng)
59 | let span = MKCoordinateSpan(latitudeDelta: latDelta, longitudeDelta: lngDelta)
60 | region = MKCoordinateRegion(center: center, span: span)
61 | }
62 |
63 | if let mapScale = mapScale {
64 | scale = mapScale
65 | }
66 |
67 | mapType = .hybrid
68 | }
69 | }
70 |
71 | extension MKMapSnapshotter {
72 | convenience init(queryItems: [URLQueryItem]) {
73 | self.init(options: MKMapSnapshotter.options(from: queryItems))
74 | }
75 |
76 | static func options(from queryItems: [URLQueryItem]) -> MKMapSnapshotter.Options {
77 | return MKMapSnapshotter.Options(items: queryItems.compactMap({
78 | if let key = MKMapSnapshotter.Options.Key(rawValue: $0.name) {
79 | return (key, $0.value)
80 | } else {
81 | return nil
82 | }
83 | }))
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/urlprotocol-example.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 21995171232FCB21003B8955 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21995170232FCB21003B8955 /* AppDelegate.swift */; };
11 | 21995175232FCB21003B8955 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21995174232FCB21003B8955 /* ViewController.swift */; };
12 | 2199517A232FCB22003B8955 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 21995179232FCB22003B8955 /* Assets.xcassets */; };
13 | 2199517D232FCB22003B8955 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2199517B232FCB22003B8955 /* LaunchScreen.storyboard */; };
14 | 21995185232FCBA7003B8955 /* MapURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21995184232FCBA7003B8955 /* MapURLProtocol.swift */; };
15 | 21995187232FCBB7003B8955 /* MapKitExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21995186232FCBB7003B8955 /* MapKitExt.swift */; };
16 | 21995189232FCBD2003B8955 /* FoundationExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21995188232FCBD2003B8955 /* FoundationExt.swift */; };
17 | 2199518B232FCC30003B8955 /* MapURLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2199518A232FCC30003B8955 /* MapURLBuilder.swift */; };
18 | 2199518D232FCC54003B8955 /* SampleData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2199518C232FCC54003B8955 /* SampleData.swift */; };
19 | 2199518F232FCC88003B8955 /* URLImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2199518E232FCC88003B8955 /* URLImageView.swift */; };
20 | 21995191232FCCA3003B8955 /* ImageURLCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21995190232FCCA3003B8955 /* ImageURLCache.swift */; };
21 | 21995193232FCCEA003B8955 /* ImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21995192232FCCEA003B8955 /* ImageCell.swift */; };
22 | /* End PBXBuildFile section */
23 |
24 | /* Begin PBXFileReference section */
25 | 2199516D232FCB21003B8955 /* urlprotocol-example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "urlprotocol-example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
26 | 21995170232FCB21003B8955 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
27 | 21995174232FCB21003B8955 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
28 | 21995179232FCB22003B8955 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
29 | 2199517C232FCB22003B8955 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
30 | 2199517E232FCB22003B8955 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
31 | 21995184232FCBA7003B8955 /* MapURLProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapURLProtocol.swift; sourceTree = ""; };
32 | 21995186232FCBB7003B8955 /* MapKitExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapKitExt.swift; sourceTree = ""; };
33 | 21995188232FCBD2003B8955 /* FoundationExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FoundationExt.swift; sourceTree = ""; };
34 | 2199518A232FCC30003B8955 /* MapURLBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapURLBuilder.swift; sourceTree = ""; };
35 | 2199518C232FCC54003B8955 /* SampleData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleData.swift; sourceTree = ""; };
36 | 2199518E232FCC88003B8955 /* URLImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLImageView.swift; sourceTree = ""; };
37 | 21995190232FCCA3003B8955 /* ImageURLCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageURLCache.swift; sourceTree = ""; };
38 | 21995192232FCCEA003B8955 /* ImageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCell.swift; sourceTree = ""; };
39 | /* End PBXFileReference section */
40 |
41 | /* Begin PBXFrameworksBuildPhase section */
42 | 2199516A232FCB21003B8955 /* Frameworks */ = {
43 | isa = PBXFrameworksBuildPhase;
44 | buildActionMask = 2147483647;
45 | files = (
46 | );
47 | runOnlyForDeploymentPostprocessing = 0;
48 | };
49 | /* End PBXFrameworksBuildPhase section */
50 |
51 | /* Begin PBXGroup section */
52 | 21995164232FCB21003B8955 = {
53 | isa = PBXGroup;
54 | children = (
55 | 2199516F232FCB21003B8955 /* urlprotocol-example */,
56 | 2199516E232FCB21003B8955 /* Products */,
57 | );
58 | sourceTree = "";
59 | };
60 | 2199516E232FCB21003B8955 /* Products */ = {
61 | isa = PBXGroup;
62 | children = (
63 | 2199516D232FCB21003B8955 /* urlprotocol-example.app */,
64 | );
65 | name = Products;
66 | sourceTree = "";
67 | };
68 | 2199516F232FCB21003B8955 /* urlprotocol-example */ = {
69 | isa = PBXGroup;
70 | children = (
71 | 21995170232FCB21003B8955 /* AppDelegate.swift */,
72 | 21995174232FCB21003B8955 /* ViewController.swift */,
73 | 2199517B232FCB22003B8955 /* LaunchScreen.storyboard */,
74 | 21995184232FCBA7003B8955 /* MapURLProtocol.swift */,
75 | 21995186232FCBB7003B8955 /* MapKitExt.swift */,
76 | 21995188232FCBD2003B8955 /* FoundationExt.swift */,
77 | 2199518A232FCC30003B8955 /* MapURLBuilder.swift */,
78 | 2199518C232FCC54003B8955 /* SampleData.swift */,
79 | 2199518E232FCC88003B8955 /* URLImageView.swift */,
80 | 21995190232FCCA3003B8955 /* ImageURLCache.swift */,
81 | 21995192232FCCEA003B8955 /* ImageCell.swift */,
82 | 21995179232FCB22003B8955 /* Assets.xcassets */,
83 | 2199517E232FCB22003B8955 /* Info.plist */,
84 | );
85 | path = "urlprotocol-example";
86 | sourceTree = "";
87 | };
88 | /* End PBXGroup section */
89 |
90 | /* Begin PBXNativeTarget section */
91 | 2199516C232FCB21003B8955 /* urlprotocol-example */ = {
92 | isa = PBXNativeTarget;
93 | buildConfigurationList = 21995181232FCB22003B8955 /* Build configuration list for PBXNativeTarget "urlprotocol-example" */;
94 | buildPhases = (
95 | 21995169232FCB21003B8955 /* Sources */,
96 | 2199516A232FCB21003B8955 /* Frameworks */,
97 | 2199516B232FCB21003B8955 /* Resources */,
98 | );
99 | buildRules = (
100 | );
101 | dependencies = (
102 | );
103 | name = "urlprotocol-example";
104 | productName = "urlprotocol-example";
105 | productReference = 2199516D232FCB21003B8955 /* urlprotocol-example.app */;
106 | productType = "com.apple.product-type.application";
107 | };
108 | /* End PBXNativeTarget section */
109 |
110 | /* Begin PBXProject section */
111 | 21995165232FCB21003B8955 /* Project object */ = {
112 | isa = PBXProject;
113 | attributes = {
114 | LastSwiftUpdateCheck = 1100;
115 | LastUpgradeCheck = 1100;
116 | ORGANIZATIONNAME = VK;
117 | TargetAttributes = {
118 | 2199516C232FCB21003B8955 = {
119 | CreatedOnToolsVersion = 11.0;
120 | };
121 | };
122 | };
123 | buildConfigurationList = 21995168232FCB21003B8955 /* Build configuration list for PBXProject "urlprotocol-example" */;
124 | compatibilityVersion = "Xcode 9.3";
125 | developmentRegion = en;
126 | hasScannedForEncodings = 0;
127 | knownRegions = (
128 | en,
129 | Base,
130 | );
131 | mainGroup = 21995164232FCB21003B8955;
132 | productRefGroup = 2199516E232FCB21003B8955 /* Products */;
133 | projectDirPath = "";
134 | projectRoot = "";
135 | targets = (
136 | 2199516C232FCB21003B8955 /* urlprotocol-example */,
137 | );
138 | };
139 | /* End PBXProject section */
140 |
141 | /* Begin PBXResourcesBuildPhase section */
142 | 2199516B232FCB21003B8955 /* Resources */ = {
143 | isa = PBXResourcesBuildPhase;
144 | buildActionMask = 2147483647;
145 | files = (
146 | 2199517D232FCB22003B8955 /* LaunchScreen.storyboard in Resources */,
147 | 2199517A232FCB22003B8955 /* Assets.xcassets in Resources */,
148 | );
149 | runOnlyForDeploymentPostprocessing = 0;
150 | };
151 | /* End PBXResourcesBuildPhase section */
152 |
153 | /* Begin PBXSourcesBuildPhase section */
154 | 21995169232FCB21003B8955 /* Sources */ = {
155 | isa = PBXSourcesBuildPhase;
156 | buildActionMask = 2147483647;
157 | files = (
158 | 21995185232FCBA7003B8955 /* MapURLProtocol.swift in Sources */,
159 | 2199518D232FCC54003B8955 /* SampleData.swift in Sources */,
160 | 21995187232FCBB7003B8955 /* MapKitExt.swift in Sources */,
161 | 2199518B232FCC30003B8955 /* MapURLBuilder.swift in Sources */,
162 | 21995175232FCB21003B8955 /* ViewController.swift in Sources */,
163 | 21995191232FCCA3003B8955 /* ImageURLCache.swift in Sources */,
164 | 21995171232FCB21003B8955 /* AppDelegate.swift in Sources */,
165 | 2199518F232FCC88003B8955 /* URLImageView.swift in Sources */,
166 | 21995189232FCBD2003B8955 /* FoundationExt.swift in Sources */,
167 | 21995193232FCCEA003B8955 /* ImageCell.swift in Sources */,
168 | );
169 | runOnlyForDeploymentPostprocessing = 0;
170 | };
171 | /* End PBXSourcesBuildPhase section */
172 |
173 | /* Begin PBXVariantGroup section */
174 | 2199517B232FCB22003B8955 /* LaunchScreen.storyboard */ = {
175 | isa = PBXVariantGroup;
176 | children = (
177 | 2199517C232FCB22003B8955 /* Base */,
178 | );
179 | name = LaunchScreen.storyboard;
180 | sourceTree = "";
181 | };
182 | /* End PBXVariantGroup section */
183 |
184 | /* Begin XCBuildConfiguration section */
185 | 2199517F232FCB22003B8955 /* Debug */ = {
186 | isa = XCBuildConfiguration;
187 | buildSettings = {
188 | ALWAYS_SEARCH_USER_PATHS = NO;
189 | CLANG_ANALYZER_NONNULL = YES;
190 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
191 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
192 | CLANG_CXX_LIBRARY = "libc++";
193 | CLANG_ENABLE_MODULES = YES;
194 | CLANG_ENABLE_OBJC_ARC = YES;
195 | CLANG_ENABLE_OBJC_WEAK = YES;
196 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
197 | CLANG_WARN_BOOL_CONVERSION = YES;
198 | CLANG_WARN_COMMA = YES;
199 | CLANG_WARN_CONSTANT_CONVERSION = YES;
200 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
201 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
202 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
203 | CLANG_WARN_EMPTY_BODY = YES;
204 | CLANG_WARN_ENUM_CONVERSION = YES;
205 | CLANG_WARN_INFINITE_RECURSION = YES;
206 | CLANG_WARN_INT_CONVERSION = YES;
207 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
208 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
209 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
210 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
211 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
212 | CLANG_WARN_STRICT_PROTOTYPES = YES;
213 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
214 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
215 | CLANG_WARN_UNREACHABLE_CODE = YES;
216 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
217 | COPY_PHASE_STRIP = NO;
218 | DEBUG_INFORMATION_FORMAT = dwarf;
219 | ENABLE_STRICT_OBJC_MSGSEND = YES;
220 | ENABLE_TESTABILITY = YES;
221 | GCC_C_LANGUAGE_STANDARD = gnu11;
222 | GCC_DYNAMIC_NO_PIC = NO;
223 | GCC_NO_COMMON_BLOCKS = YES;
224 | GCC_OPTIMIZATION_LEVEL = 0;
225 | GCC_PREPROCESSOR_DEFINITIONS = (
226 | "DEBUG=1",
227 | "$(inherited)",
228 | );
229 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
230 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
231 | GCC_WARN_UNDECLARED_SELECTOR = YES;
232 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
233 | GCC_WARN_UNUSED_FUNCTION = YES;
234 | GCC_WARN_UNUSED_VARIABLE = YES;
235 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
236 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
237 | MTL_FAST_MATH = YES;
238 | ONLY_ACTIVE_ARCH = YES;
239 | SDKROOT = iphoneos;
240 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
241 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
242 | };
243 | name = Debug;
244 | };
245 | 21995180232FCB22003B8955 /* Release */ = {
246 | isa = XCBuildConfiguration;
247 | buildSettings = {
248 | ALWAYS_SEARCH_USER_PATHS = NO;
249 | CLANG_ANALYZER_NONNULL = YES;
250 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
251 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
252 | CLANG_CXX_LIBRARY = "libc++";
253 | CLANG_ENABLE_MODULES = YES;
254 | CLANG_ENABLE_OBJC_ARC = YES;
255 | CLANG_ENABLE_OBJC_WEAK = YES;
256 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
257 | CLANG_WARN_BOOL_CONVERSION = YES;
258 | CLANG_WARN_COMMA = YES;
259 | CLANG_WARN_CONSTANT_CONVERSION = YES;
260 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
261 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
262 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
263 | CLANG_WARN_EMPTY_BODY = YES;
264 | CLANG_WARN_ENUM_CONVERSION = YES;
265 | CLANG_WARN_INFINITE_RECURSION = YES;
266 | CLANG_WARN_INT_CONVERSION = YES;
267 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
268 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
269 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
270 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
271 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
272 | CLANG_WARN_STRICT_PROTOTYPES = YES;
273 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
274 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
275 | CLANG_WARN_UNREACHABLE_CODE = YES;
276 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
277 | COPY_PHASE_STRIP = NO;
278 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
279 | ENABLE_NS_ASSERTIONS = NO;
280 | ENABLE_STRICT_OBJC_MSGSEND = YES;
281 | GCC_C_LANGUAGE_STANDARD = gnu11;
282 | GCC_NO_COMMON_BLOCKS = YES;
283 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
284 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
285 | GCC_WARN_UNDECLARED_SELECTOR = YES;
286 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
287 | GCC_WARN_UNUSED_FUNCTION = YES;
288 | GCC_WARN_UNUSED_VARIABLE = YES;
289 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
290 | MTL_ENABLE_DEBUG_INFO = NO;
291 | MTL_FAST_MATH = YES;
292 | SDKROOT = iphoneos;
293 | SWIFT_COMPILATION_MODE = wholemodule;
294 | SWIFT_OPTIMIZATION_LEVEL = "-O";
295 | VALIDATE_PRODUCT = YES;
296 | };
297 | name = Release;
298 | };
299 | 21995182232FCB22003B8955 /* Debug */ = {
300 | isa = XCBuildConfiguration;
301 | buildSettings = {
302 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
303 | CODE_SIGN_STYLE = Manual;
304 | DEVELOPMENT_TEAM = "";
305 | INFOPLIST_FILE = "urlprotocol-example/Info.plist";
306 | IPHONEOS_DEPLOYMENT_TARGET = 9.3;
307 | LD_RUNPATH_SEARCH_PATHS = (
308 | "$(inherited)",
309 | "@executable_path/Frameworks",
310 | );
311 | PRODUCT_BUNDLE_IDENTIFIER = "com.vk.urlprotocol-example";
312 | PRODUCT_NAME = "$(TARGET_NAME)";
313 | PROVISIONING_PROFILE_SPECIFIER = "";
314 | SWIFT_VERSION = 5.0;
315 | TARGETED_DEVICE_FAMILY = 1;
316 | };
317 | name = Debug;
318 | };
319 | 21995183232FCB22003B8955 /* Release */ = {
320 | isa = XCBuildConfiguration;
321 | buildSettings = {
322 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
323 | CODE_SIGN_STYLE = Manual;
324 | DEVELOPMENT_TEAM = "";
325 | INFOPLIST_FILE = "urlprotocol-example/Info.plist";
326 | IPHONEOS_DEPLOYMENT_TARGET = 9.3;
327 | LD_RUNPATH_SEARCH_PATHS = (
328 | "$(inherited)",
329 | "@executable_path/Frameworks",
330 | );
331 | PRODUCT_BUNDLE_IDENTIFIER = "com.vk.urlprotocol-example";
332 | PRODUCT_NAME = "$(TARGET_NAME)";
333 | PROVISIONING_PROFILE_SPECIFIER = "";
334 | SWIFT_VERSION = 5.0;
335 | TARGETED_DEVICE_FAMILY = 1;
336 | };
337 | name = Release;
338 | };
339 | /* End XCBuildConfiguration section */
340 |
341 | /* Begin XCConfigurationList section */
342 | 21995168232FCB21003B8955 /* Build configuration list for PBXProject "urlprotocol-example" */ = {
343 | isa = XCConfigurationList;
344 | buildConfigurations = (
345 | 2199517F232FCB22003B8955 /* Debug */,
346 | 21995180232FCB22003B8955 /* Release */,
347 | );
348 | defaultConfigurationIsVisible = 0;
349 | defaultConfigurationName = Release;
350 | };
351 | 21995181232FCB22003B8955 /* Build configuration list for PBXNativeTarget "urlprotocol-example" */ = {
352 | isa = XCConfigurationList;
353 | buildConfigurations = (
354 | 21995182232FCB22003B8955 /* Debug */,
355 | 21995183232FCB22003B8955 /* Release */,
356 | );
357 | defaultConfigurationIsVisible = 0;
358 | defaultConfigurationName = Release;
359 | };
360 | /* End XCConfigurationList section */
361 | };
362 | rootObject = 21995165232FCB21003B8955 /* Project object */;
363 | }
364 |
--------------------------------------------------------------------------------