Make custom location annotations in using Mapbox - swift

I am using Mapbox to create my app. When I click a button I would like it to mark my current location and add a marker to it as it currently does. I then would like the abiilty to tap on this marker and have it display the current location information such as the adress of the marked point.
Right now all I have is...
https://imgur.com/a/RSx0G
I would like to note that I am using Xcode 9.1 and Swift 4. Thank you for all your feedback in advance.
Currently the swift file looks like...
import Foundation
import UIKit
import CoreLocation
import Mapbox
import MapKit
import MapboxGeocoder
class SecondViewController: UIViewController, CLLocationManagerDelegate, MGLMapViewDelegate, UITextFieldDelegate {
let geocoder = Geocoder.shared
let dismissesAutomatically: Bool = false
let isAnchoredToAnnotation: Bool = true
weak var delegate: MGLCalloutViewDelegate?
let tipHeight: CGFloat = 10.0
let tipWidth: CGFloat = 20.0
#IBOutlet var mapView: MGLMapView!
let manager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func markStuff(_ sender: Any) {
}
#IBAction func refLocation(_ sender: Any) {
manager.startUpdatingLocation()
}
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return true
}
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
return nil
}
func mapView(_ mapView: MGLMapView, tapOnCalloutFor annotation: MGLAnnotation)
{
print("tap on callout")
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]
let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
mapView.setCenter(center, zoomLevel: 10, animated: true)
let annotation = MGLPointAnnotation()
annotation.coordinate = location.coordinate
mapView.selectAnnotation(annotation, animated: true)
annotation.title = "Testing"
annotation.subtitle = "\(annotation.coordinate.latitude), \(annotation.coordinate.longitude)"
self.mapView.addAnnotation(annotation)
manager.stopUpdatingLocation()

You can create a subclass of MGLUserLocationAnnotationView, then use that as your view for the MGLUserLocation annotation.
For example, if your custom subclass was called YourLocationAnnotationView(), you could do something like:
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {This custom view is created below.
if annotation is MGLUserLocation && mapView.userLocation != nil {
return YourLocationAnnotationView()
}
return nil
}
}
For a complete implementation, see this example from the official documentation.

Related

How do I add image overlays to an MKMapView?

----------UPDATED------------
original question at the bottom
I've gotten pretty far, and I have this now:
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet var mapView: MKMapView!
var locationManager: CLLocationManager!
var mapOverlay: MKOverlay!
override func viewDidLoad() {
super.viewDidLoad()
var points = [CLLocationCoordinate2D(latitude: -29.8122, longitude: 148.6351),
CLLocationCoordinate2D(latitude: -27.9307, longitude: 148.6351),
CLLocationCoordinate2D(latitude: -27.9307, longitude: 150.9909),
CLLocationCoordinate2D(latitude: -29.8122, longitude: 150.9909)]
let tile = MKPolygon(coordinates: &points, count: points.count)
tile.title = "zurich"
mapView.addOverlay(tile)
//Setup our Location Manager
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
//Setup our Map View
mapView.delegate = self
mapView.mapType = MKMapType.satellite
mapView.showsUserLocation = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// mapView delegate function
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolygonRenderer(overlay: overlay)
renderer.fillColor = UIColor.red
return renderer
}
}
I now need to know how to replace the renderer.fillColor = UIColor.red with something that will display my image.
Thanks once again
----- original question ------
So, I'm new to Swift and MapKit and I want to add a simple image overlay on top of an MKMapView. I've found a few answers, but they're all confusing, and they are all for Swift 3 and earlier.
I've found that a delegate for the map view is needed, is that a file?
I have already created a map view using the main view controller.
This is what I've done so far (this is in the ViewController.swift file):
import UIKit
import MapKit
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate
{
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
}
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib
let location = CLLocationCoordinate2D(latitude: 47.457925,
longitude: 8.548466)
let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
let region = MKCoordinateRegion(center: location, span: span)
mapView.setRegion(region, animated: true)
}
}
Thank you and I hope you can help!
There are a lot of way to embed image into your maps.
Annotation Views
Callouts
Custom Map Tile
Explain your need more, and maybe we can help better to how to get there.
You are adding overlay over the map. We want to change with specific map tile.
func createLocalUrl(forImageNamed name: String) -> URL? {
let fileManager = FileManager.default
let cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)[0]
let url = cacheDirectory.appendingPathComponent("\(name).png")
guard fileManager.fileExists(atPath: url.path) else {
guard
let image = UIImage(named: name),
let data = image.pngData()
else { return nil }
fileManager.createFile(atPath: url.path, contents: data, attributes: nil)
return url
}
return url
}
func setupTiles() {
let url = createLocalUrl(forImageNamed: "yourImageName")
let template = url?.absoluteString
let overlay = MKTileOverlay(urlTemplate: template)
overlay.canReplaceMapContent = true
self.tileOverlay = overlay
mapView.addOverlay(overlay)
self.tileRenderer = MKTileOverlayRenderer(tileOverlay: overlay)
}
func isInDesiredArea(middlePoint: MKMapPoint) -> Bool {
//mapView has convert function which converts CGPoint ->
//CLLocationCoordinate2D and vice versa Use this function and,
//Your polygon has boundingMapRect which has contains function.
//Also your map has func mapView(_ mapView: MKMapView,
//regionDidChangeAnimated animated: Bool) which runs whenever region changes..
return myBoundsPolygon.boundingMapRect.hasContain(middlePoint)
}
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
//Convert middle point of your view to CLLocationCoordinate2D
//Convert your coordinate to MKMapPoint
if isInDesiredArea(middlePoint: point) {
setupTiles()
}
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
....
if overlay is MKTileOverlay {
return tileRenderer
}

