I am using Mapbox Maps iOS to create a custom 3d pucker and use that to display the user's location.
Everything seems to be working, the camera redirects to the user's location, the pucker successfully loads. However, I am having trouble setting the pucker's location to the user's location.
You can see that I am required to set the location of the pucker in the Model instance in the loadCharacter() function. But I am unsure of how to link that to the user's current location.
Please help!!!
// ViewController.swift
import UIKit
import MapboxMaps
public class ViewController: UIViewController {
internal var mapView: MapView!
internal var cameraLocationConsumer: CameraLocationConsumer!
override public func viewDidLoad() {
super.viewDidLoad()
let options = MapInitOptions(cameraOptions: CameraOptions(zoom: 16))
mapView = MapView(frame: view.bounds, mapInitOptions: options)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(mapView)
mapView.mapboxMap.onNext(.styleLoaded) { _ in
self.loadCharacter()
}
cameraLocationConsumer = CameraLocationConsumer(mapView: mapView)
mapView.mapboxMap.onNext(.mapLoaded) { _ in
self.mapView.location.addLocationConsumer(newConsumer: self.cameraLocationConsumer)
}
}
internal func loadCharacter() {
let uri = Bundle.main.url(forResource: "race_car_model",
withExtension: "gltf")
// Instantiate the model
let myModel = Model(uri: uri,
position: [-77.150925, 39.085006],
orientation: [0, 0, 180])
// Setting an expression to scale the model based on camera zoom
let scalingExpression = Exp(.interpolate) {
Exp(.linear)
Exp(.zoom)
0
Exp(.literal) {
[256000.0, 256000.0, 256000.0]
}
4
Exp(.literal) {
[40000.0, 40000.0, 40000.0]
}
8
Exp(.literal) {
[2000.0, 2000.0, 2000.0]
}
12
Exp(.literal) {
[100.0, 100.0, 100.0]
}
16
Exp(.literal) {
[7.0, 7.0, 7.0]
}
20
Exp(.literal) {
[1.0, 1.0, 1.0]
}
}
let configuration = Puck3DConfiguration(model: myModel, modelScale: .expression(scalingExpression))
mapView.location.options.puckType = .puck3D(configuration)
}
}
// Create class which conforms to LocationConsumer, update the camera's centerCoordinate when a locationUpdate is received
public class CameraLocationConsumer: LocationConsumer {
weak var mapView: MapView?
init(mapView: MapView) {
self.mapView = mapView
}
public func locationUpdate(newLocation: Location) {
mapView?.camera.ease(
to: CameraOptions(center: newLocation.coordinate, zoom: 16, pitch: 50.0),
duration: 1.3)
}
}
Mapbox has a repo with examples of navigation use cases that you can try, including 3D puck sample.
Note that this sample uses API from Navigation SDK, which in turn uses Maps SDK API internally. Fortunately, Navigation SDK is open-sourced, you can always look at how it draws current user location here.
Related
I want to plot a specific point on the map having the lat and lon from an api.
Program flow:
Get LAT & LON from api (done)
Ping api again via timer after every 5 seconds to get the latest location (done)
Plot location with retrieved LAT & LON on map
The issue is every code on the net has to do with 2 points, so user loc and destination loc. I cant seem to get it to work without user loc. I have however coded this to plot the location. However, with this when I touch the map, the map zooms out. Another issue is when I get another point the previous one also remains on the screen. (for testing purpose I hard coded the lat and lon but when I connect the api code that refreshes, prior points remain and the map code is the same as this. The lat and lon are passed via func parameters in createAnnotation().)
My code:
import UIKit
import MapKit
class ViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self // or connect in storyboard
createAnnotation()
}
func createAnnotation(){
let annotations = MKPointAnnotation()
annotations.coordinate = CLLocationCoordinate2D(latitude: 41.87369, longitude: -87.813293)
mapView.addAnnotation(annotations)
}}
How Do I plot the coordinates properly? and then delete the prior and show the new one?.
For the "previous one also remains on the screen" problem: don't keep making a new annotation and calling addAnnotation if you don't want to keep adding new annotations. Instead, keep hold of the annotation that you add, and move it later using its coordinate property. Something like this maybe:
class ViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
var annotationForThing: MKPointAnnotation?
var coordinateOfThing: CLLocationCoordinate2D? {
didSet {
guard let newCoord = coordinateOfThing else {
if let existing = annotationForThing {
mapView.removeAnnotation(existing)
}
return
}
if let existing = annotationForThing {
existing.coordinate = coordinateOfThing
}
else {
let newAnnotation = MKPointAnnotation()
newAnnotation = coordinateOfThing
mapView.addAnnotation(newAnnotation)
annotationForThing = newAnnotation
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self // or connect in storyboard
}
I'm getting this error:
This application has been blocked by the Google Maps API. This might be because of an incorrectly registered key.
When I try to set google maps panorama street view. If I display a regular map view, it works so seems like the key is registered properly.
I followed the directions for a panorama view exactly as per google docs:
import UIKit
import GoogleMaps
class ViewController: UIViewController, GMSMapViewDelegate {
override func loadView() {
let panoView = GMSPanoramaView(frame: .zero)
self.view = panoView
panoView.moveNearCoordinate(CLLocationCoordinate2D(latitude: -33.732, longitude: 150.312))
}
}
Any ideas?
For me, the solution was that I needed to link my project in Google Cloud Platform to a billing account.
Once I did that the street view started working correctly.
The standard Google map worked fine without a billing account, which confused me initially.
You need to configure the delegate for this object. Something like:
import UIKit
import GoogleMaps
class ViewController: UIViewController, GMSMapViewDelegate {
override func loadView() {
let panoView = GMSPanoramaView(frame: .zero)
panoView.delegate = self
self.view = panoView
panoView.moveNearCoordinate(CLLocationCoordinate2D(latitude: -33.732, longitude: 150.312))
}
}
extension ViewController: GMSPanoramaViewDelegate {
func panoramaView(_ view: GMSPanoramaView, error: Error, onMoveNearCoordinate coordinate: CLLocationCoordinate2D) {
print("error: \(error.localizedDescription)")
}
}
I had the implementation of place auto completion using GMSAutocompleteResultsViewController was working fine so far. But when I updated the sdk to 3.0.2 stopped working. If I revert to version 2.7.0 start working.
I have gone through the migrationguide not getting what I am missing. I checked the examples didn't observe any changes. Would anyone point me in the right direction to make it work again?
class PlaceAutoCompleteController: BaseViewController {
//MARK: - Property declaration
private var resultsViewController = GMSAutocompleteResultsViewController()
private lazy var searchController = UISearchController(searchResultsController: resultsViewController)
private var viewModel: PlaceTypeAheadViewModel
var storeSelectionDelegate: StoreSelectionDelegate?
//MARK: - Life cycle
init(withViewModel aviewModel: PlaceTypeAheadViewModel) {
viewModel = aviewModel
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
setupProperties()
}
}
//MARK: - Property setup
extension PlaceAutoCompleteController {
/// Setting up view default properties
func setupProperties() {
resultsViewController.view.backgroundColor = UIColor.white
resultsViewController.tableCellBackgroundColor = .white
resultsViewController.delegate = self
// Specify the place data types to return.
// let fields: GMSPlaceField = GMSPlaceField(rawValue: UInt(GMSPlaceField.name.rawValue) |
// UInt(GMSPlaceField.placeID.rawValue))!
// resultsViewController.placeFields = fields
let filter = GMSAutocompleteFilter()
//suitable filter type
filter.type = .establishment
filter.country = viewModel.getCountryId()
resultsViewController.autocompleteFilter = filter
searchController.searchResultsUpdater = resultsViewController
// Put the search bar in the navigation bar.
searchController.searchBar.sizeToFit()
searchController.searchBar.placeholder = "Search places"
let searchBgView = UIView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: 44))
searchBgView.addSubview(searchController.searchBar)
view.addSubview(searchBgView)
// Prevent the navigation bar from being hidden when searching.
searchController.hidesNavigationBarDuringPresentation = false
// This makes the view area include the nav bar even though it is opaque.
// Adjust the view placement down.
extendedLayoutIncludesOpaqueBars = false
edgesForExtendedLayout = []
}
}
// Handle the user's selection.
extension PlaceAutoCompleteController: GMSAutocompleteResultsViewControllerDelegate {
func resultsController(_ resultsController: GMSAutocompleteResultsViewController,
didAutocompleteWith place: GMSPlace) {
searchController.isActive = false
AppManager.setPlace(withPlaceId: place.placeID ?? "" , name: place.name ?? "" , latitude: place.coordinate.latitude, longitude: place.coordinate.longitude)
openStoreSelection()
}
func resultsController(_ resultsController: GMSAutocompleteResultsViewController,
didFailAutocompleteWithError error: Error){
// TODO: handle the error.
print("Error: ", error.localizedDescription)
}
// Turn the network activity indicator on and off again.
func didRequestAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
func didUpdateAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
/// Open store selection screen with selected country
func openStoreSelection() {
navigateToStoreSelection()
}
}
Thanks in advance.
In your Google Cloud project:
Make sure you enabled "Places API". The old one "Places SDK for iOS" does not work with 3.0.0 version of SDK.
Update API restrictions for your API key with "Places API" instead of "Places SDK for iOS"
Setup billing details for your Google Cloud project
Got the access from business people, accepted the privacy policy and I couldn't find the appkey in credentials which they have shared earlier.
Now I started using iOS key (auto created by Google Service) which working perfectly fine with updated sdk version i.e 3.0.3
EnvironmentXcode 8Swift 3
Problem Statement
I want to be able to determine if a user taps on a MKPointAnnotation and then extract information (like title and subtitle) from that annotation for use within my app.
I imagine this is not terribly difficult, but I'm a bit lost in terms of what I need to do / what various classes / objects / methods / etc. I need to use to do this.So I'm looking for pointers / guidance - code is welcome, but at this point the pointers / guidance would be a significant step forward for me.
Code SnippetsAbridged version of the code thus far (trying to limit it to just the relevant pieces)
class NewLocationViewController: UIViewController, CLLocationManagerDelegate, UITextFieldDelegate {
//... various #IBOutlet's for text fields, buttons, etc. ...
#IBOutlet weak var map: MKMapView!
var coords: CLLocationCoordinate2D?
var locationManager: CLLocationManager = CLLocationManager()
var myLocation: CLLocation!
var annotation: MKPointAnnotation!
var annotationList: [MKPointAnnotation] = []
var matchingItems: [MKMapItem] = [MKMapItem]()
override func viewDidLoad() {
super.viewDidLoad()
//... text field delegates, and other initilizations ...
locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.delegate = self
}
myLocation = nil
//... other initializations...
}
// Search for things that match what my app is looking for ("<search string>")
func performSearch() {
annotationList.removeAll() // clear list
matchingItems.removeAll() // clear list
var closest = MKMapItem()
var distance = 10000.0
let request = MKLocalSearchRequest()
let span = MKCoordinateSpan(latitudeDelta: 0.001, longitudeDelta: 0.001)
request.naturalLanguageQuery = "<search string>"
request.region = MKCoordinateRegionMake(myLocation.coordinate, span)
let search = MKLocalSearch(request: request)
if search.isSearching {
search.cancel()
}
search.start(completionHandler: {
(_ response, _ error) in
if error != nil {
self.showAlert(msg: "Error occurred in search\nERROR: \(error?.localizedDescription)")
}
else if response!.mapItems.count == 0 {
self.showAlert(msg: "No matches found")
}
else {
for item in response!.mapItems {
// Track the closest placemark to our current [specified] location
let (distanceBetween, prettyDistance) = self.getDistance(loc1: self.myLocation, loc2: item.placemark.location!)
let addrObj = self.getAddress(placemark: item.placemark)
//... some code omitted ...
// Add markers for all the matches found
self.matchingItems.append(item as MKMapItem)
let annotation = MKPointAnnotation()
annotation.coordinate = item.placemark.coordinate
annotation.title = item.name
annotation.subtitle = "\(addrObj.address!) (\(prettyDistance))"
self.map.addAnnotation(annotation)
self.annotationList.append(annotation)
}
//... some code omitted ...
}
})
}
//... code for getDistance(), getAddress() omitted for brevity - they work as designed ...
//... other code omitted as not being relevant to the topic at hand
}
I imagine that I will need to override touchesEnded and possibly touchesBegan and maybe touchesMoved in order to detect the tap.
What I cannot figure out is how to compare a touch's location (represented as X/Y coordinates on the screen) to an MKPointAnnotation's or MKMapItem's location (which is represented as latitude/longitude coordinates on a map)
So - that's kind of where I'm currently stuck. I searched various terms on the web but wasn't able to find anything that [simplisticly] answerwed my question - and in Swift code format (there were a number of postings that looked like they might help, but the code presented wasn't in Swift and I don't do the translation that easily).
UPDATE (19:48 ET)
I found this article: How do I implement the UITapGestureRecognizer into my application and tried to follow it, but ...
I modified the code a bit (added UIGestureRecognizerDelegate):
class NewLocationViewController: UIViewController, CLLocationManagerDelegate, UITextFieldDelegate, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
//...other code...
let tapHandler = UITapGestureRecognizer(target: self, action: Selector(("handleTap:"))) //<<<== See notes below
tapHandler.numberOfTapsRequired = 1
tapHandler.numberOfTouchesRequired = 1
tapHandler.delegate = self
print("A")//#=#
map.addGestureRecognizer(tapHandler)
map.isUserInteractionEnabled = true
print("B")//#=#
}
func handleTap(tap: UITapGestureRecognizer) {
print("ARRIVED")//#=#
let here = tap.location(in: map)
print("I AM HERE: \(here)")//#=#
}
//...
}
With regard to the declaration / definition of tapHandler, I tried the following:
let tapHandler = UITapGestureRecognizer(target: self, action: "handleTap:")
let tapHandler = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
let tapHandler = UITapGestureRecognizer(target: self, action: Selector(("handleTap:"))) // supresses warning
The first two caused a warning to show up in Xcode, the last simply supresses the warning:
[W] No method declared with Objective-C selector 'handleTap:'
When I run my app and tap on a pin - I get the following in my log:
A
B
libc++abi.dylib: terminating with uncaught exception of type NSException
Which would seem (to me) to indicate that the general setup in viewDidLoad is okay, but as soon as it tries to handle the tap, it dies without ever getting to my handleTap function - and thus the warning (shown above) would seem to be far more serious.
So, I'm not sure if I can count this as making progress, but I'm trying...
Thanks to this MKAnnotationView and tap detection I was able to find a solution. My code changes from those originally posted:
class NewLocationViewController: UIViewController, CLLocationManagerDelegate, UITextFieldDelegate, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
//...other code...
let tapHandler = UITapGestureRecognizer() //<<<== No parameters
tapHandler.numberOfTapsRequired = 1
tapHandler.numberOfTouchesRequired = 1
tapHandler.delegate = self
map.addGestureRecognizer(tapHandler)
map.isUserInteractionEnabled = true
}
// Not sure who calls this and requires the Bool response, but it seems to work...
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return self.handleTap(touch: touch).count > 0
}
// Major Changes
private func handleTap(touch: UITouch) -> [MKAnnotationView] {
var tappedAnnotations: [MKAnnotationView] = []
for annotation in self.map.annotations {
if let annotationView: MKAnnotationView = self.map.view(for: annotation) {
let annotationPoint = touch.location(in: annotationView)
if annotationView.bounds.contains(annotationPoint) {
self.name.text = annotationView.annotation?.title!
let addr = AddrInfo(composite: ((annotationView.annotation?.subtitle)!)!)
self.address.text = addr.street!
self.city.text = addr.city!
self.state.text = addr.state!
self.zipcode.text = addr.zip!
tappedAnnotations.append(annotationView)
break
}
}
}
return tappedAnnotations
}
//...
}
The AddrInfo piece is my own little subclass that, among other things, takes a string like "1000 Main St., Pittsburgh, PA 15212, United States" and breaks it into the individual pieces so that they can be accessed, well, individually (as indicated in the code above).
There might be an easier, or better, way to achieve what I was looking for - but the above does achieve it, and so I consider it to be the answer for my issue.
When I receive in my Bluetooth class (new) values from my device, then I call a delegate. So the Bluetooth class is running in the background.
My protocol is simple:
protocol RefreshPositionInDrive {
func changingValue(latitude: Double, longitude: Double)
}
In my UIViewController I initialize a map. When I worked at the beginning without delegates this code works fine.
func initializeMapResolution() {
let regionRadius: CLLocationDistance = 1000
let initialLocation = CLLocation(latitude: 50.910349, longitude: 8.066895)
let coordinateRegion = MKCoordinateRegionMakeWithDistance(initialLocation.coordinate,
regionRadius * 1.5, regionRadius * 1.5)
MapDrive.setRegion(coordinateRegion, animated: true)
MapDrive.delegate = self
}
My method from my protocol:
func changingValue(latitude: Double,longitude: Double) {
print("THE NEW COORDINATES \(latitude) \(longitude)")
if self.MapDrive == nil {
print("Is nil")
} else {
updateTheMap()
}
}
Output:
THE NEW COORDINATES 25.012x 16.992
Is nil
But I don't understand that. I initialize my map first. After that the changingValue is called. How can be the MapDrive nil?
I tested the code without delegates, just with some fix coordinates in my UIViewController and the annotation appears.
(I'm working the first time with delegates.)
EDIT
I was indistinctly: My MapDrive:
#IBOutlet weak var MapDrive: MKMapView!
So I can't instantiate like you mean or?
You'll want to reference a MapDrive instance to the UIViewController, your MapDrive is probably released when your function ends.
class UIViewController {
var mapDrive = MapDrive()
func initializeMapResolution() {
// Instantiate map drive, assign it to self.mapDrive
//then assign the delegate of the property on self.
self.mapDrive.delegate = self
}
}