Update MapView with current location on SwiftUI - swift

Trying to update the mapview of the Project 14 of 100daysOfSwiftUI to show my current location, the problem i can´t zoom in move around
i have this code i add #Binding var currentLocation : CLLocationCoordinate2D and view.setCenter(currentLocation, animated: true) to my MapView so i have a button that send thats value and the view actually move so slow to the location but then i can move away anymore
import SwiftUI
import MapKit
struct MapView: UIViewRepresentable {
#Binding var centerCoordinate: CLLocationCoordinate2D
#Binding var selectedPlace: MKPointAnnotation?
#Binding var showingPlaceDetails: Bool
#Binding var currentLocation : CLLocationCoordinate2D
var annotations: [MKPointAnnotation]
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ view: MKMapView, context: Context) {
if annotations.count != view.annotations.count {
view.removeAnnotations(view.annotations)
view.addAnnotations(annotations)
}
view.setCenter(currentLocation, animated: true)
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, MKMapViewDelegate{
var parent: MapView
init(_ parent: MapView) {
self.parent = parent
}
func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
parent.centerCoordinate = mapView.centerCoordinate
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "PlaceMark"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView?.canShowCallout = true
annotationView?.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
} else {
annotationView?.annotation = annotation
}
return annotationView
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
guard let placemark = view.annotation as? MKPointAnnotation else {return}
parent.selectedPlace = placemark
parent.showingPlaceDetails = true
}
}
}
an this is my swiftUI view
...
#State private var currentLocation = CLLocationCoordinate2D()
var body: some View {
ZStack{
MapView(centerCoordinate: $centerCoordinate, selectedPlace: $selectedPlace, showingPlaceDetails: $showingPlaceDetails, currentLocation: $currentLocation , annotations: locations)
// MapView(centerCoordinate: $centerCoordinate, selectedPlace: $selectedPlace, showingPlaceDetails: $showingPlaceDetails, annotations: locations)
.edgesIgnoringSafeArea(.all)
VStack{
Spacer()
HStack{
Spacer()
Button(action: {
self.getCurrentLocation()
}){
ButtonIcon(icon: "location.fill")
}
}
.padding()
}
}
.onAppear(perform: getCurrentLocation)
}
func getCurrentLocation() {
let lat = locationManager.lastLocation?.coordinate.latitude ?? 0
let log = locationManager.lastLocation?.coordinate.longitude ?? 0
self.currentLocation.latitude = lat
self.currentLocation.longitude = log
}
...
UPDATE
thanks for the support I using this class to call locationManager.requestWhenInUseAuthorization()
import Foundation
import CoreLocation
import Combine
class LocationManager: NSObject, ObservableObject {
override init() {
super.init()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
}
#Published var locationStatus: CLAuthorizationStatus? {
willSet {
objectWillChange.send()
}
}
#Published var lastLocation: CLLocation? {
willSet {
objectWillChange.send()
}
}
var statusString: String {
guard let status = locationStatus else {
return "unknown"
}
switch status {
case .notDetermined: return "notDetermined"
case .authorizedWhenInUse: return "authorizedWhenInUse"
case .authorizedAlways: return "authorizedAlways"
case .restricted: return "restricted"
case .denied: return "denied"
default: return "unknown"
}
}
let objectWillChange = PassthroughSubject<Void, Never>()
private let locationManager = CLLocationManager()
}
extension LocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
self.locationStatus = status
print(#function, statusString)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
self.lastLocation = location
print(#function, location)
}
}
i just want to center my mapview on my current location when i press the button