Pass the url from the marker into the controller

I'm doing the app for viewing IP cameras and want to add cameras to the map so you can click on the desired marker and go to see the desired camera. I have a map and PlayerViewController which reproduce a webcam.
Now each marker only transmits the first stream of the webcam. Help me. How to make different webcam worked?
ViewController
import UIKit
import MapKit
class ViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
var moscow: [(name: String, URLs:String, img:String, latitude: Double, longitude: Double)] =
[("cam1", "http://example/1.m3u8", "1.jpg", 55.753989, 37.620235),
("cam2", "http://example/2.m3u8", "2.jpg", 55.741308, 37.653914),
("cam3","http://example/3.m3u8","3.jpg", 55.742468, 37.629292)]
override func viewDidLoad() {
super.viewDidLoad()
var latitudes = moscow.map({ $0.latitude })
var longitudes = moscow.map({ $0.longitude })
var annotations = moscow.map({ $0.name })
for i in 0...2 {
let coordinate = CLLocationCoordinate2DMake(latitudes[i], longitudes[i])
let span = MKCoordinateSpanMake(0.003, 0.003)
let region = MKCoordinateRegionMake(coordinate, span)
mapView.setRegion(region, animated:true)
let annotation = MKPointAnnotation()
annotation.coordinate = coordinate
annotation.title = annotations[i]
self.mapView.addAnnotation(annotation)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
print(#function)
}
// Called when the annotation was added
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
let reuseId = "pin"
var pinView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) as? MKPinAnnotationView
if pinView == nil {
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView?.animatesDrop = true
pinView?.canShowCallout = true
pinView?.isDraggable = true
pinView?.pinColor = .purple
let rightButton: AnyObject! = UIButton(type: UIButtonType.detailDisclosure)
pinView?.rightCalloutAccessoryView = rightButton as? UIView
} else {
pinView?.annotation = annotation
}
return pinView
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
print(#function)
if control == view.rightCalloutAccessoryView {
performSegue(withIdentifier: "toTheMoon", sender: self)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toTheMoon" {
let controller = segue.destination as! PlayerViewController
var urlll = moscow.map({ $0.URLs })
for i in 0...2 {
controller.webcamURL = urlll[i] // only first cam play
}
}
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, didChange newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {
if newState == MKAnnotationViewDragState.ending {
let droppedAt = view.annotation?.coordinate
print(droppedAt)
}
}
}
PlayerViewController
import UIKit
import AVFoundation
import AVKit
class PlayerViewController: AVPlayerViewController {
var webcamURL: String!
var webcamTitle: String!
override func viewDidLoad() {
super.viewDidLoad()
self.title = webcamTitle
let url = URL(string: webcamURL)
player = AVPlayer(url: url!)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
player!.play()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.player!.pause()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
player = nil
}
}
First of All MKAnnotation is a Protocol so you can implement this protocol in your model class, lets say "Camera"
import UIKit
import MapKit
class Camera: NSObject, MKAnnotation {
var name: String = ""
var urlString :String = ""
var coordinate: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 0, longitude: 0)
var imageName : String = ""
init(name:String,camUrl:String,imageNamed:String,latitude:CLLocationDegrees,longitude:CLLocationDegrees) {
super.init()
self.name = name
self.urlString = camUrl
self.imageName = imageNamed
guard latitude != 0 && longitude != 0 else
{
return
}
guard latitude.isNaN || longitude.isNaN else
{
return
}
self.coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
// Title and subtitle for use by selection UI.
public var title: String? {
get{
return self.name
}
}
}
Then you can reduce your ViewController code like this
import UIKit
import MapKit
class ViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView!
var moscow: [Camera] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.moscow = [Camera(name: "cam1", camUrl: "http://example/1.m3u8", imageNamed: "1.jpg", latitude: 55.753989, longitude: 37.620235),
Camera(name: "cam2", camUrl: "http://example/2.m3u8", imageNamed: "2.jpg", latitude: 55.741308, longitude: 37.653914),
Camera(name: "cam3", camUrl: "http://example/3.m3u8", imageNamed: "3.jpg", latitude: 55.742468, longitude: 37.629292)]
self.mapView.addAnnotations(self.moscow)
self.mapView.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension ViewController : MKMapViewDelegate
{
func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
print(#function)
}
// Called when the annotation was added
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
let reuseId = "pin"
var pinView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) as? MKPinAnnotationView
if pinView == nil {
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView?.animatesDrop = true
pinView?.canShowCallout = true
pinView?.isDraggable = true
pinView?.pinColor = .purple
let rightButton: AnyObject! = UIButton(type: UIButtonType.detailDisclosure)
pinView?.rightCalloutAccessoryView = rightButton as? UIView
} else {
pinView?.annotation = annotation
}
return pinView
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
print(#function)
let camera = view.annotation as! Camera
if control == view.rightCalloutAccessoryView {
performSegue(withIdentifier: "toTheMoon", sender: camera)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toTheMoon" {
let controller = segue.destination as! PlayerViewController
controller.webcamURL = (sender as! Camera).urlString
controller.webcamTitle = (sender as! Camera).name
}
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, didChange newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {
if newState == MKAnnotationViewDragState.ending {
let droppedAt = view.annotation?.coordinate
print(droppedAt)
}
}
}
Hope this helps

Swift performSegue going to Xcode

I am trying to perform a Segue between two UI Views I have correctly typed the identifier for the segue as can be seen Here
This is my function, with the same identifier "no" but when the button is clicked in the simulator it simply shows This (It looks like it is showing the stack in bottom left?)
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
if control == view.rightCalloutAccessoryView {
performSegue(withIdentifier: "no", sender:self)
}
}
I have attached my full code incase further analysis is needed. Thanks for your help.
View Controller:
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, MKMapViewDelegate,CLLocationManagerDelegate {
#IBOutlet weak var MapView: MKMapView!
let manager = CLLocationManager()
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
//let location = locations[0]
//let span:MKCoordinateSpan = MKCoordinateSpanMake(0.02, 0.02)
//let myLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
}
override func viewDidLoad() {
super.viewDidLoad()
// tracking user's location
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
// Setting up Map
let distanceSpan:CLLocationDegrees = 2000
MapView.setRegion(MKCoordinateRegionMakeWithDistance(CLLocationCoordinate2DMake(-39.0556253, 174.0752278), distanceSpan, distanceSpan), animated: true)
MapView.showsUserLocation = true
MapView.delegate = self
// artwork on map
let windwandcoord: CLLocationCoordinate2D = CLLocationCoordinate2DMake(-39.055961,174.072288)
let artworkPin = Artwork(title:"Wind Wand",locationName:"Majestic",discipline:"Statue",
coordinate:windwandcoord)
MapView.addAnnotation(artworkPin)
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
{
if annotation is MKUserLocation {return nil}
let reuseId = "pin"
var pinView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) as? MKPinAnnotationView
if pinView == nil {
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView!.canShowCallout = true
pinView!.animatesDrop = true
pinView!.calloutOffset = CGPoint(x: -5, y: 5)
let calloutButton = UIButton(type: .detailDisclosure)
pinView!.rightCalloutAccessoryView = calloutButton
pinView!.sizeToFit()
}
else {
pinView!.annotation = annotation
}
return pinView
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
if control == view.rightCalloutAccessoryView {
performSegue(withIdentifier: "no", sender:self)
}
}
}
Any help appreciated.
See the solid blue bar in the gutter in your screen shot?
It is a breakpoint. When the path of execution reaches a breakpoint that is active (solid blue like this), we pause there. That is what a breakpoint is for.
If you don't want that to happen, drag the breakpoint out of the gutter.

