I'm using Google Maps SDK to embed maps on my application. I have also enabled maps recenter icon using:
extension DashboardVC: GMSMapViewDelegate {
func mapView(_ mapView: GMSMapView, willMove gesture: Bool) {
if !change{
googleMapView.settings.myLocationButton = true
ColorLocationButton()
}
change = false
}
func didTapMyLocationButton(for mapView: GMSMapView) -> Bool {
googleMapView.settings.myLocationButton = false
change = true
return false
}
func mapView(_ mapView: GMSMapView, didTapMyLocation location: CLLocationCoordinate2D) {
googleMapView.settings.myLocationButton = false
}
}
How can I add light and dark mode to this button rendering from Google Maps itself?
First Add a View and Add the Image inside a View
Then Make The Outlets on Your Controllers
#IBOutlet weak var recenterView: UIView!
#IBOutlet weak var hamburgerImage: UIImageView!
if Your View On the Google Maps View Must add these Line of Code in Your viewWillAppear
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
googleMapView.settings.myLocationButton = false
self.googleMapView.bringSubviewToFront(self.recenterView)
recenterView.isUserInteractionEnabled = true
let tapOnRecenter = UITapGestureRecognizer(target: self, action: #selector(recenterTheMap(gesture:)))
tapOnRecenter.delegate = self
recenterView.addGestureRecognizer(tapOnRecenter)
// Setup The Recenter View For Corner Radius and Shadow
recenterView.clipsToBounds = true
recenterView.layer.cornerRadius = recenterView.frame.size.width / 2
recenterView.layer.shadowColor = UIColor(ciColor: .gray).cgColor
recenterView.layer.shadowRadius = 12
}
your Recenter Button Code
#objc func recenterTheMap(gesture: UITapGestureRecognizer){
googleMapView.camera = GMSCameraPosition.camera(withTarget: marker.position, zoom: 15)
}
Do not Forget the Add Gestures Delegate
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
Detect Light or DarkMode Through This Delegate Method
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if #available(iOS 13.0, *) {
if self.traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
if traitCollection.userInterfaceStyle == .light {
recenterView.tintColor = .white
}
else {
recenterView.tintColor = .black
}
}
} else {
// Fallback on earlier versions
}
}
Here is Your Code For Current Location
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
location: CLLocation = locations.last!
}
Related
The following code able to detect tap location
Able to detect tap location
class ViewController: UIViewController {
private lazy var gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tap))
#objc func tap(_ gestureRecognizer: UITapGestureRecognizer) {
let location = gestureRecognizer.location(in: gestureRecognizer.view)
print("location \(location)")
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
self.view.addGestureRecognizer(gestureRecognizer)
}
}
However, we want to detection done on the top level Window, globally, without tied to a single view controller's view.
Not able to detect tap location
extension UIWindow {
static var key: UIWindow! {
if #available(iOS 13, *) {
return UIApplication.shared.windows.first { $0.isKeyWindow }
} else {
return UIApplication.shared.keyWindow
}
}
}
class ViewController: UIViewController {
private lazy var gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tap))
#objc func tap(_ gestureRecognizer: UITapGestureRecognizer) {
let location = gestureRecognizer.location(in: gestureRecognizer.view)
print("location \(location)")
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
//self.view.addGestureRecognizer(gestureRecognizer)
UIWindow.key.addGestureRecognizer(gestureRecognizer)
}
}
Doesn't matter where do you tap, we always get
location (0.0, 0.0)
location (0.0, 0.0)
Does anyone know why it is so, and how can we resolve such? Thanks.
I don't know exactly why passing the window to location(in:) always gives you (0, 0). This is as if the window is not in the same view hierarchy, as if you did location(in: UIView()).
Anyway, note that the documentation says that you should pass nil to location(in:) to indicate that you want the location in the coordinate system of the window:
Parameters
view
A UIView object on which the gesture took place. Specify nil to indicate the window.
I suppose this could be interpreted as "you should not pass the window to location(in:), use nil to do that instead".
This works:
let location = gestureRecognizer.location(in: nil)
print("location \(location)")
You always need a view to add the gesture to , Replace
UIWindow.key.addGestureRecognizer(gestureRecognizer)
with
UIWindow.key.subviews.last?.addGestureRecognizer(gestureRecognizer)
I have Apple's mapView and custom markers displayed in it. Each marker has 2 images in Assets for the dark and light mode appropriately. The problem is that markers do not react to the dark/light mode theme changes. What I've tried:
reload input views of mapView inside method traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
resetting image in the marker view class inside method traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
SO, how can I update markers when the theme changes?
I've just found the same thing, so I've modified my MKAnnotationView-derived class to handle it:
class MapPinView: MKAnnotationView {
// MKAnnotationView doesn't seem to automatically use the correct image for dark mode, so we have to ensure it does
var combinedImage: UIImage? {
didSet {
updateImage()
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if let imageName = selected ? "pushPinSelected" : "pushPin",
let image = UIImage(named: imageName) {
self.combinedImage = image
}
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.userInterfaceStyle != previousTraitCollection?.userInterfaceStyle {
updateImage()
}
}
private func updateImage() {
image = combinedImage?.imageAsset?.image(with: traitCollection) ?? combinedImage
if let image = self.image {
centerOffset = CGPoint(x: 0.0, y: -image.size.height / 2.0)
}
}
}
Note that I also initialise the combinedImage, when creating the MapPinView:
extension MapView: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: Constants.annotationId, for: annotation) as! MapPinView
annotationView.combinedImage = UIImage(named: "pushPin")
return annotationView
}
...
I'm trying to implement HERE maps SDK gestures. I added delegate and didRecivePan function. The pan gesture is recognized by the application.
Problem is that my mapView gets frozen for the time my pan gesture is being used and it creates lagging like map scrolling which makes UX really annoying. Is there anyway this can be resolved.
var follow : Bool = true;
#IBOutlet weak var mapView: NMAMapView!
let positionManager = NMAPositioningManager.self
let gestureMarker = NMAMapMarker.self
override func viewDidLoad() {
super.viewDidLoad()
mapView.gestureDelegate = self
}
#objc func didLosePosition(){ print("Position lost")}
#objc func positionDidUpdate(){
/* Possible solution but an ugly one!
if(follow == true){
mapView.gestureDelegate = self
}else if(follow == false){
mapView.gestureDelegate = nil
}*/
print("Follow \(follow)")
print("position updated")
let position = positionManager.sharedInstance().currentPosition
print(position!.coordinates ?? "xx")
if(follow){
mapView.set(geoCenter:(position?.coordinates)!,animation: .linear)
}
}
override func viewWillAppear(_ animated: Bool) {
if(positionManager.sharedInstance().startPositioning()){
// Register to positioning manager notifications
NotificationCenter.default.addObserver(self, selector: #selector(self.positionDidUpdate), name: NSNotification.Name.NMAPositioningManagerDidUpdatePosition, object: positionManager.sharedInstance())
NotificationCenter.default.addObserver(self, selector: #selector(self.didLosePosition), name: NSNotification.Name.NMAPositioningManagerDidLosePosition, object: positionManager.sharedInstance())
}
super.viewWillAppear(animated)
mapView.zoomLevel = 17
mapView.set(geoCenter: NMAGeoCoordinates(latitude: 45.921189263505788, longitude: 14.234863696633125),
animation: .linear)
let mapMarker = NMAMapMarker(geoCoordinates: NMAGeoCoordinates(latitude: 45.921189263505788, longitude: 14.234863696633125), icon: NMAImage(uiImage: UIImage(named: "driver") ?? UIImage())!)
mapView.positionIndicator.isVisible = true;
mapView.positionIndicator.isAccuracyIndicatorVisible = true
mapView.positionIndicator.set(displayObject: mapMarker, toLayer: NMAMapLayerType.foreground)
mapView.copyrightLogoPosition = NMALayoutPosition.centerRight
}
func mapView(_ mapView: NMAMapView, didReceivePan translation: CGPoint, at location: CGPoint) {
follow = false
}
#IBAction func followButton(_ sender: UIButton) {
follow = true
mapView.set(geoCenter: (positionManager.sharedInstance().currentPosition?.coordinates)!,
animation: .linear)
mapView.zoomLevel = 17
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I've implemented three methods of GMSMapViewDelegate, of which two (markerInfoWindow, willMove) are getting called as expected while didTapMyLocationButtonForMapView is never triggered when I click the 'My Location' button.
Initialization:
class MapViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
GMSServices.provideAPIKey("KEY")
mapView = GMSMapView(frame: view.frame)
mapView.settings.myLocationButton = true
mapView.padding = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
mapView.isMyLocationEnabled = true
mapView.delegate = self
view.insertSubview(mapView, at: 0)
}
}
Delegate implementation:
extension MapViewController: GMSMapViewDelegate {
func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? {
...
return infoView
}
func mapView(_ mapView: GMSMapView, willMove gesture: Bool) {
...
}
func didTapMyLocationButtonForMapView(mapView: GMSMapView!) -> Bool {
...
return false
}
}
I tried to:
Initialize in viewDidLoad.
Change the order of the methods in viewDidAppear (for example, setting the delegate after inserting the view).
Set the class in storyboard to be GMSMapView rather than adding in programmatically.
But nothing seems to help. Any ideas?
Missing _
func didTapMyLocationButtonForMapView(_ mapView: GMSMapView!) -> Bool {
...
return false
}
Swift 5
func didTapMyLocationButton(for mapView: GMSMapView) -> Bool {
return false //default move map to my location
}
I am working on a MapView where on click of any custom annotation pin, I am showing custom callout view (load from xib file).
From this Custom Callout I have an UIButton, I already can detect click on this button but I want to access on the Map like the : view?.rightCalloutAccessoryView in the basic callout.
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView, {
if view.annotation!.isKind(of: MKUserLocation.self){
return
}
let customView = (Bundle.main.loadNibNamed("CustomCalloutView", owner: self, options: nil))?[0] as! CustomCalloutView;
let calloutViewFrame = customView.frame;
customView.frame = CGRect(x: -calloutViewFrame.size.width/2.23, y: -calloutViewFrame.size.height+10, width: 315, height: 170)
view.addSubview(customView)
let region = MKCoordinateRegion(center: pinToZoomOn!.coordinate, span: span)
mapView.setRegion(region, animated: true)
}
The route is correctly calculated from the classic callout but I can't know how to access my map from the button of my custom callout.
My CustomCalloutViewClass :
import UIKit
import MapKit
class CustomCalloutView: MKAnnotationView {
#IBOutlet weak var goButton: UIButton!
#IBAction func goButton(_ sender: AnyObject) {
print("Button clicked sucessfully")
}
// MARK: - Detect taps on callout
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let hitView = super.hitTest(point, with: event)
if hitView != nil {
superview?.bringSubview(toFront: self)
}
return hitView
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let rect = self.bounds
var isInside = rect.contains(point)
if !isInside {
for view in subviews {
isInside = view.frame.contains(point)
if isInside {
break
}
}
}
return isInside
}
}
If someone have an idea it will be helpfull I'm stuck on this issue.
Thank you in advance.
Option 1: Capture a MKMapView instance in the closure passed to a CustomCalloutView
Add the closure which will be called on the button action. The closure will capture the MKMapView instance and you will be able to us is inside.
class CustomCalloutView: MKAnnotationView {
var didTapGoButton: (() -> Void)?
#IBAction func goButton(_ sender: AnyObject) {
didTapGoButton?()
}
}
Option 2: Add a weak reference to MKMapView as a property of the callout
This is not a clean solution but it may be an option under some circumstances. You only have to create a weak property storing a reference to MKMapView instance in CustomCalloutView
class CustomCalloutView: MKAnnotationView {
weak var mapView: MKMapView?
}
Configuration
This is how you can wire up the CustomCalloutView for both solutions. Remember to use swift capture list to capture a weak reference to a MKMapView instance. Without it you may create a strong reference cycle.
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
// ...
let customView = (Bundle.main.loadNibNamed("CustomCalloutView", owner: self, options: nil))?[0] as! CustomCalloutView;
// Option 1
customView.didTapGoButton = { [weak mapView ] in
print(mapView?.annotations.count)
}
// Option 2
customView.mapView = mapView
// ...
}
Thank you so much for your help !
I used the first option to capture the MKMapView instance.
If someone is stuck on the same issue you can use the first option and add this in your didSelect MapView function:
let selectedLoc = view.annotation
print("Annotation '\(String(describing: selectedLoc?.title!))' has been selected")
let location = view.annotation as! YourCustomClassType
let launchOptions = [MKLaunchOptionsDirectionsModeKey : MKLaunchOptionsDirectionsModeDriving]
customView.didTapGoButton = { location.mapItem().openInMaps(launchOptions: launchOptions) }
Thank's
var didTapGoButton: (() -> Void)? this types of closures are very helpful for navigation from the view to viewControllers through the button actions in the call layoutviews in the mkmapview.