No where here do you ever call locationManager.requestWhenInUseAuthorization(). When I did that (of course, making sure the Info.plist had an entry for NSLocationWhenInUseUsageDescription), it updated the location correctly.
E.g.
func getCurrentLocation() {
if CLLocationManager.authorizationStatus() == .notDetermined {
locationManager.requestWhenInUseAuthorization()
}
if let coordinate = locationManager.location?.coordinate {
currentLocation = coordinate
}
}
Now, this is just a quick and dirty fix to demonstrate that it works. But it’s not quite right, because the first time you call getCurrentLocation, if it has to ask the user for permission, which it does asynchronously, which means that it won’t yet have a location when you get to the lastLocation line in your implementation. This is a one time thing, but still, it’s not acceptable. You’d want your CLLocationManagerDelegate update currentLocation if needed. But hopefully you’ve got enough here to diagnose why your location is not being captured correctly by the CLLocationManager.
FWIW, you might consider using a userTrackingMode of .follow, which obviates the need for all of this manual location manager and currentLocation stuff. The one caveat I’ll mention (because I spent hours one day trying to diagnose this curious behavior), is that the userTrackingMode doesn’t work if you initialize your map view with:
let mapView = MKMapView()
But it works if you do give it some frame, e.g.:
let mapView = MKMapView(frame: UIScreen.main.bounds)
So, for user tracking mode:
struct MapView: UIViewRepresentable {
#Binding var userTrackingMode: MKUserTrackingMode
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView(frame: UIScreen.main.bounds)
mapView.delegate = context.coordinator
mapView.userTrackingMode = userTrackingMode
return mapView
}
func updateUIView(_ view: MKMapView, context: Context) {
view.userTrackingMode = userTrackingMode
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, MKMapViewDelegate {
var parent: MapView
init(_ parent: MapView) {
self.parent = parent
}
// MARK: - MKMapViewDelegate
func mapView(_ mapView: MKMapView, didChange mode: MKUserTrackingMode, animated: Bool) {
DispatchQueue.main.async {
self.parent.$userTrackingMode.wrappedValue = mode
}
}
// note, implementation of `mapView(_:viewFor:)` is generally not needed if we register annotation view class
}
}
And then, we can have a “follow” button that appears when user tracking is turned off (so that you can turn it back on):
struct ContentView: View {
#State var userTrackingMode: MKUserTrackingMode = .follow
private var locationManager = CLLocationManager()
var body: some View {
ZStack {
MapView(userTrackingMode: $userTrackingMode)
.edgesIgnoringSafeArea(.all)
VStack {
HStack {
Spacer()
if self.userTrackingMode == .none {
Button(action: {
self.userTrackingMode = .follow
}) {
Text("Follow")
}.padding()
}
}
Spacer()
}
}.onAppear { self.requestAuthorization() }
}
func requestAuthorization() {
if CLLocationManager.authorizationStatus() == .notDetermined {
locationManager.requestWhenInUseAuthorization()
}
}
}

Related

Pass data in swift from one class to another