How to detect zoom's effect on the map in Swift

I am using regionDidChangeAnimated for my mapView. I want to detect when user zoom's or drag's a map. Drag is being detected, but I can't seem to detect when user zoom's the map. I tried to use:
func mapView(mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
print("region will change")
}
But when I zoom my map it does not work.
Full code:
var locationManager: CLLocationManager = CLLocationManager()
var startLocation: CLLocation!
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
//get current location
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
self.mapView.showsUserLocation = true
startLocation = nil
let mapDragRecognizer = UIPanGestureRecognizer(target: self, action: "didDragMap:")
mapDragRecognizer.delegate = self
self.mapView.addGestureRecognizer(mapDragRecognizer)
self.mapView.pitchEnabled = true
self.mapView.showsBuildings = true
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [CLLocation]) {
let location = locations.last
let center = CLLocationCoordinate2D(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05))
mapView.setRegion(region, animated: false)
let currentLocX = String(format: "%.4f", location!.coordinate.latitude)
let currentLocY = String(format: "%.4f", location!.coordinate.longitude)
self.locationManager.stopUpdatingLocation()
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("Errors: " + error.localizedDescription)
}
func didDragMap(gestureRecognizer: UIGestureRecognizer) {
print("Drag")
}
func mapView(mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
print("Zoom")
}
I have solved it. In the override func viewDidLoad() need to add self.mapView.delegate = self
And add functions for determine zoom's on the map:
private var mapChangedFromUserInteraction = false
private func mapViewRegionDidChangeFromUserInteraction() -> Bool {
let view = self.mapView.subviews[0]
if let gestureRecognizers = view.gestureRecognizers {
for recognizer in gestureRecognizers {
if( recognizer.state == UIGestureRecognizer.State.began || recognizer.state == UIGestureRecognizer.State.ended )) {
return true
}
}
}
return false
}
func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
mapChangedFromUserInteraction = mapViewRegionDidChangeFromUserInteraction()
}
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
if (mapChangedFromUserInteraction) {
print("ZOOM finished")
}
}

