diff --git a/.gitignore b/.gitignore index c5f71c457..f9d5fdaeb 100644 --- a/.gitignore +++ b/.gitignore @@ -74,12 +74,6 @@ fastlane/Preview.html fastlane/screenshots/**/*.png fastlane/test_output -# Code Injection -# After new code Injection tools there's a generated folder /iOSInjectionProject -# https://github.com/johnno1962/injectionforxcode - -iOSInjectionProject/ - # End of https://www.gitignore.io/api/swift .DS_Store @@ -92,4 +86,3 @@ Products/ /output/ /node_modules /muter_logs -Sources/Demo/offline diff --git a/CHANGELOG.md b/CHANGELOG.md index fc4679a82..237f1a686 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ Guide: https://keepachangelog.com/en/1.0.0/ +- [Demo] Add OfflineDemoViewController to MapboxSearch.xcodeproj > Demo application. +- [Demo] Remove support for `--offline` launch argument. + - [SearchResult] Add support for `mapboxId` field when availalbe. - [FavoriteRecord] Add support for `mapboxId` field when availalbe. - [HistoryRecord] Add support for `mapboxId` field when availalbe. diff --git a/MapboxSearch.xcodeproj/project.pbxproj b/MapboxSearch.xcodeproj/project.pbxproj index 1f3f568b7..1097ff58b 100644 --- a/MapboxSearch.xcodeproj/project.pbxproj +++ b/MapboxSearch.xcodeproj/project.pbxproj @@ -48,6 +48,7 @@ 04C0848D2B4C82F3002F9C69 /* SdkInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04C0848C2B4C82F3002F9C69 /* SdkInformation.swift */; }; 04C127552B62F6BC00884325 /* ApiType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04C127542B62F6BC00884325 /* ApiType.swift */; }; 04C127582B62FFDB00884325 /* ApiType+Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04C127572B62FFDB00884325 /* ApiType+Core.swift */; }; + 04DAF7582BDAAB4C002719E2 /* OfflineDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DAF7572BDAAB4C002719E2 /* OfflineDemoViewController.swift */; }; 04DFB40C2B8CF1ED00231830 /* search-box-suggestions-categories.json in Resources */ = {isa = PBXBuildFile; fileRef = 04DFB40B2B8CF1ED00231830 /* search-box-suggestions-categories.json */; }; 04DFB40D2B8CF1ED00231830 /* search-box-suggestions-categories.json in Resources */ = {isa = PBXBuildFile; fileRef = 04DFB40B2B8CF1ED00231830 /* search-box-suggestions-categories.json */; }; 04DFB40E2B8CF1ED00231830 /* search-box-suggestions-categories.json in Resources */ = {isa = PBXBuildFile; fileRef = 04DFB40B2B8CF1ED00231830 /* search-box-suggestions-categories.json */; }; @@ -551,6 +552,7 @@ 04C0848C2B4C82F3002F9C69 /* SdkInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SdkInformation.swift; sourceTree = ""; }; 04C127542B62F6BC00884325 /* ApiType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiType.swift; sourceTree = ""; }; 04C127572B62FFDB00884325 /* ApiType+Core.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ApiType+Core.swift"; sourceTree = ""; }; + 04DAF7572BDAAB4C002719E2 /* OfflineDemoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfflineDemoViewController.swift; sourceTree = ""; }; 04DFB40B2B8CF1ED00231830 /* search-box-suggestions-categories.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "search-box-suggestions-categories.json"; sourceTree = ""; }; 04DFB40F2B8CF46D00231830 /* search-box-retrieve-categories.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "search-box-retrieve-categories.json"; sourceTree = ""; }; 04DFB4132B8CFD4700231830 /* search-box-suggestions-san-francisco.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "search-box-suggestions-san-francisco.json"; sourceTree = ""; }; @@ -1071,6 +1073,14 @@ path = Engine; sourceTree = ""; }; + 04DAF7562BDAAB38002719E2 /* Offline */ = { + isa = PBXGroup; + children = ( + 04DAF7572BDAAB4C002719E2 /* OfflineDemoViewController.swift */, + ); + path = Offline; + sourceTree = ""; + }; 04E5FF972B48829200DADC18 /* Region */ = { isa = PBXGroup; children = ( @@ -1933,6 +1943,7 @@ FEEDD3B32508E3CD00DC0A98 /* Demo */ = { isa = PBXGroup; children = ( + 04DAF7562BDAAB38002719E2 /* Offline */, 14F7186829A1335F00D5BC2E /* Place Autocomplete */, 14B92D5C298BFD04006003C1 /* Category */, 1440BF4B28FD7591009B3679 /* Address Autofill */, @@ -2855,6 +2866,7 @@ buildActionMask = 2147483647; files = ( 1440BF4F290019AD009B3679 /* AddressAutofillResultViewController.swift in Sources */, + 04DAF7582BDAAB4C002719E2 /* OfflineDemoViewController.swift in Sources */, 1440BF4D28FD75A9009B3679 /* AddressAutofillMainViewController.swift in Sources */, 041DAFD92BCDA45B0071F9EB /* DiscoverViewController.swift in Sources */, FEEDD3C32508E3CD00DC0A98 /* AppDelegate.swift in Sources */, diff --git a/Sources/Demo/AppDelegate.swift b/Sources/Demo/AppDelegate.swift index 15ea8746a..f3265827c 100644 --- a/Sources/Demo/AppDelegate.swift +++ b/Sources/Demo/AppDelegate.swift @@ -14,6 +14,34 @@ class AppDelegate: UIResponder, UIApplicationDelegate { ServiceProvider.shared.localHistoryProvider.deleteAll() } + setUpProgrammaticUI(application: application) + return true } + + /// Set up the fifth tab 'Offline' programmatically. + private func setUpProgrammaticUI(application: UIApplication) { + let tabBarControllers = application.connectedScenes + .compactMap { $0 as? UIWindowScene } + .flatMap(\.windows) + .compactMap { window in + window.backgroundColor = UIColor.systemBackground + return window.rootViewController as? UITabBarController + } + + let offlineDemoViewController = OfflineDemoViewController() + offlineDemoViewController.tabBarItem = UITabBarItem( + title: "Offline", + image: UIImage(systemName: "icloud.and.arrow.down"), + tag: 0 + ) + + for tabBarController in tabBarControllers { + guard var viewControllers = tabBarController.viewControllers else { + break + } + viewControllers.append(offlineDemoViewController) + tabBarController.setViewControllers(viewControllers, animated: false) + } + } } diff --git a/Sources/Demo/MapRootController.swift b/Sources/Demo/MapRootController.swift index 38d191188..b2403a47b 100644 --- a/Sources/Demo/MapRootController.swift +++ b/Sources/Demo/MapRootController.swift @@ -15,10 +15,6 @@ class MapRootController: UIViewController { let panelController = MapboxPanelController(rootViewController: searchController) addChild(panelController) - if ProcessInfo.processInfo.arguments.contains("--offline") { - enableOfflineSearch() - } - // Enabling jp/ja search options for testing Japanese Address Search. // Setting Japanese into the list of preferred languages is a way to activate it. if Locale.preferredLanguages.contains(where: { $0.contains("ja") }) { @@ -26,38 +22,6 @@ class MapRootController: UIViewController { } } - func enableOfflineSearch() { - let engine = searchController.searchEngine - - engine.setOfflineMode(.enabled) { - let descriptor = SearchOfflineManager.createDefaultTilesetDescriptor() - - let dcLocation = NSValue(mkCoordinate: CLLocationCoordinate2D( - latitude: 38.89992081005698, - longitude: -77.03399849939174 - )) - - guard let options = MapboxCommon.TileRegionLoadOptions.build( - geometry: Geometry(point: dcLocation), - descriptors: [descriptor], - acceptExpired: true - ) else { - assertionFailure() - return - } - - _ = engine.offlineManager.tileStore.loadTileRegion(id: "dc", options: options, progress: nil) { result in - switch result { - case .success(let region): - assert(region.id == "dc") - case .failure(let error): - print(error.localizedDescription) - assertionFailure() - } - } - } - } - let locationManager = CLLocationManager() override func viewDidAppear(_ animated: Bool) { @@ -114,18 +78,3 @@ extension MapRootController: SearchControllerDelegate { showAnnotation([annotation], isPOI: true) } } - -extension Style { - static let clown = Style( - primaryTextColor: .white, - primaryBackgroundColor: .red, - secondaryBackgroundColor: .systemBlue, - separatorColor: .systemBlue, - primaryAccentColor: .orange, - primaryInactiveElementColor: .yellow, - panelShadowColor: .green, - panelHandlerColor: .black, - iconTintColor: .cyan, - activeSegmentTitleColor: .black - ) -} diff --git a/Sources/Demo/Offline/OfflineDemoViewController.swift b/Sources/Demo/Offline/OfflineDemoViewController.swift new file mode 100644 index 000000000..b6f3b1b03 --- /dev/null +++ b/Sources/Demo/Offline/OfflineDemoViewController.swift @@ -0,0 +1,145 @@ +import CoreLocation +import MapboxSearch +import MapboxSearchUI +import MapKit +import UIKit + +/// Demonstrate how to use Offline Search in the Demo app +class OfflineDemoViewController: UIViewController { + private var mapView = MKMapView() + private var messageLabel = UILabel() + private lazy var searchController = MapboxSearchController() + + override func viewDidLoad() { + super.viewDidLoad() + + setUpLayout() + + searchController.delegate = self + let panelController = MapboxPanelController(rootViewController: searchController) + addChild(panelController) + + enableOfflineSearch() + + // Enabling jp/ja search options for testing Japanese Address Search. + // Setting Japanese into the list of preferred languages is a way to activate it. + if Locale.preferredLanguages.contains(where: { $0.contains("ja") }) { + searchController.searchOptions = SearchOptions(countries: ["jp"], languages: ["ja"]) + } + } + + func enableOfflineSearch() { + let engine = searchController.searchEngine + + engine.setOfflineMode(.enabled) { + let descriptor = SearchOfflineManager.createDefaultTilesetDescriptor() + + let dcLocation = NSValue(mkCoordinate: CLLocationCoordinate2D( + latitude: 38.89992081005698, + longitude: -77.03399849939174 + )) + + guard let options = MapboxCommon.TileRegionLoadOptions.build( + geometry: Geometry(point: dcLocation), + descriptors: [descriptor], + acceptExpired: true + ) else { + assertionFailure() + return + } + + _ = engine.offlineManager.tileStore.loadTileRegion(id: "dc", options: options, progress: nil) { result in + switch result { + case .success(let region): + assert(region.id == "dc") + case .failure(let error): + print(error.localizedDescription) + assertionFailure() + } + } + } + } + + let locationManager = CLLocationManager() + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + locationManager.requestWhenInUseAuthorization() + } + + private func setUpLayout() { + // Set up the Map and programmatic layout + for subview in [messageLabel, mapView] { + view.addSubview(subview) + subview.translatesAutoresizingMaskIntoConstraints = false + } + + NSLayoutConstraint.activate([ + messageLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + messageLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor), + messageLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor), + + mapView.topAnchor.constraint(equalTo: messageLabel.bottomAnchor), + mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + mapView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), + ]) + + // Set up the help message in the navigation bar + let message = + "Offline search is available as a premium feature.\nContact Mapbox sales team for more information." + + messageLabel.textAlignment = .center + messageLabel.numberOfLines = 0 + messageLabel.text = message + } + + func showAnnotation(_ annotations: [MKAnnotation], isPOI: Bool) { + mapView.removeAnnotations(mapView.annotations) + + guard !annotations.isEmpty else { return } + mapView.addAnnotations(annotations) + + if annotations.count == 1, let annotation = annotations.first { + let delta = isPOI ? 0.005 : 0.5 + let span = MKCoordinateSpan(latitudeDelta: delta, longitudeDelta: delta) + let region = MKCoordinateRegion(center: annotation.coordinate, span: span) + mapView.setRegion(region, animated: true) + } else { + mapView.showAnnotations(annotations, animated: true) + } + } +} + +extension OfflineDemoViewController: SearchControllerDelegate { + func categorySearchResultsReceived(category: SearchCategory, results: [SearchResult]) { + let annotations = results.map { searchResult -> MKPointAnnotation in + let annotation = MKPointAnnotation() + annotation.coordinate = searchResult.coordinate + annotation.title = searchResult.name + annotation.subtitle = searchResult.address?.formattedAddress(style: .medium) + return annotation + } + + showAnnotation(annotations, isPOI: false) + } + + func searchResultSelected(_ searchResult: SearchResult) { + let annotation = MKPointAnnotation() + annotation.coordinate = searchResult.coordinate + annotation.title = searchResult.name + annotation.subtitle = searchResult.address?.formattedAddress(style: .medium) + + showAnnotation([annotation], isPOI: searchResult.type == .POI) + } + + func userFavoriteSelected(_ userFavorite: FavoriteRecord) { + let annotation = MKPointAnnotation() + annotation.coordinate = userFavorite.coordinate + annotation.title = userFavorite.name + annotation.subtitle = userFavorite.address?.formattedAddress(style: .medium) + + showAnnotation([annotation], isPOI: true) + } +}