summaryrefslogtreecommitdiff
path: root/platform/ios/demo/Examples/Swift/RuntimeMultipleAnnotationsExample.swift
diff options
context:
space:
mode:
Diffstat (limited to 'platform/ios/demo/Examples/Swift/RuntimeMultipleAnnotationsExample.swift')
-rw-r--r--platform/ios/demo/Examples/Swift/RuntimeMultipleAnnotationsExample.swift207
1 files changed, 207 insertions, 0 deletions
diff --git a/platform/ios/demo/Examples/Swift/RuntimeMultipleAnnotationsExample.swift b/platform/ios/demo/Examples/Swift/RuntimeMultipleAnnotationsExample.swift
new file mode 100644
index 0000000000..46a244b268
--- /dev/null
+++ b/platform/ios/demo/Examples/Swift/RuntimeMultipleAnnotationsExample.swift
@@ -0,0 +1,207 @@
+import Mapbox
+
+@objc(RuntimeMultipleAnnotationsExample_Swift)
+
+class RuntimeMultipleAnnotationsExample_Swift: UIViewController, MGLMapViewDelegate {
+ var mapView: MGLMapView!
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ let mapView = MGLMapView(frame: view.bounds)
+ mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+
+ mapView.setCenter(CLLocationCoordinate2D(latitude: 37.090240, longitude: -95.712891), zoomLevel: 2, animated: false)
+
+ mapView.delegate = self
+
+ view.addSubview(mapView)
+
+ // Add our own gesture recognizer to handle taps on our custom map features.
+ mapView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleMapTap(sender:))))
+
+ self.mapView = mapView
+ }
+
+ func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
+ fetchPoints() { [weak self] (features) in
+ self?.addItemsToMap(features: features)
+ }
+ }
+
+ func addItemsToMap(features: [MGLPointFeature]) {
+ // MGLMapView.style is optional, so you must guard against it not being set.
+ guard let style = mapView.style else { return }
+
+ // You can add custom UIImages to the map style.
+ // These can be referenced by an MGLSymbolStyleLayer’s iconImage property.
+ style.setImage(UIImage(named: "lighthouse")!, forName: "lighthouse")
+
+ // Add the features to the map as a shape source.
+ let source = MGLShapeSource(identifier: "lighthouses", features: features, options: nil)
+ style.addSource(source)
+
+ let lighthouseColor = UIColor(red: 0.08, green: 0.44, blue: 0.96, alpha: 1.0)
+
+ // Use MGLCircleStyleLayer to represent the points with simple circles.
+ // In this case, we can use style functions to gradually change properties between zoom level 2 and 7: the circle opacity from 50% to 100% and the circle radius from 2pt to 3pt.
+ let circles = MGLCircleStyleLayer(identifier: "lighthouse-circles", source: source)
+ circles.circleColor = MGLStyleValue(rawValue: lighthouseColor)
+ circles.circleOpacity = MGLStyleValue(interpolationMode: .exponential,
+ cameraStops: [2: MGLStyleValue(rawValue: 0.5),
+ 7: MGLStyleValue(rawValue: 1)],
+ options: nil)
+ circles.circleRadius = MGLStyleValue(interpolationMode: .exponential,
+ cameraStops: [2: MGLStyleValue(rawValue: 2),
+ 7: MGLStyleValue(rawValue: 3)],
+ options: nil)
+
+ // Use MGLSymbolStyleLayer for more complex styling of points including custom icons and text rendering.
+ let symbols = MGLSymbolStyleLayer(identifier: "lighthouse-symbols", source: source)
+ symbols.iconImageName = MGLStyleValue(rawValue: "lighthouse")
+ symbols.iconColor = MGLStyleValue(rawValue: lighthouseColor)
+ symbols.iconScale = MGLStyleValue(rawValue: 0.5)
+ symbols.iconOpacity = MGLStyleValue(interpolationMode: .exponential,
+ cameraStops: [5.9: MGLStyleValue(rawValue: 0),
+ 6: MGLStyleValue(rawValue: 1)],
+ options: nil)
+ symbols.iconHaloColor = MGLStyleValue(rawValue: UIColor.white.withAlphaComponent(0.5))
+ symbols.iconHaloWidth = MGLStyleValue(rawValue: 1)
+ // {name} references the "name" key in an MGLPointFeature’s attributes dictionary.
+ symbols.text = MGLStyleValue(rawValue: "{name}")
+ symbols.textColor = symbols.iconColor
+ symbols.textFontSize = MGLStyleValue(interpolationMode: .exponential,
+ cameraStops: [10: MGLStyleValue(rawValue: 10),
+ 16: MGLStyleValue(rawValue: 16)],
+ options: nil)
+ symbols.textTranslation = MGLStyleValue(rawValue: NSValue(cgVector: CGVector(dx: 10, dy: 0)))
+ symbols.textOpacity = symbols.iconOpacity
+ symbols.textHaloColor = symbols.iconHaloColor
+ symbols.textHaloWidth = symbols.iconHaloWidth
+ symbols.textJustification = MGLStyleValue(rawValue: NSValue(mglTextJustification: .left))
+ symbols.textAnchor = MGLStyleValue(rawValue: NSValue(mglTextAnchor: .left))
+
+ style.addLayer(circles)
+ style.addLayer(symbols)
+ }
+
+ // MARK: - Feature interaction
+ func handleMapTap(sender: UITapGestureRecognizer) {
+ if sender.state == .ended {
+ // Limit feature selection to just the following layer identifiers.
+ let layerIdentifiers: Set = ["lighthouse-symbols", "lighthouse-circles"]
+
+ // Try matching the exact point first.
+ let point = sender.location(in: sender.view!)
+ for f in mapView.visibleFeatures(at: point, styleLayerIdentifiers:layerIdentifiers)
+ where f is MGLPointFeature {
+ showCallout(feature: f as! MGLPointFeature)
+ return
+ }
+
+ let touchCoordinate = mapView.convert(point, toCoordinateFrom: sender.view!)
+ let touchLocation = CLLocation(latitude: touchCoordinate.latitude, longitude: touchCoordinate.longitude)
+
+ // Otherwise, get all features within a rect the size of a touch (44x44).
+ let touchRect = CGRect(origin: point, size: .zero).insetBy(dx: -22.0, dy: -22.0)
+ let possibleFeatures = mapView.visibleFeatures(in: touchRect, styleLayerIdentifiers: Set(layerIdentifiers)).filter { $0 is MGLPointFeature }
+
+ // Select the closest feature to the touch center.
+ let closestFeatures = possibleFeatures.sorted(by: {
+ return CLLocation(latitude: $0.coordinate.latitude, longitude: $0.coordinate.longitude).distance(from: touchLocation) < CLLocation(latitude: $1.coordinate.latitude, longitude: $1.coordinate.longitude).distance(from: touchLocation)
+ })
+ if let f = closestFeatures.first {
+ showCallout(feature: f as! MGLPointFeature)
+ return
+ }
+
+ // If no features were found, deselect the selected annotation, if any.
+ mapView.deselectAnnotation(mapView.selectedAnnotations.first, animated: true)
+ }
+ }
+
+ func showCallout(feature: MGLPointFeature) {
+ let point = MGLPointFeature()
+ point.title = feature.attributes["name"] as? String
+ point.coordinate = feature.coordinate
+
+ // Selecting an feature that doesn’t already exist on the map will add a new annotation view.
+ // We’ll need to use the map’s delegate methods to add an empty annotation view and remove it when we’re done selecting it.
+ mapView.selectAnnotation(point, animated: true)
+ }
+
+ // MARK: - MGLMapViewDelegate
+
+ func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
+ return true
+ }
+
+ func mapView(_ mapView: MGLMapView, didDeselect annotation: MGLAnnotation) {
+ mapView.removeAnnotations([annotation])
+ }
+
+ func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
+ // Create an empty view annotation. Set a frame to offset the callout.
+ return MGLAnnotationView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
+ }
+
+ // MARK: - Data fetching and parsing
+
+ func fetchPoints(withCompletion completion: @escaping (([MGLPointFeature]) -> Void)) {
+ // Wikidata query for all lighthouses in the United States: http://tinyurl.com/zrl2jc4
+ let query = "SELECT DISTINCT ?item " +
+ "?itemLabel ?coor ?image " +
+ "WHERE " +
+ "{ " +
+ "?item wdt:P31 wd:Q39715 . " +
+ "?item wdt:P17 wd:Q30 . " +
+ "?item wdt:P625 ?coor . " +
+ "OPTIONAL { ?item wdt:P18 ?image } . " +
+ "SERVICE wikibase:label { bd:serviceParam wikibase:language \"en\" } " +
+ "} " +
+ "ORDER BY ?itemLabel"
+
+ let characterSet = NSMutableCharacterSet()
+ characterSet.formUnion(with: CharacterSet.urlQueryAllowed)
+ characterSet.removeCharacters(in: "?")
+ characterSet.removeCharacters(in: "&")
+ characterSet.removeCharacters(in: ":")
+
+ let encodedQuery = query.addingPercentEncoding(withAllowedCharacters: characterSet as CharacterSet)!
+
+ let request = URLRequest(url: URL(string: "https://query.wikidata.org/sparql?query=\(encodedQuery)&format=json")!)
+
+ URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
+ guard let data = data else { return }
+ guard let json = try? JSONSerialization.jsonObject(with: data, options:[]) as? [String: AnyObject] else { return }
+ guard let results = json?["results"] as? [String: AnyObject] else { return }
+ guard let items = results["bindings"] as? [[String: AnyObject]] else { return }
+ DispatchQueue.main.async {
+ completion(self.parseJSONItems(items: items))
+ }
+ }).resume()
+ }
+
+ func parseJSONItems(items: [[String: AnyObject]]) -> [MGLPointFeature] {
+ var features = [MGLPointFeature]()
+ for item in items {
+ guard let label = item["itemLabel"] as? [String: AnyObject],
+ let title = label["value"] as? String else { continue }
+ guard let coor = item["coor"] as? [String: AnyObject],
+ let point = coor["value"] as? String else { continue }
+
+ let parsedPoint = point.replacingOccurrences(of: "Point(", with: "").replacingOccurrences(of: ")", with: "")
+ let pointComponents = parsedPoint.components(separatedBy: " ")
+ let coordinate = CLLocationCoordinate2D(latitude: Double(pointComponents[1])!, longitude: Double(pointComponents[0])!)
+ let feature = MGLPointFeature()
+ feature.coordinate = coordinate
+ feature.title = title
+ // A feature’s attributes can used by runtime styling for things like text labels.
+ feature.attributes = [
+ "name": title
+ ]
+ features.append(feature)
+ }
+ return features
+ }
+}