I have an app where a user takes a photo and he creates the annotation on the map with this photo, the photo has to be transferred to ImageAnnotation class and than a pin must be created on the map
import UIKit
import MapKit
import CoreLocation
import Firebase
class MapViewController: UIViewController, CLLocationManagerDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
#IBOutlet weak var MapButton: UITabBarItem!
#IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()
var currentLocation: CLLocation!
let regionInMeters: Double = 10000
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.isNavigationBarHidden = true
let db = Firestore.firestore()
db.collection("locations").getDocuments() { [self] (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
if let coords = document.get("pinLocation") {
let point = coords as! GeoPoint
let lat = point.latitude
let lon = point.longitude
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: lat, longitude: lon)
mapView.addAnnotation(annotation)
}
}
}
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
navigationController?.isNavigationBarHidden = false
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
checkLocationServices()
locationManager.delegate = self
}
func setupLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func centerViewOnUserLocation() {
if let location = locationManager.location?.coordinate {
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
mapView.setRegion(region, animated: true)
}
}
func checkLocationServices() {
if CLLocationManager.locationServicesEnabled() {
setupLocationManager()
checkLocationAuthorization()
} else {
}
}
func checkLocationAuthorization() {
switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse:
mapView.showsUserLocation = true
centerViewOnUserLocation()
locationManager.startUpdatingLocation()
break
case .denied:
break
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .restricted:
break
case .authorizedAlways:
break
}
}
#IBAction func plusPressed(_ sender: UIButton) {
// guard let currLoc = locationManager.location else { return }
currentLocation = locationManager.location
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: currentLocation.coordinate.latitude, longitude: currentLocation.coordinate.longitude)
mapView.addAnnotation(annotation)
let picker = UIImagePickerController()
picker.sourceType = .camera
picker.delegate = self
present(picker, animated: true)
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation.isKind(of: MKUserLocation.self) { //Handle user location annotation..
return nil //Default is to let the system handle it.
}
if !annotation.isKind(of: ImageAnnotation.self) { //Handle non-ImageAnnotations..
var pinAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "DefaultPinView")
if pinAnnotationView == nil {
pinAnnotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "DefaultPinView")
}
return pinAnnotationView
}
//Handle ImageAnnotations..
var view: ImageAnnotationView? = mapView.dequeueReusableAnnotationView(withIdentifier: "imageAnnotation") as? ImageAnnotationView
if view == nil {
view = ImageAnnotationView(annotation: annotation, reuseIdentifier: "imageAnnotation")
}
let annotation = annotation as! ImageAnnotation
view?.image = annotation.image
view?.annotation = annotation
return view
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// guard let location = locations.last else { return }
// let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
// let region = MKCoordinateRegion.init(center: center, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
// mapView.setRegion(region, animated: true)
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
checkLocationAuthorization()
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else {
return
}
}
}
class ImageAnnotation : NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
var image: UIImage?
var color: UIColor?
override init() {
self.coordinate = CLLocationCoordinate2D()
self.image = ///// Here must go the image from imagePickerController
self.color = UIColor.systemGreen
}
}
class ImageAnnotationView: MKAnnotationView {
private var imageView: UIImageView!
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
self.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
self.imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
self.addSubview(self.imageView)
self.imageView.layer.cornerRadius = 25
self.imageView.layer.masksToBounds = true
}
override var image: UIImage? {
get {
return self.imageView.image
}
set {
self.imageView.image = newValue
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
You posted a whole bunch of code that isn't relevant to your question. You should narrow it down to the code specific to your question (The class definition for your map view controller definition, plus the plusPressed IBAction method, the image picker delegate methods, the map view delegate methods, and your classes and methods for your custom annotation and annotation view classes.
You also did not have your code formatted correctly. I edited your question and put all of your code inside triple backticks so it would show up correctly.
Your code won't work as written. Your plusPressed() method creates an MKPointAnnotation object instead if your custom ImageAnnotation object. Your plusPressed() method should create and return an ImageAnnotation object instead.
You have your mapView(_:viewFor:) method nested inside your plusPressed() method, which does not make sense. You want the mapView(_:viewFor:) at the top level of your view controller class (or whatever class is the map view delegate. In your case that's the view controller.)
You should have your plusPressed() invoke the image picker, and only create an add an ImageAnnotation to the map if the user selects an image and presses OK. (You'd put code in your image picker's didFinishPickingMediaWithInfo method that would take the user-selected image, use it to create an ImageAnnotation, and add that annotation to the map.)

How do I see if a annotation is selected with MapBox in SwiftUI?

I'm trying to update my UI accordingly when a MapBox annotation is selected using swiftUI. Everything works good until I change the bool within the MapView Coordinator. Once I do, the annotations will not update.
struct MainView: View {
#State var annotations: [MGLPointAnnotation] = []
#State var pingDetailsShown = false
var body: some View {
///...
MapView(annotations: self.$annotations, pingDetailsShown: self.$pingDetailsShown).centerCoordinate(.init(latitude: 53.460067, longitude: -114.996973)).zoomLevel(5.0)
//...
MapView
struct MapView: UIViewRepresentable {
#Binding var annotations: [MGLPointAnnotation]
#Binding var pingDetailsShown: Bool
private let mapView: MGLMapView = MGLMapView(frame: .zero, styleURL: MGLStyle.streetsStyleURL)
func makeUIView(context: UIViewRepresentableContext<MapView>) -> MGLMapView {
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ uiView: MGLMapView, context: UIViewRepresentableContext<MapView>) {
updateAnnotations()
}
func makeCoordinator() -> MapView.Coordinator {
Coordinator(self, pingDetailsShown: $pingDetailsShown)
}
private func updateAnnotations() {
if let currentAnnotations = mapView.annotations {
mapView.removeAnnotations(currentAnnotations)
}
mapView.addAnnotations(annotations)
}
Here's where I run into trouble...
final class Coordinator: NSObject, MGLMapViewDelegate {
var control: MapView
var startZoom: Double = 5.0
#Binding var pingDetailsShown: Bool
init(_ control: MapView, pingDetailsShown: Binding<Bool>) {
self.control = control
self._pingDetailsShown = pingDetailsShown
}
func mapView(_ mapView: MGLMapView, didSelect annotation: MGLAnnotation) {
print(((annotation.title ?? "") ?? ""))
pingDetailsShown = true
}
func mapView(_ mapView: MGLMapView, didDeselect annotationView: MGLAnnotationView) {
pingDetailsShown = false
}
}
I've tried the pingDetailsShown as not binded also, but the same issue happens, as soon as I change the pingDetailsShown value, it no longer allows the MapView annotations to be updated.
All I am trying to do is update the MainView UI when an annotation is selected, and have the annotations still refresh after.
If you declare your coordinates array and point annotations similarly to this Annotation Views example, your annotations should still update successfully.
Declare your coordinates in the didFinishLoading method:
let coordinates = [
CLLocationCoordinate2D(latitude: 37.791329, longitude: -122.396906),
CLLocationCoordinate2D(latitude: 37.791591, longitude: -122.396566),
CLLocationCoordinate2D(latitude: 37.791147, longitude: -122.396009),
CLLocationCoordinate2D(latitude: 37.790883, longitude: -122.396349),
CLLocationCoordinate2D(latitude: 37.791329, longitude: -122.396906),
]
for coordinate in coordinates {
let point = MGLPointAnnotation()
point.coordinate = coordinate
point.title = "\(coordinate.latitude), \(coordinate.longitude)"
pointAnnotations.append(point)
}
mapView.addAnnotations(pointAnnotations)
and then in the didSelectAnnotation method, you can set whatever action you wish to occur when the annotation is selected:
func mapView(_ mapView: MGLMapView, didSelect annotation: MGLAnnotation) {
if annotation.title == "37.791329, -122.396906" {
mapView.styleURL = MGLStyle.lightStyleURL
} else if annotation.title == "37.790883, -122.396349" {
mapView.styleURL = MGLStyle.darkStyleURL
}
pingDetailsShown = true
}
You should change updateUIView and updateAnnotations to include the current view being refreshed:
func updateUIView(_ uiView: MGLMapView, context: Context) {
updateAnnotations(uiView)
}
private func updateAnnotations(_ view: MGLMapView) {
if let currentAnnotations = view.annotations {
view.removeAnnotations(currentAnnotations)
}
view.addAnnotations(annotations)
}

The centre locations in MapKit is not visible in the UiLabel

Unable to show the centrelocation of map in the label!
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var myMapView: MKMapView!
#IBOutlet weak var addressLabel: UILabel!
let locationManager = CLLocationManager()
let regionInMeters: Double = 10000
var previousLocation: CLLocation?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
checkLocationServices()
}
func setupLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func centerViewOnUserLocation() {
if let location = locationManager.location?.coordinate {
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
myMapView.setRegion(region, animated: true)
}
}
// the Location service
func checkLocationServices() {
if CLLocationManager.locationServicesEnabled() {
setupLocationManager()
checkLocationAuthorisation()
} else {
//show user to turn ON the services
}
}
func checkLocationAuthorisation() {
switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse:
// do the stuff
startTrackingLocation()
case .denied:
//Alert to turn ON the permissions
break
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .restricted:
// Alert to show what's up
break
case .authorizedAlways:
break
}
}
func startTrackingLocation() {
myMapView.showsUserLocation = true
centerViewOnUserLocation()
locationManager.startUpdatingLocation()
previousLocation = getCenterLocation(for: myMapView)
}
func getCenterLocation(for mapView: MKMapView) -> CLLocation {
let latitude = mapView.centerCoordinate.latitude
let longitude = mapView.centerCoordinate.longitude
return CLLocation(latitude: latitude, longitude: longitude)
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
// We'll be back
checkLocationAuthorisation()
}
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
let center = getCenterLocation(for: myMapView)
let geoCoder = CLGeocoder()
guard let previousLocation = self.previousLocation else { return }
guard center.distance(from: previousLocation) > 50 else { return }
self.previousLocation = center
geoCoder.reverseGeocodeLocation(center) { [weak self] (placemarks, error) in
guard let self = self else { return }
if let _ = error {
//TODO: Show alert informing the user
return
}
guard let placemark = placemarks?.first else {
//TODO: Show alert informing the user
return
}
let streetNumber = placemark.subThoroughfare ?? ""
let streetName = placemark.subThoroughfare ?? ""
DispatchQueue.main.async {
self.addressLabel.text = "\(streetNumber) \(streetName)"
}
}
}
#IBAction func satelliteView(_ sender: Any) {
myMapView.mapType = MKMapType.satellite
}
#IBAction func hybridView(_ sender: Any) {
myMapView.mapType = MKMapType.hybrid
}
#IBAction func standardView(_ sender: Any) {
myMapView.mapType = MKMapType.standard
}
#IBAction func findDirections(_ sender: Any) {
}
}
The label in the app is supposed to show the centrelocation(pin) of the map. But don't know for where is it going wrong!
Changed the getCenterlocation function and its working fine!
`func getCenterLocation(for mapView: MKMapView) -> CLLocation {
let latitude=mapView.centerCoordinate.latitude
let longitude=mapView.centerCoordinate.longitude
return CLLocation(latitude:latitude, longitude:longitude)
}
func getCenterLocation(for myMapView: MKMapView) -> CLLocation {
let latitude=myMapView.centerCoordinate.latitude
let longitude=myMapView.centerCoordinate.longitude
return CLLocation(latitude:latitude, longitude:longitude)
}`

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

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))
}
}