How to disallow rotation on MKMapView but show heading?

For my project, I would like to put on a MKMapView the heading of the user without rotating the map (with the blue cone).
Here's a gist
Furthermore, with mapView.setUserTrackingMode(MKUserTrackingMode.FollowWithHeading, animated: true) enable, I can't navigate trough my map.
I tried to set the trackingMode on the mapView:didChangeUserTrackingMode but It's not working.
Any idea ?
I had the same issue and resolved it by using CLLocationManager and locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) method:
class MyViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView!
weak var userAnnotationView: MKAnnotationView?
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
mapView.showsUserLocation = true
mapView.delegate = self
locationManager.delegate = self
locationManager.startUpdatingHeading()
}
}
// MARK: - MKMapViewDelegate
extension MyViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if let annotation = annotation as? MKUserLocation {
// User
let reuseIdentifier = "UserAnnotationView"
let annotationView: MKAnnotationView
if let view = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier) {
annotationView = view
} else {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
}
annotationView.image = UIImage(named: "userLocationWithoutHeading")
userAnnotationView = annotationView
return annotationView
} else {
return nil
}
}
}
// MARK: - CLLocationManagerDelegate
extension MyViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
guard let userAnnotationView = userAnnotationView else { return }
userAnnotationView.image = UIImage(named: "arrowUp")
let rotationAngle = heading.magneticHeading * Double.pi / 180.0
userAnnotationView.transform = CGAffineTransform(rotationAngle: CGFloat(rotationAngle))
}
}