I'm trying to generate a map using the GoogleMaps SDK, I had previously generated a map using the lines "let map = GMSMapView()" and then setting the view with "self.view = map" and that worked like a charm, but then I switched over to this more complicated format used in a tutorial I am following, and even though the code is the same, mine appears to fail to generate the map :(
Any help would be amazing as I've been struggling with this for a while now and I really can't spot the issue!
//Modules
import GoogleMaps
import SwiftUI
import UIKit
import GoogleMapsUtils
class MapViewController: UIViewController, GMSMapViewDelegate, CLLocationManagerDelegate {
var mapView: GMSMapView!
let locationManager = CLLocationManager()
override func loadView() {
super.loadView() //LoadMap - Default
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
guard let myLoc = locationManager.location?.coordinate else{return}
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
//Camera Focus
let camera = GMSCameraPosition.camera(withLatitude: myLoc.latitude, longitude: myLoc.longitude, zoom: 15.0)
mapView = GMSMapView.map(withFrame: self.view.bounds, camera: camera)
mapView.isMyLocationEnabled = true
mapView.delegate = self
self.view = mapView
}
}
EDIT: Added swiftui bridge viewcontroller below:
import GoogleMaps
import SwiftUI
struct MapViewControllerBridge: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> MapViewController {
let uiViewController = MapViewController()
return uiViewController
}
func updateUIViewController(_ uiViewController: MapViewController, context: Context) {
}
}
you have just assigned the view. instead of that add mapView to view as subview
just replace this line
self.view = mapView with
self.view.addSubview(mapView)
Related
I am trying to use the existing MapKit functionality on an image.
What I am trying to achieve is to drop pins on an image and maybe add notes on these pins.
I have managed to give the user the possibility to add a pin dynamically with a longGesture but I don't know how to achieve the same on an image.
My code is as follows:
import UIKit
import MapKit
class ViewController: UIViewController , MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
var keyLat:String = "49.2768"
var keyLon:String = "-123.1120"
#IBOutlet weak var image: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
let longPressRecogniser = UILongPressGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
longPressRecogniser.minimumPressDuration = 0.5
mapView.addGestureRecognizer(longPressRecogniser)
mapView.mapType = MKMapType.standard
let location = CLLocationCoordinate2D(latitude: CLLocationDegrees(keyLat.toFloat()),longitude: CLLocationDegrees(keyLon.toFloat()))
let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
let region = MKCoordinateRegion(center: location, span: span)
mapView.setRegion(region, animated: true)
let annotation = MKPointAnnotation()
annotation.coordinate = location
annotation.title = "MY Pin"
annotation.subtitle = "On the Map"
mapView.addAnnotation(annotation)
}
#objc func handleTap(_ gestureReconizer: UILongPressGestureRecognizer)
{
let location = gestureReconizer.location(in: mapView)
let coordinate = mapView.convert(location,toCoordinateFrom: mapView)
// Add annotation:
let annotation = MKPointAnnotation()
annotation.coordinate = coordinate
annotation.title = "latitude:" + String(format: "%.02f",annotation.coordinate.latitude) + "& longitude:" + String(format: "%.02f",annotation.coordinate.longitude)
mapView.addAnnotation(annotation)
}
var selectedAnnotation: MKPointAnnotation?
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
let latValStr : String = String(format: "%.02f",Float((view.annotation?.coordinate.latitude)!))
let lonvalStr : String = String(format: "%.02f",Float((view.annotation?.coordinate.longitude)!))
print("latitude: \(latValStr) & longitude: \(lonvalStr)")
}
}
any help will be really appreciated.
Thanks
George
The same can be achieved by following the same procedure with a few tweaks here and there. I've made a sample ViewController that demonstrates how you can add pointers (UIViews in this case) into a UIImageView.
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView! // Image view
lazy var longPress = UILongPressGestureRecognizer(target: self, action: #selector(didLongPressScreen)) // long press gesture
// MARK: LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
setup()
}
// MARK: Functions
private func setup() {
imageView.image = UIImage(named: "photo")
imageView.addGestureRecognizer(longPress) // Adding gesture recognizer
imageView.isUserInteractionEnabled = true // ImageViews are not user interactive by default
}
// UILongPressGestureRecognizer Action
#objc func didLongPressScreen(_ sender: UILongPressGestureRecognizer) {
let location = sender.location(in: self.view) //Getting location
DispatchQueue.main.async {
let pointer = UIView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
pointer.backgroundColor = .red
pointer.center = location // Setting the center of the view to the x,y coordinates of the long press
self.view.addSubview(pointer) // Adding the UIView to the view
}
}
}
The most important part of this is to enable the user interaction for the UIImageView as its. isUserInteractionEnabled is set to false by default. The output of the above can be seen below,
----------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
}
problem
I want to make the screen transition when I tap a circle drawn using GMSCircle, not a marker or title.
I wrote the code below, but tapping the circle does not show anything in the debug area and I can not transition.
What is missing?
environment
swift4
Google Maps SDK for iOS
Xcode 9.4.1
import UIKit
import GoogleMaps
import CoreLocation
class ViewController: UIViewController, GMSMapViewDelegate{
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// map
let camera = GMSCameraPosition.camera(withLatitude: 35.687396, longitude: 139.743924, zoom: 17)
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
mapView.delegate = self
self.view = mapView
// mapView.isMyLocationEnabled
let circle = GMSCircle()
circle.position = CLLocationCoordinate2DMake(35.687396, 139.743924)
circle.radius = CLLocationDistance(10)
circle.fillColor = UIColor.blue
circle.isTappable = true
circle.map = mapView
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func mapView(_ mapView: GMSMapView, didTapCircle: GMSCircle) {
performSegue(withIdentifier: "goTalk", sender: nil)
}
}
I checked what was happening and I noticed that as you said, it didn't work.
But according to this answer, I noticed that you need this other delegate method:
func mapView(_ mapView: GMSMapView, didTap overlay: GMSOverlay) {
performSegue(withIdentifier: "goTalk", sender: nil)
}
This works propertly.
I am trying to construct a Map App that can receive user inputs of latitude and longitude coordinates that when entered, will place a pin on a map in a different tab. My FirstVC consists of a button "Add Locations" that segues to OtherVC which the user can input the coordinates. SecondVC consists of the MapView. My initial idea is to have a specific array of coordinates, and any new coordinates will be appended to this array. The execution is where I am lacking, because I am not sure how to transfer this array to the MapView. Here is what I have so far:
For the input of coordinates:
import UIKit
import CoreLocation
class OtherVC: UIViewController {
#IBOutlet weak var latitudeField: UITextField!
#IBOutlet weak var longitudeField: UITextField!
var coordinates = [CLLocationCoordinate2D]()
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addToMap(_ sender: Any) {
let lat = Double(latitudeField.text!)
let long = Double(longitudeField.text!)
self.coordinates.append(CLLocationCoordinate2D(latitude: lat!, longitude: long!))
}
}
For the MapView:
import UIKit
import MapKit
class MapViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
var coordinates = [CLLocationCoordinate2D]() {
didSet {
// Update the pins
// Since it doesn't check for which coordinates are new, it you go back to
// the first view controller and add more coordinates, the old coordinates
// will get a duplicate set of pins
for (index, coordinate) in self.coordinates.enumerated() {
let annotation = MKPointAnnotation()
annotation.coordinate = coordinate
annotation.title = "Location \(index)"
mapView.addAnnotation(annotation)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "pinAnnotation"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKPinAnnotationView
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView?.canShowCallout = true
}
annotationView?.annotation = annotation
return annotationView
}
}
I think you need is get your second ViewController MapViewController from your tabBarController and then pass the coordinates array, so in your addToMap Action replace with this
#IBAction func addToMap(_ sender: Any) {
let lat = Double(latitudeField.text!)
let long = Double(longitudeField.text!)
self.coordinates.append(CLLocationCoordinate2D(latitude: lat!, longitude: long!))
//here we pass the coordinate array to mapViewController
if let mapViewController = self.tabBarController?.viewControllers?[1] as? MapViewController
{
mapViewController.coordinates = self.coordinates
}
}
You need also add a navigation controller, like in the picture
I hope this helps you
Generally speaking I only use the method of directly passing the data from one ViewController to the next if there is a parent child relationship and I can do it in prepareForSegue or in and unwind (child to parent). Otherwise I think its better to use the publisher subscriber model with Notification. When your coordinate changes you post a Notification:
NotificationCenter.default.post(name: NSNotification.Name("MapViewController.coordinate.updated"), object: self, userInfo: nil)
Now anyone who cares about MapViewController's coordinates changing can listen:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(coordinateUpdated), name: NSNotification.Name("coordinate.updated"), object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
#objc private func coordinateUpdated( notification: Notification) {
if let source = notification.object as? MapViewController {
print(source.coordinates)
}
}
This makes the views loosely coupled and MapViewController doesn't need to care about who needs to be updated; the subscribers are responsible for registering themselves.
import UIKit
import GoogleMaps
import CoreLocation
class ViewController: UIViewController {
let locationManager = CLLocationManager()
var mapView: GMSMapView?
override func viewDidLoad() {
super.viewDidLoad()
GMSServices.provideAPIKey("XYZ")
let camera = GMSCameraPosition.cameraWithLatitude(37.621262,longitude: -122.378945, zoom: 12)
mapView = GMSMapView.mapWithFrame(CGRectZero, camera: camera)
view = mapView
}
}
What should I add to this code?
This should help you,Please check.
import UIKit
import GoogleMaps
import CoreLocation
class ViewController: UIViewController {
let locationManager = CLLocationManager()
var mapView: GMSMapView?
override func viewDidLoad()
{
super.viewDidLoad()
GMSServices.provideAPIKey("XYZ")
let camera = GMSCameraPosition.camera(withLatitude: 37.621262,longitude: -122.378945, zoom: 12)
mapView = GMSMapView.map(withFrame: CGRect.init(), camera: camera)
// Below line is to default show user current location(Bue dot with circular accuracy radius around it)
mapView!.isMyLocationEnabled = true
view = mapView
}
}
Hope that helps.