I'm currently building an Uber clone with Swift and Firebase, following Stephan Dowless' course on Udemy to learn more about MapKit, and so far it's going well, but I'm struggling to add a polyline overlay to the map, showing the route from the user's current location to the annotation (which is added to the mapView by tapping one of the search results displayed in a tableView).
I've looked for other similar questions on SO but haven't found anything that answers my question. I've also tried cloning other projects that use this feature (the completed Uber clone from the Udemy course, this Ray Wenderlich tutorial and this article on polylines using SwiftUI) to check whether it's my code that's the problem but they all present the same problem i.e. the annotations appear on the screen but the overlay doesn't appear at all.
In my app, tapping "Starbucks" on the tableView in this screen produces this screen (showing the annotation for Starbucks and user's current location, but no overlay).
Similarly, running the SwiftUI MapKit tutorial app from Medium mentioned previously shows this (both annotations but no overlay).
This leads me to believe that it's something wrong on my side. I've also tried running these apps on my phone (iPhone 7) with the same issues.
Here are the relevant lines of code:
Declaration of properties including route and mapView
// MARK:- Properties
private let mapView = MKMapView()
private var searchResults = [MKPlacemark]()
private var route: MKRoute?
mapView delegate set to self (function called when user is confirmed to be logged in)
private func configureMapView() {
view.addSubview(mapView)
mapView.frame = view.frame
mapView.delegate = self
mapView.showsUserLocation = true
mapView.userTrackingMode = .follow
}
MapView Functions
// MARK:- MapView Functions
extension HomeViewController: MKMapViewDelegate {
private func generatePolyline(toDestination destination: MKMapItem) {
let request = MKDirections.Request()
request.source = MKMapItem.forCurrentLocation()
request.destination = destination
request.transportType = .automobile
let directionRequest = MKDirections(request: request)
directionRequest.calculate { (response, error) in
guard let response = response else { return }
self.route = response.routes[0]
guard let polyline = self.route?.polyline else { return }
self.mapView.addOverlay(polyline)
}
}
private func searchBy(naturalLanguageQuery: String, completion: #escaping([MKPlacemark]) -> Void) {
var results = [MKPlacemark]()
let request = MKLocalSearch.Request()
request.region = mapView.region
request.naturalLanguageQuery = naturalLanguageQuery
let search = MKLocalSearch(request: request)
search.start { (response, error) in
guard let response = response else { return }
response.mapItems.forEach { (item) in
results.append(item.placemark)
}
completion(results)
}
}
// Change driver annotation appearance to Uber arrow
public func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if let annotation = annotation as? DriverAnnotation {
let view = MKAnnotationView(annotation: annotation, reuseIdentifier: DriverAnnotation.identifier)
view.image = #imageLiteral(resourceName: "chevron-sign-to-right")
return view
}
return nil
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let route = self.route {
let polyline = route.polyline
let lineRenderer = MKPolylineRenderer(overlay: polyline)
lineRenderer.strokeColor = .mainBlueTint
lineRenderer.lineWidth = 4
return lineRenderer
}
return MKOverlayRenderer()
}
}
TableView didSelectRowAt Method
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let placemark = self.searchResults[indexPath.row]
configureActionButtonState(config: .dismissActionView)
let destination = MKMapItem(placemark: placemark)
self.generatePolyline(toDestination: destination)
self.dismissInputView { _ in
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.coordinate
self.mapView.addAnnotation(annotation)
self.mapView.selectAnnotation(annotation, animated: true)
}
}
Finally, here's the whole view controller, just in case I've missed anything above. Please let me know if any further information is required to answer the question and I'll provide it.
Entire view controller
import UIKit
import Firebase
import MapKit
private enum ActionButtonConfiguration {
case showMenu
case dismissActionView
init() {
self = .showMenu
}
}
class HomeViewController: UIViewController {
// MARK:- Properties
private let mapView = MKMapView()
private let locationManager = LocationHandler.shared.locationManager
private let inputActivationView = LocationInputActivationView()
private let locationInputView = LocationInputView()
private var searchResults = [MKPlacemark]()
private var actionButtonConfig = ActionButtonConfiguration()
private var route: MKRoute?
private let tableView = UITableView()
private var user: User? {
didSet {
locationInputView.user = user
}
}
private let actionButton: UIButton = {
let button = UIButton()
button.setImage(#imageLiteral(resourceName: "baseline_menu_black_36dp").withRenderingMode(.alwaysOriginal), for: .normal)
button.addTarget(self, action: #selector(didTapActionButton), for: .touchUpInside)
return button
}()
// MARK:- Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
checkIfUserLoggedIn()
locationManagerDidChangeAuthorization(locationManager!)
}
// MARK:- Selectors
#objc private func didTapActionButton() {
switch actionButtonConfig {
case .showMenu:
print("Show menu")
case .dismissActionView:
removeAnnotationsAndOverlays()
UIView.animate(withDuration: 0.3) {
self.configureActionButtonState(config: .showMenu)
self.inputActivationView.alpha = 1
}
}
}
// MARK:- API
private func fetchUserData() {
guard let currentUserId = Auth.auth().currentUser?.uid else { return }
Service.shared.fetchUserData(uid: currentUserId) { user in
self.user = user
}
}
// N.B. Service.shared.fetchDrivers automatically gets called every time the location of the driver changes since it is observing the database via geofire (see definition of this within Service.swift)
private func fetchDrivers() {
guard let location = locationManager?.location else { return }
Service.shared.fetchDrivers(location: location) { (driver) in
guard let coordinate = driver.location?.coordinate else { return }
let annotation = DriverAnnotation(uid: driver.uid, coordinate: coordinate)
var driverIsVisible: Bool {
return self.mapView.annotations.contains { annotation -> Bool in
guard let driverAnnotation = annotation as? DriverAnnotation else { return false }
if driverAnnotation.uid == driver.uid {
// Driver is already visible - update driver location whenever this function is called
driverAnnotation.updateAnnotationPosition(withCoordinate: coordinate)
return true
}
// Driver is not visible
return false
}
}
// If driver is not visible then add to map
if !driverIsVisible {
self.mapView.addAnnotation(annotation)
}
}
}
private func checkIfUserLoggedIn() {
if Auth.auth().currentUser == nil {
// User is not logged in
print("DEBUG: User is not logged in")
DispatchQueue.main.async {
let nav = UINavigationController(rootViewController: LoginViewController())
nav.isModalInPresentation = true
nav.modalPresentationStyle = .fullScreen
self.present(nav, animated: true, completion: nil)
}
} else {
// User is logged in
configure()
}
}
private func logOut() {
do {
try Auth.auth().signOut()
DispatchQueue.main.async {
let nav = UINavigationController(rootViewController: LoginViewController())
nav.isModalInPresentation = true
nav.modalPresentationStyle = .fullScreen
self.present(nav, animated: true, completion: nil)
}
} catch {
print("DEBUG: Error signing user out: \(error)")
}
}
// MARK:- Public Helper Functions
public func configure() {
configureUI()
fetchUserData()
fetchDrivers()
}
public func configureUI() {
configureMapView()
configureActionButton()
configureInputActivationView()
configureTableView()
}
// MARK:- Private Helper Functions
private func configureActionButton() {
view.addSubview(actionButton)
actionButton.anchor(top: view.safeAreaLayoutGuide.topAnchor, left: view.safeAreaLayoutGuide.leftAnchor,
paddingTop: 16, paddingLeft: 16, width: 30, height: 30)
}
private func configureActionButtonState(config: ActionButtonConfiguration) {
switch config {
case .showMenu:
self.actionButton.setImage(#imageLiteral(resourceName: "baseline_menu_black_36dp").withRenderingMode(.alwaysOriginal), for: .normal)
self.actionButtonConfig = .showMenu
case .dismissActionView:
self.actionButton.setImage(#imageLiteral(resourceName: "baseline_arrow_back_black_36dp-1").withRenderingMode(.alwaysOriginal), for: .normal)
self.actionButtonConfig = .dismissActionView
}
}
private func configureMapView() {
view.addSubview(mapView)
mapView.frame = view.frame
mapView.delegate = self
mapView.showsUserLocation = true
mapView.userTrackingMode = .follow
}
private func configureInputActivationView() {
inputActivationView.delegate = self
view.addSubview(inputActivationView)
inputActivationView.centerX(inView: view)
inputActivationView.anchor(top: actionButton.bottomAnchor, left: view.safeAreaLayoutGuide.leftAnchor, right: view.safeAreaLayoutGuide.rightAnchor, paddingTop: 18, paddingLeft: 20, paddingRight: 20, height: 40)
// Animate inputActivationView (fade in)
inputActivationView.alpha = 0
UIView.animate(withDuration: 2) {
self.inputActivationView.alpha = 1
}
}
private func configureLocationInputView() {
locationInputView.delegate = self
view.addSubview(locationInputView)
locationInputView.anchor(top: view.topAnchor, left: view.leftAnchor, right: view.rightAnchor, height: 200)
locationInputView.alpha = 0
UIView.animate(withDuration: 0.5) {
self.locationInputView.alpha = 1
} completion: { _ in
print("DEBUG: Present table view")
UIView.animate(withDuration: 0.3) {
self.tableView.frame.origin.y = self.locationInputView.frame.height
}
}
}
private func configureTableView() {
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
tableView.register(LocationTableViewCell.self, forCellReuseIdentifier: LocationTableViewCell.identifier)
tableView.rowHeight = 60
tableView.tableFooterView = UIView()
let height = view.frame.height - locationInputView.frame.height
tableView.frame = CGRect(x: 0, y: view.frame.height, width: view.frame.width, height: height)
}
private func dismissInputView(completion: ((Bool) -> Void)? = nil) {
UIView.animate(withDuration: 0.3, animations: {
self.locationInputView.alpha = 0
self.tableView.frame.origin.y = self.view.frame.height
self.locationInputView.removeFromSuperview()
}, completion: completion)
}
private func removeAnnotationsAndOverlays() {
mapView.annotations.forEach { annotation in
if let anno = annotation as? MKPointAnnotation {
mapView.removeAnnotation(anno)
}
}
if mapView.overlays.count > 0 {
mapView.removeOverlay(mapView.overlays[0])
}
}
}
// MARK:- MapView Functions
extension HomeViewController: MKMapViewDelegate {
private func generatePolyline(toDestination destination: MKMapItem) {
let request = MKDirections.Request()
request.source = MKMapItem.forCurrentLocation()
request.destination = destination
request.transportType = .automobile
let directionRequest = MKDirections(request: request)
directionRequest.calculate { (response, error) in
guard let response = response else { return }
self.route = response.routes[0]
guard let polyline = self.route?.polyline else { return }
self.mapView.addOverlay(polyline)
}
}
private func searchBy(naturalLanguageQuery: String, completion: #escaping([MKPlacemark]) -> Void) {
var results = [MKPlacemark]()
let request = MKLocalSearch.Request()
request.region = mapView.region
request.naturalLanguageQuery = naturalLanguageQuery
let search = MKLocalSearch(request: request)
search.start { (response, error) in
guard let response = response else { return }
response.mapItems.forEach { (item) in
results.append(item.placemark)
}
completion(results)
}
}
// Change driver annotation appearance to Uber arrow
public func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if let annotation = annotation as? DriverAnnotation {
let view = MKAnnotationView(annotation: annotation, reuseIdentifier: DriverAnnotation.identifier)
view.image = #imageLiteral(resourceName: "chevron-sign-to-right")
return view
}
return nil
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let route = self.route {
let polyline = route.polyline
let lineRenderer = MKPolylineRenderer(overlay: polyline)
lineRenderer.strokeColor = .mainBlueTint
lineRenderer.lineWidth = 3
return lineRenderer
}
return MKOverlayRenderer()
}
}
// MARK:- Location Manager Services
extension HomeViewController {
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .notDetermined:
print("DEBUG: Not determined")
locationManager?.requestWhenInUseAuthorization()
case .restricted:
break
case .denied:
break
case .authorizedAlways:
print("DEBUG: Auth always")
locationManager?.startUpdatingLocation()
locationManager?.desiredAccuracy = kCLLocationAccuracyBest
case .authorizedWhenInUse:
print("DEBUG: Auth when in use")
locationManager?.requestAlwaysAuthorization()
#unknown default:
break
}
}
}
// MARK:- Input Activation View Delegate Methods
extension HomeViewController: LocationInputActivationViewDelegate {
func presentLocationInputView() {
configureLocationInputView()
self.inputActivationView.alpha = 0
}
}
// MARK:- Input View Delegate Methods
extension HomeViewController: LocationInputViewDelegate {
func executeSearch(query: String) {
searchBy(naturalLanguageQuery: query) { (results) in
print("DEBUG: Placemarks are \(results)")
self.searchResults = results
self.tableView.reloadData()
}
}
func dismissLocationInputView() {
dismissInputView()
UIView.animate(withDuration: 0.5) {
self.inputActivationView.alpha = 1
}
}
}
// MARK:- TableView Delegate and Datasource Methods
extension HomeViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
2
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "test"
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return section == 0 ? 2 : searchResults.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: LocationTableViewCell.identifier, for: indexPath) as! LocationTableViewCell
if indexPath.section == 1 {
cell.placemark = searchResults[indexPath.row]
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let placemark = self.searchResults[indexPath.row]
configureActionButtonState(config: .dismissActionView)
let destination = MKMapItem(placemark: placemark)
self.generatePolyline(toDestination: destination)
self.dismissInputView { _ in
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.coordinate
self.mapView.addAnnotation(annotation)
self.mapView.selectAnnotation(annotation, animated: true)
}
}
}
Here's the output in the debug window also, not sure if it's relevant:
2021-01-26 19:36:40.853479+0700 Uber[17163:1579722] [Default] InfoLog PolylineOverlayFillShader: WARNING: 0:42: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:48: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:54: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:66: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:68: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:72: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:74: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:78: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:80: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
Thanks in advance!
I have created a simplified version of this - with a hard-coded destination, but creating the route as you do, and this works very nicely.
private func generatePolyline(toDestination destination: MKMapItem) {
let request = MKDirections.Request()
request.source = MKMapItem.forCurrentLocation()
request.destination = destination
request.transportType = .automobile
let directionRequest = MKDirections(request: request)
directionRequest.calculate { (response, error) in
guard let response = response else { return }
self.route = response.routes[0]
guard let polyline = self.route?.polyline else { return }
self.mapView.addOverlay(polyline)
print("update map?")
}
}
the only thing I have added here is the debug print statement at the end - always nice to know that something should be happening, but always good to know it actually has!
I called the function from a simple test button
#IBAction func cmdButton(_ sender: Any) {
// for testing - Edinburgh Castle
generatePolyline(toDestination: MKMapItem(placemark: MKPlacemark(coordinate: CLLocationCoordinate2D(latitude: 55.9483, longitude: -3.1981), addressDictionary: nil)))
}
The renderer is very simple, and includes a test print as above
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let pr = MKPolylineRenderer(overlay: overlay)
pr.strokeColor = UIColor.red.withAlphaComponent(0.5)
pr.lineWidth = 7
print("rendererFor overlay")
return pr
}
If you're seeing the debug print comments, you should be ok. Let me know how you get on...
After digging a little deeper, the reason is that I'm in Phnom Penh, Cambodia. Apple Maps doesn't have the ability to give directions in Cambodia. Guess I'll have to resort to Google Maps if I want a line drawn from one point to another then.
Related
I would like to take a screenshot of ongoing VideoCaptureView during a call in iOS Swift. I used QuickBlox.
I have used below code that return black image
public extension UIView {
public func snapshotImage() -> UIImage? {
UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque,0)
drawHierarchy(in: bounds, afterScreenUpdates: false)
let snapshotImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return snapshotImage
}
public func snapshotView() -> UIView? {
if let snapshotImage = snapshotImage() {
return UIImageView(image: snapshotImage)
} else {
return nil
}
}
}
let snapshot = view.snapshotView()
Try this
fileprivate func captureUIImageFromUIView(_ view:UIView?) -> UIImage {
guard (view != nil) else{
// if the view is nil (it's happened to me) return an alternative image
let errorImage = UIImage(named: "Logo.png")
return errorImage!
}
// if the view is all good then convert the image inside the view to a uiimage
if #available(iOS 10.0, *) {
let renderer = UIGraphicsImageRenderer(size: view!.bounds.size)
let capturedImage = renderer.image {
(ctx) in
view!.drawHierarchy(in: view!.bounds, afterScreenUpdates: true)
}
return capturedImage
} else {
UIGraphicsBeginImageContextWithOptions((view!.bounds.size), view!.isOpaque, 0.0)
view!.drawHierarchy(in: view!.bounds, afterScreenUpdates: false)
let capturedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return capturedImage!
}
}
Call
let pdfImage = self.captureUIImageFromUIView(self.containerView)
//
// ViewController.swift
// GooglePlace-AutoComplete
//
// Created by infos on 7/10/18.
// Copyright © 2018 infos. All rights reserved.
//
import UIKit
import GoogleMaps
import GooglePlaces
import SwiftyJSON
import Alamofire
enum Location {
case startLocation
case destinationLocation
}
class ViewController: UIViewController , GMSMapViewDelegate , CLLocationManagerDelegate,UITextFieldDelegate {
#IBOutlet weak var googleMaps: GMSMapView!
#IBOutlet weak var startLocation: UITextField!
#IBOutlet weak var destinationLocation: UITextField!
var locationManager = CLLocationManager()
var locationSelected = Location.startLocation
var polyline = GMSPolyline()
var locationStart = CLLocation()
var locationEnd = CLLocation()
override func viewDidLoad() {
super.viewDidLoad()
GMSPlacesClient.provideAPIKey("AIzaSyC55Dq1qPH7EM_uiAVf-8QuxJtf2W1viQs")
GMSServices.provideAPIKey("AIzaSyC55Dq1qPH7EM_uiAVf-8QuxJtf2W1viQs")
// Create a GMSCameraPosition that tells the map to display the
let camera = GMSCameraPosition.camera(withLatitude: 13.082680,
longitude: 80.270718,
zoom: 10.0,
bearing: 30,
viewingAngle: 40)
//Setting the googleView
googleMaps.camera = camera
googleMaps.delegate = self
googleMaps.isMyLocationEnabled = true
googleMaps.settings.myLocationButton = true
googleMaps.settings.compassButton = true
googleMaps.settings.zoomGestures = true
googleMaps.animate(to: camera)
self.view.addSubview(googleMaps)
//Setting the start and end location
let origin = "\(13.082680),\(80.270718)"
let destination = "\(15.912900),\(79.739987)"
let url = "https://maps.googleapis.com/maps/api/directions/json?origin=\(origin)&destination=\(destination)&mode=driving"
//Rrequesting Alamofire and SwiftyJSON
Alamofire.request(url).responseJSON { response in
print(response.request as Any) // original URL request
print(response.response as Any) // HTTP URL response
print(response.data as Any) // server data
print(response.result) // result of response serialization
do {
let json = try JSON(data: response.data!)
let routes = json["routes"].arrayValue
for route in routes
{
let routeOverviewPolyline = route["overview_polyline"].dictionary
let points = routeOverviewPolyline?["points"]?.stringValue
let path = GMSPath.init(fromEncodedPath: points!)
let polyline = GMSPolyline.init(path: path)
polyline.strokeColor = UIColor.blue
polyline.strokeWidth = 2
polyline.map = self.googleMaps
}
}
catch {
}
}
// Creates a marker in the center of the map.
let marker = GMSMarker()
marker.position = CLLocationCoordinate2D(latitude: 28.524555, longitude: 77.275111)
marker.title = "Mobiloitte"
marker.snippet = "India"
marker.map = googleMaps
//28.643091, 77.218280
let marker1 = GMSMarker()
marker1.position = CLLocationCoordinate2D(latitude: 28.643091, longitude: 77.218280)
marker1.title = "NewDelhi"
marker1.snippet = "India"
marker1.map = googleMaps
}
// MARK: function for create a marker pin on map
func createMarker(titleMarker: String, iconMarker: UIImage, latitude: CLLocationDegrees, longitude: CLLocationDegrees) {
let marker = GMSMarker()
marker.position = CLLocationCoordinate2DMake(latitude, longitude)
marker.isDraggable=true
marker.title = titleMarker
marker.icon = iconMarker
marker.map = googleMaps
}
//MARK: - Location Manager delegates
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error to get location : \(error)")
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations.last
// let camera = GMSCameraPosition.camera(withLatitude: (location?.coordinate.latitude)!, longitude: (location?.coordinate.longitude)!, zoom: 17.0)
let locationMobi = CLLocation(latitude: 28.524555, longitude: 77.275111)
drawPath(startLocation: location!, endLocation: locationMobi)
//self.googleMaps?.animate(to: camera)
self.locationManager.stopUpdatingLocation()
}
// MARK: - GMSMapViewDelegate
func mapView(_ mapView: GMSMapView, idleAt position: GMSCameraPosition) {
googleMaps.isMyLocationEnabled = true
}
func mapView(_ mapView: GMSMapView, willMove gesture: Bool) {
googleMaps.isMyLocationEnabled = true
if (gesture) {
mapView.selectedMarker = nil
}
}
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
googleMaps.isMyLocationEnabled = true
return false
}
func mapView(_ mapView: GMSMapView, didTapAt coordinate: CLLocationCoordinate2D) {
print("COORDINATE \(coordinate)") // when you tapped coordinate
}
func didTapMyLocationButton(for mapView: GMSMapView) -> Bool {
googleMaps.isMyLocationEnabled = true
googleMaps.selectedMarker = nil
return false
}
//MARK: - Marker Delegate
func mapView(_ mapView: GMSMapView, didDrag marker: GMSMarker) {
}
func mapView(_ mapView: GMSMapView, didBeginDragging marker: GMSMarker) {
}
func mapView(_ mapView: GMSMapView, didEndDragging marker: GMSMarker) {
self.googleMaps.reloadInputViews()
//self.polyline.map = nil;
print("marker dragged to location: \(marker.position.latitude),\(marker.position.longitude)")
let locationMobi = CLLocation(latitude: marker.position.latitude, longitude: marker.position.longitude)
self.drawPath(startLocation: locationMobi, endLocation: locationEnd)
}
//MARK: - this is function for create direction path, from start location to desination location
func drawPath(startLocation: CLLocation, endLocation: CLLocation)
{
let origin = "\(startLocation.coordinate.latitude),\(startLocation.coordinate.longitude)"
let destination = "\(endLocation.coordinate.latitude),\(endLocation.coordinate.longitude)"
self.polyline.map = nil
//self.googleMaps.clear()
let url = "https://maps.googleapis.com/maps/api/directions/json?origin=\(origin)&destination=\(destination)&mode=driving"
Alamofire.request(url).responseJSON { response in
print(response.request as Any) // original URL request
print(response.response as Any) // HTTP URL response
print(response.data as Any) // server data
print(response.result as Any) // result of response serialization
do {
let json = try JSON(data: response.data!)
let routes = json["routes"].arrayValue
// print route using Polyline
for route in routes
{
let routeOverviewPolyline = route["overview_polyline"].dictionary
let points = routeOverviewPolyline?["points"]?.stringValue
let path = GMSPath.init(fromEncodedPath: points!)
self.polyline = GMSPolyline.init(path: path)
self.polyline.strokeWidth = 2
self.polyline.strokeColor = UIColor.red
self.polyline.map = self.googleMaps
}
} catch {
}
}
}
// MARK: when start location tap, this will open the search location
#IBAction func openStartLocation(_ sender: UIButton) {
let autoCompleteController = GMSAutocompleteViewController()
autoCompleteController.delegate = self
// selected location
locationSelected = .startLocation
// Change text color
UISearchBar.appearance().setTextColor(color: UIColor.black)
self.locationManager.stopUpdatingLocation()
self.present(autoCompleteController, animated: true, completion: nil)
}
// MARK: when destination location tap, this will open the search location
#IBAction func openDestinationLocation(_ sender: UIButton) {
let autoCompleteController = GMSAutocompleteViewController()
autoCompleteController.delegate = self
// selected location
locationSelected = .destinationLocation
// Change text color
UISearchBar.appearance().setTextColor(color: UIColor.black)
self.locationManager.stopUpdatingLocation()
self.present(autoCompleteController, animated: true, completion: nil)
}
// MARK: SHOW DIRECTION WITH BUTTON
#IBAction func showDirection(_ sender: UIButton) {
// when button direction tapped, must call drawpath func
self.drawPath(startLocation: locationStart, endLocation: locationEnd)
}
}
// MARK: - GMS Auto Complete Delegate, for autocomplete search location
extension ViewController: GMSAutocompleteViewControllerDelegate {
func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
print("Error \(error)")
}
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
// Change map location
let camera = GMSCameraPosition.camera(withLatitude: place.coordinate.latitude, longitude: place.coordinate.longitude, zoom: 16.0
)
// set coordinate to text
if locationSelected == .startLocation {
if (self.locationManager.location?.coordinate.longitude) != nil {
startLocation.text = "\(place.coordinate.latitude), \(place.coordinate.longitude)"
locationStart = CLLocation(latitude: place.coordinate.latitude, longitude: place.coordinate.longitude)
createMarker(titleMarker: "Location Start", iconMarker: #imageLiteral(resourceName: "images"), latitude: place.coordinate.latitude, longitude: place.coordinate.longitude)
}else {
// handle the error by declaring default value
}
} else {
if (self.locationManager.location?.coordinate.longitude) != nil {
destinationLocation.text = "\(place.coordinate.latitude), \(place.coordinate.longitude)"
locationEnd = CLLocation(latitude: place.coordinate.latitude, longitude: place.coordinate.longitude)
createMarker(titleMarker: "Location End", iconMarker: #imageLiteral(resourceName: "images"), latitude: place.coordinate.latitude, longitude: place.coordinate.longitude)
}else {
// handle the error by declaring default value
}
}
self.googleMaps.camera = camera
self.dismiss(animated: true, completion: nil)
}
func wasCancelled(_ viewController: GMSAutocompleteViewController) {
self.dismiss(animated: true, completion: nil)
}
func didRequestAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
func didUpdateAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
}
public func textFieldShouldReturn(_ textField: UITextField) -> Bool
{
textField.resignFirstResponder()
return true
}
public extension UISearchBar {
public func setTextColor(color: UIColor) {
let svs = subviews.flatMap { $0.subviews }
guard let tf = (svs.filter { $0 is UITextField }).first as? UITextField else { return }
tf.textColor = color
}
}
Here is working code to take a screenshot of ongoing video during a call in QuickBlox
#IBOutlet weak var stackView: UIStackView!
let stillImageOutput = AVCaptureStillImageOutput()
override func viewDidLoad() {
super.viewDidLoad()
QBRTCClient.initializeRTC()
QBRTCClient.instance().add(self)
cofigureVideo()
}
func cofigureVideo() {
QBRTCConfig.mediaStreamConfiguration().videoCodec = .H264
QBRTCConfig.setAnswerTimeInterval(30)
QBRTCConfig.setStatsReportTimeInterval(5)
let videoFormat = QBRTCVideoFormat.init()
videoFormat.frameRate = 30
videoFormat.pixelFormat = .format420f
videoFormat.width = 640
videoFormat.height = 480
self.videoCapture = QBRTCCameraCapture.init(videoFormat: videoFormat, position: .front)
self.videoCapture.previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
self.videoCapture.startSession {
self.stillImageOutput.outputSettings = [AVVideoCodecKey:AVVideoCodecJPEG]
if self.videoCapture.captureSession.canAddOutput(self.stillImageOutput) {
self.videoCapture.captureSession.addOutput(self.stillImageOutput)
}
let localView = LocalVideoView.init(withPreviewLayer:self.videoCapture.previewLayer)
self.stackView.addArrangedSubview(localView)
}
}
Take photo Button Click
#IBAction func TakePhotoTapped(_ sender: Any) {
if let videoConnection = stillImageOutput.connection(with: AVMediaType.video) {
stillImageOutput.captureStillImageAsynchronously(from: videoConnection) {
(imageDataSampleBuffer, error) -> Void in
let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer!)
if let image = UIImage(data: imageData!){
// Your image is Here
}
}
}
}
I am trying to create a custom MKPointAnnotation to use on a map view. It looks very much like the one used in Apple's Photos:
I will be retrieving a number of photos from a server, along with their location. I then want to display an annotation like the one above, with the image inside the annotation.
I currently have a program that can add a normal MKPointAnnotation at the right coordinate, and can also retrieve the relevant photo from the server.
All I want is to style my MKPointAnnotation to look just like that.
I have tried following other answers but I think this is slightly different because I want to show an image on a template every time.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? from MKMapViewDelegate is the function you need to override.
It allows you to provide "custom" views for the each annotation. Within this function, you can either dequeue a custom view (subclass of MKAnnotationView) and set custom properties OR you can dequeue a regular MKAnnotationView which has a property image.
You can set that property to display custom images. I'd rather use my own annotationView anyway as you can add custom layouts (labels, imageViews, etc..) and themes (colours, layers, etc..).
Example:
//
// ViewController.swift
// Maps
//
// Created by Brandon T on 2017-02-20.
// Copyright © 2017 XIO. All rights reserved.
//
import UIKit
import MapKit
class ImageAnnotation : NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
var title: String?
var subtitle: String?
var image: UIImage?
var colour: UIColor?
override init() {
self.coordinate = CLLocationCoordinate2D()
self.title = nil
self.subtitle = nil
self.image = nil
self.colour = UIColor.white
}
}
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: 50, height: 50)
self.imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
self.addSubview(self.imageView)
self.imageView.layer.cornerRadius = 5.0
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")
}
}
class ViewController: UIViewController, MKMapViewDelegate {
var mapView: MKMapView!
var locationManager: CLLocationManager!
override func viewDidLoad() {
super.viewDidLoad()
self.initControls()
self.doLayout()
self.loadAnnotations()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func initControls() {
self.mapView = MKMapView()
self.mapView.isRotateEnabled = true
self.mapView.showsUserLocation = true
self.mapView.delegate = self
let center = CLLocationCoordinate2DMake(43.761539, -79.411079)
let region = MKCoordinateRegionMake(center, MKCoordinateSpanMake(0.005, 0.005))
self.mapView.setRegion(region, animated: true)
}
func doLayout() {
self.view.addSubview(self.mapView)
self.mapView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
self.mapView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
self.mapView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
self.mapView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
self.mapView.translatesAutoresizingMaskIntoConstraints = false
}
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 loadAnnotations() {
let request = NSMutableURLRequest(url: URL(string: "https://i.imgur.com/zIoAyCx.png")!)
request.httpMethod = "GET"
let session = URLSession(configuration: URLSessionConfiguration.default)
let dataTask = session.dataTask(with: request as URLRequest) { (data, response, error) in
if error == nil {
let annotation = ImageAnnotation()
annotation.coordinate = CLLocationCoordinate2DMake(43.761539, -79.411079)
annotation.image = UIImage(data: data!, scale: UIScreen.main.scale)
annotation.title = "Toronto"
annotation.subtitle = "Yonge & Bloor"
DispatchQueue.main.async {
self.mapView.addAnnotation(annotation)
}
}
}
dataTask.resume()
}
}
If you want set in map many location with different pictures use this code:
#Brandon, Thank you
#objc func loadAnnotations() {
for item in locations{
DispatchQueue.main.async {
let request = NSMutableURLRequest(url: URL(string: "<YourPictureUrl>")!)
request.httpMethod = "GET"
let session = URLSession(configuration: URLSessionConfiguration.default)
let dataTask = session.dataTask(with: request as URLRequest) { (data, response, error) in
if error == nil {
let annotation = ImageAnnotation()
annotation.coordinate = CLLocationCoordinate2DMake(Double(item["pl_lat"] as! String)!,
Double(item["pl_long"] as! String)!)
annotation.image = UIImage(data: data!, scale: UIScreen.main.scale)
annotation.title = "T"
annotation.subtitle = ""
DispatchQueue.main.async {
self.mapView.addAnnotation(annotation)
}
}
}
dataTask.resume()
}
}
}
I am doing a projects by following this tutorial enter link description here in swift3 Xcode 8.2.1.
I want to get 10 Nearest Restaurants from my current location.
But Still I can't get any data as map pins or tableview.
Please help me I am new to Swift.
Please find whole coding here including pod file.
Pod File
'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!
target "FSResturant" do
pod 'QuadratTouch',
:git => 'https://github.com/Constantine-Fry/das-quadrat', :branch => 'fry-swift30'
pod 'RealmSwift'
end
ResturantAPI.swift
import Foundation
import QuadratTouch
import MapKit
import Realm
import RealmSwift
//Create Venues Struct
struct API
{
struct notifications
{
static let venuesUpdated = "venues_updated"
}
}
class ResturantAPI
{
static let sharedInstance = ResturantAPI()
var session:Session?
init()
{
// Initialize the Foursquare client API Keys
let client = Client(clientID: "XXXX", clientSecret: "XXXX", redirectURL: "")
let configuration = Configuration(client:client)
Session.setupSharedSessionWithConfiguration(configuration)
self.session = Session.sharedSession()
}
//getting venue data from FOURSQUARE
func getResturantsWithLocation(location:CLLocation)
{
//function body
if let session = self.session
{
//this category Id will receive nearby halal resturants in dubai
var parameters = location.parameters()
parameters += [Parameter.categoryId: "52e81612bcbc57f1066b79ff"]
parameters += [Parameter.radius: "2000"]
parameters += [Parameter.limit: "10"]
// Start a "search", i.e. an async call to Foursquare that should return venue data
let searchTask = session.venues.search(parameters)
{
(result) -> Void in
if let response = result.response
{
if let venues = response["venues"] as? [[String: AnyObject]]
{
autoreleasepool
{
let realm = try! Realm()
realm.beginWrite()
for venue:[String: AnyObject] in venues
{
let venueObject:Venue = Venue()
if let id = venue["id"] as? String
{
venueObject.id = id
}
if let name = venue["name"] as? String
{
venueObject.name = name
}
if let location = venue["location"] as? [String: AnyObject]
{
if let longitude = location["lng"] as? Float
{
venueObject.longitude = longitude
}
if let latitude = location["lat"] as? Float
{
venueObject.latitude = latitude
}
if let formattedAddress = location["formattedAddress"] as? [String]
{
venueObject.address = formattedAddress.joined(separator: " ")
}
}
realm.add(venueObject, update: true)
}
do {
try realm.commitWrite()
print("Committing write...")
}
catch (let e)
{
print("Y U NO REALM ? \(e)")
}
}
NotificationCenter.default.post(name: Foundation.Notification.Name(rawValue: API.notifications.venuesUpdated), object:nil, userInfo:nil)
}
}
}
searchTask.start()
}
}
}
extension CLLocation
{
func parameters() -> Parameters
{
let ll = "\(self.coordinate.latitude),\(self.coordinate.longitude)"
// let near = "Dubai"
let llAcc = "\(self.horizontalAccuracy)"
// let llAcc = "10000.0"
let alt = "\(self.altitude)"
// let alt = "0"
let altAcc = "\(self.verticalAccuracy)"
// let altAcc = "10000.0"
// let query = "resturants"
let parameters = [
Parameter.ll:ll,
// Parameter.near:near,
Parameter.llAcc:llAcc,
Parameter.alt:alt,
Parameter.altAcc:altAcc,
// Parameter.query:query
]
return parameters
}
}
ViewController.swift
import UIKit
import MapKit
import RealmSwift
import Realm
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, UITableViewDataSource, UITableViewDelegate {
//Initialize the Map View
#IBOutlet var mapView:MKMapView?
//To get the user location
var locationManager:CLLocationManager?
//Span view in meters default
let distanceSpan:Double = 500
//show user's location on the map
var lastLocation:CLLocation?
// Stores venues from Realm, as a non-lazy list
var venues:[Venue]?
//Initializing the table view
#IBOutlet var tableView1:UITableView?
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.onVenuesUpdated(_:)), name: NSNotification.Name(rawValue: API.notifications.venuesUpdated), object: nil);
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
//Initializing mapview delegate to display
if let mapView = self.mapView
{
mapView.delegate = self
}
//Initializing UITableview delegate to display
if let tableView1 = self.tableView1
{
tableView1.delegate = self
tableView1.dataSource = self
}
}
override func viewDidAppear(_ animated: Bool)
{
if locationManager == nil
{
locationManager = CLLocationManager()
locationManager!.delegate = self
locationManager!.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationManager!.requestAlwaysAuthorization()
// Don't send location updates with a distance smaller than 50 meters between them
locationManager!.distanceFilter = 50
locationManager!.startUpdatingLocation()
}
}
// UITable View Delegates
func tableView(_ tableView1: UITableView, numberOfRowsInSection section: Int) -> Int
{
return venues?.count ?? 0
}
func numberOfSections(in tableView1: UITableView) -> Int {
return 1
}
func tableView(_ tableView1: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView1.dequeueReusableCell(withIdentifier: "cellIdentifier");
if cell == nil
{
cell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "cellIdentifier")
}
if let venue = venues?[indexPath.row]
{
cell!.textLabel?.text = venue.name
cell!.detailTextLabel?.text = venue.address
}
return cell!
}
func tableView(_ tableView1: UITableView, didSelectRowAt indexPath: IndexPath) {
if let venue = venues?[indexPath.row]
{
let region = MKCoordinateRegionMakeWithDistance(CLLocationCoordinate2D(latitude: Double(venue.latitude), longitude: Double(venue.longitude)), distanceSpan, distanceSpan)
mapView?.setRegion(region, animated: true)
}
}
func refreshVenues(_ location: CLLocation?, getDataFromFoursquare:Bool = false)
{
if location != nil
{
lastLocation = location
}
if let location = lastLocation
{
if getDataFromFoursquare == true
{
ResturantAPI.sharedInstance.getResturantsWithLocation(location: location)
}
// Convenience method to calculate the top-left and bottom-right GPS coordinates based on region (defined with distanceSpan)
let (start, stop) = calculateCoordinatesWithRegion(location);
// Set up a predicate that ensures the fetched venues are within the region
let predicate = NSPredicate(format: "latitude < %f AND latitude > %f AND longitude > %f AND longitude < %f", start.latitude, stop.latitude, start.longitude, stop.longitude);
let realm = try! Realm()
venues = realm.objects(Venue.self).filter(predicate).sorted {
location.distance(from: $0.coordinate) < location.distance(from: $1.coordinate);
};
for venue in venues!
{
let annotation = ResturantAnnotation(title: venue.name, subtitle: venue.address, coordinate: CLLocationCoordinate2D(latitude: Double(venue.latitude), longitude: Double(venue.longitude)));
mapView?.addAnnotation(annotation);
}
// RELOAD ALL THE DATAS !!!
tableView1?.reloadData()
}
}
//Delegate Method of CLLocation Manager
func locationManager(manager: CLLocationManager, didUpdateToLocation newLocation: CLLocation, fromLocation oldLocation: CLLocation)
{
if let mapView = self.mapView
{
let region = MKCoordinateRegionMakeWithDistance(newLocation.coordinate, distanceSpan, distanceSpan)
mapView.setRegion(region, animated: true)
refreshVenues(newLocation, getDataFromFoursquare: true)
}
}
//Adds Annotations in the Map View
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
{
if annotation.isKind(of: MKUserLocation.self)
{
return nil
}
var view = mapView.dequeueReusableAnnotationView(withIdentifier: "annotationIdentifier");
if view == nil
{
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "annotationIdentifier")
}
view?.canShowCallout = true
return view
}
func onVenuesUpdated(_ notification:Foundation.Notification)
{
// When new data from Foursquare comes in, reload from local Realm
refreshVenues(nil);
}
func calculateCoordinatesWithRegion(_ location:CLLocation) -> (CLLocationCoordinate2D, CLLocationCoordinate2D)
{
let region = MKCoordinateRegionMakeWithDistance(location.coordinate, distanceSpan, distanceSpan);
var start:CLLocationCoordinate2D = CLLocationCoordinate2D();
var stop:CLLocationCoordinate2D = CLLocationCoordinate2D();
start.latitude = region.center.latitude + (region.span.latitudeDelta / 2.0);
start.longitude = region.center.longitude - (region.span.longitudeDelta / 2.0);
stop.latitude = region.center.latitude - (region.span.latitudeDelta / 2.0);
stop.longitude = region.center.longitude + (region.span.longitudeDelta / 2.0);
return (start, stop);
}
Venue.swift
import Foundation
import RealmSwift
import MapKit
class Venue: Object
{
dynamic var id:String = ""
dynamic var name:String = ""
dynamic var latitude:Float = 0
dynamic var longitude:Float = 0
dynamic var address:String = ""
var coordinate:CLLocation {
return CLLocation(latitude: Double(latitude), longitude: Double(longitude));
}
//The the primary key to Realm
override static func primaryKey() -> String?
{
return "id";
}
}
ResturantAnnotation.swift
import Foundation
import MapKit
class ResturantAnnotation: NSObject, MKAnnotation
{
let title:String?
let subtitle:String?
let coordinate: CLLocationCoordinate2D
init(title:String?, subtitle:String?, coordinate:CLLocationCoordinate2D)
{
self.title=title
self.subtitle=subtitle
self.coordinate=coordinate
super.init();
}
}
Can Anybody figure out what is the mistake here, that not showing responses in the map and tableview.
Thanks.
For some odd reason the viewForAnnotation is only working for the pin that is set in viewDidLoad (this is a test pin). The pins that are loaded elsewhere aren't getting annotated when pressed. I've already set the delegate. I think it has something to do with the identifier in the mapView call? But I'm unsure of how to fix it. Any help is appreciated! Thanks!
Here's my code:
import Foundation
import UIKit
import MapKit
import CoreLocation
import Alamofire
class MapViewController: UIViewController, MKMapViewDelegate {
var locationManager:CLLocationManager = CLLocationManager()
#IBOutlet weak var potholeMapView: MKMapView!
var listData: Array<String> = []
var idData: Array<Int> = []
var descriptionData: Array<String> = []
var latitudeData:Array<Double> = []
var longitudeData:Array<Double> = []
override func viewDidLoad() {
super.viewDidLoad()
potholeMapView.delegate = self
locationManager.requestWhenInUseAuthorization()
potholeMapView!.region = sanDiegoCountyLocation()
potholeMapView!.mapType = MKMapType.Standard
potholeMapView!.showsUserLocation = true
potholeMapView!.showsTraffic = true
print(potholeMapView!.userLocationVisible)
// WORKING HERE ACCESSORY VIEW SHOWS
let encinitas = CLLocationCoordinate2DMake(32.955, -117.2459)
let marker = AnnotatedLocation(
coordinate: encinitas,
title: "There",
subtitle: "You are not here")
potholeMapView!.addAnnotation(marker)
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//HERE ACCESSORY VIEWS DONT SHOW
loadPotholeData()
}
func sanDiegoCountyLocation()-> MKCoordinateRegion {
let center = CLLocationCoordinate2DMake(32.76572795, -117.07319880 )
let widthMeters:CLLocationDistance = 100
let heightMeters:CLLocationDistance = 1000*120
return MKCoordinateRegionMakeWithDistance(center, widthMeters, heightMeters)
}
func loadPotholeData(){
let url = "http://bismarck.sdsu.edu/city/fromDate"
let parametersGet = ["type" : "street", "user" : "008812"]
Alamofire.request(.GET, url, parameters: parametersGet)
.responseJSON { response in
if let dataGet = response.result.value {
let dataDict:NSArray = dataGet as! NSArray
for item in dataDict{
let descrip = item["created"]
self.listData.append(descrip!! as! String)
let ids = item["id"]
self.idData.append(ids! as! Int)
let description = item["description"]
self.descriptionData.append(description!! as! String)
let latitude = item["latitude"]
self.latitudeData.append(latitude as! Double)
let longitude = item["longitude"]
self.longitudeData.append(longitude as! Double)
}
}
else {
print("There was some error getting data")
}
}
createAllPins()
}
func createAllPins(){
for (x, y) in zip(self.latitudeData, self.longitudeData) {
let location = CLLocationCoordinate2DMake(x, y)
let marker = AnnotatedLocation(
coordinate: location,
title: "",
subtitle: "")
potholeMapView!.addAnnotation(marker)
}
}
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if let annotation = annotation as? AnnotatedLocation {
let identifier = "pin"
var view: MKPinAnnotationView
if let dequeuedView = mapView.dequeueReusableAnnotationViewWithIdentifier(identifier)
as? MKPinAnnotationView {
dequeuedView.annotation = annotation
view = dequeuedView
} else {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
//view = MKPinAnnotationView(annotation: <#T##MKAnnotation?#>, reuseIdentifier: <#T##String?#>)
view.canShowCallout = true
view.calloutOffset = CGPoint(x: -5, y: 5)
view.rightCalloutAccessoryView = UIButton(type: .DetailDisclosure)
}
return view
}
return nil
}
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
self.performSegueWithIdentifier("pushAnnotation", sender: view)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
switch identifier {
case "pushAnnotation":
let nextVC = segue.destinationViewController as! MapAnnotationDetailViewController
//nextVC.
default: break
}
}
}
}
It appears that it is because your title and subtitle for the others are blank. I tested out having nothing in quotations and adding something, and that seems to fix that issue.
For me, it was that after specifying my custom mapView.mapType, I had to also tell the mapView to "showAnnotations".
Until I told it to show them, the annotations would not display.
mapView.mapType = mapTypes[Preference.mapType]
mapView.showAnnotations(mapView.annotations, animated: true) //this fixed it
I am working on a person project that allows users to add annotation pins to the map, followed by downloading photos from Flickr for the chosen location. The pins/annotations are clickable so as to trigger certain functions (e.g. deletion or triggering segue to a detail view). I am currently stumbled upon making the callout action work. Below is the code I have written:
class ViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView!
let reusedPinId = "pin"
var selectedLocation: TouringLocationAnnotation!
let initLocation = CLLocation(latitude: 21.282788, longitude: -157.829444)
let regionRadius: CLLocationDistance = 1000
var myPin: MKPinAnnotationView?
var editModeOn = false
override func viewDidLoad() {
super.viewDidLoad()
let longPressRecognizer = UILongPressGestureRecognizer()
longPressRecognizer.addTarget(self, action: "addMapAnnotation:")
mapView.addGestureRecognizer(longPressRecognizer)
mapView.delegate = self
centerMapOnLocation(initLocation)
let touringSpot = TouringSpot(title: "Picture at this touring site", coordinate: CLLocationCoordinate2D(latitude: 21.283921, longitude: -157.831661))
mapView.addAnnotation(touringSpot) //TODO: change this to the first pin
self.navigationItem.rightBarButtonItem = self.editButtonItem() //toggle edit and Done
self.navigationItem.title = "Virtual Tourist"
fetchedResultsController.delegate = self
do {
try fetchedResultsController.performFetch()
if let savedPins = fetchedResultsController.fetchedObjects {
for pin in savedPins {
print("retrieved a pin from coredata")
showPinOnMap(pin as! Pin)
}
}
} catch let error as NSError {
print("error in retrieving saved pins: \(error)")
}
}
//show the pin as an annotation on map
func showPinOnMap(pin: Pin) {
if pin.isValid() { //check if the pin has coordinate set up
let locationAnnot = TouringLocationAnnotation(tourLocationPin: pin)
//TODO: fetch photo for the pin
if pin.photos.count == 0 {
print("about to fetch flickr photos for pin:")
pin.fetchPhotosFromFlickr({ (success, error) -> Void in
if success {
print("successfl in fetching flickr photos")
} else {
print("error in fetching flickr photos")
//TODO: give warning to user
return
}
});
}
mapView.addAnnotation(locationAnnot)
} else {
print("pin is invalid")
//TODO: give warning to user on invalid pin
}
}
func centerMapOnLocation(location: CLLocation) {
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate, regionRadius * 2.0, regionRadius * 2.0)
mapView.setRegion(coordinateRegion, animated: true)
}
}
Below is the method for adding map annotations:
func centerMapOnLocation(location: CLLocation) {
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate, regionRadius * 2.0, regionRadius * 2.0)
mapView.setRegion(coordinateRegion, animated: true)
}
func addMapAnnotation(gestureRecognizer: UIGestureRecognizer) {
if gestureRecognizer.state == UIGestureRecognizerState.Began {
let touchPoint = gestureRecognizer.locationInView(mapView)
let newCoordinate = mapView.convertPoint(touchPoint, toCoordinateFromView: mapView)
let annotation = TouringSpot(title: "new spot", coordinate: newCoordinate)
var locationNameStr = ""
let locationDictionary: [String:AnyObject] = [
Pin.Keys.Name: "\(newCoordinate.latitude, newCoordinate.longitude)",
Pin.Keys.Latitude: newCoordinate.latitude,
Pin.Keys.Longitude: newCoordinate.longitude
]
defer {
//TODO: check for duplicate pins in database first?
let newPin = Pin(dictionary: locationDictionary, context: sharedContext)
newPin.name = locationNameStr
do {
//persist the new pin to core data
try sharedContext.save()
print("saved a pin!")
showPinOnMap(newPin)
} catch let error as NSError {
print("error saving the new pin in context")
}
}
//problem: cannot pass the locationNameStr to the defer{} block
//TODO: retrieve the location name as title of the annotation
CLGeocoder().reverseGeocodeLocation(CLLocation(latitude: newCoordinate.latitude, longitude: newCoordinate.longitude), completionHandler: {(placemarks, error) -> Void in
if error != nil {
print("reverse geocoding failed with error: \(error)")
//return
} else if placemarks!.count > 0 {
let firstPlace = placemarks![0] as CLPlacemark
if firstPlace.country != nil {
locationNameStr = "a place in \(firstPlace.country!)"
}
else if firstPlace.locality != nil {
locationNameStr = "a place in \(firstPlace.locality!)"
}
print("location name: \(locationNameStr)")
}
})
}
}
When the Edit button is clicked, the user can click on a pin for deletion:
override func setEditing(editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
if editing {
print("going to edit the pins")
let titleDict: NSDictionary = [NSForegroundColorAttributeName: UIColor.redColor()
]
navigationController?.navigationBar.titleTextAttributes = titleDict as! [String : AnyObject]
self.navigationItem.title = "Click a pin for deletion"
editModeOn = true
} else {
print("done with editing the pins")
let titleDict: NSDictionary = [NSForegroundColorAttributeName: UIColor.blackColor()
]
navigationController?.navigationBar.titleTextAttributes = titleDict as! [String : AnyObject]
self.navigationItem.title = "Tourist"
editModeOn = false
}
}
Below is my implementation of the MKMapViewDelegate:
extension ViewController: MKMapViewDelegate {
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if let anno = annotation as? TouringLocationAnnotation {
print("calling the viewForAnnotation delegate")
var pinView: MKPinAnnotationView!
if let dequeuedView = mapView.dequeueReusableAnnotationViewWithIdentifier(reusedPinId)
as? MKPinAnnotationView {
dequeuedView.annotation = anno
pinView = dequeuedView
} else {
pinView = MKPinAnnotationView(annotation: anno, reuseIdentifier: reusedPinId)
}
pinView.canShowCallout = true
if #available(iOS 9.0, *) {
pinView.pinTintColor = UIColor.purpleColor()
} else {
// Fallback on earlier versions
}
pinView.animatesDrop = true
// pinView.calloutOffset = CGPoint(x: -5, y: 5)
let btn = UIButton(type: .DetailDisclosure)
// btn.addTarget(self, action: Selector("showPhotosForPin:"), forControlEvents: UIControlEvents.TouchUpInside)
pinView.rightCalloutAccessoryView = btn //UIButton.buttonWithType(.DetailDisclosure) as! UIView
return pinView
}
return nil
}
//call out
func mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!, calloutAccessoryControlTapped control: UIControl!) {
print("callout accessory control triggered!")
/*
if let annotation = view.annotation as? TouringLocationAnnotation {
mapView.removeAnnotation(annotation)
}
*/
let annotation = view.annotation as! TouringLocationAnnotation
if control == view.rightCalloutAccessoryView {
print("right callout button is clicked")
}
}
func showPhotosForPin(sender: UIButton!) {
print("showing photos for pin")
}
func mapView(mapView: MKMapView, didSelectAnnotationView view: MKAnnotationView) {
let latitude = view.annotation!.coordinate.latitude
let longitude = view.annotation!.coordinate.longitude
//let annotation = view.annotation as! TouringLocationAnnotation
if !editModeOn {
print("did select the annotation View, getting its coordinates, latitude: \(latitude), longitude: \(longitude)")
// print("pin is: \(annotation.tourLocationPin.latitude)")
} else {
print("for deleting the pin")
}
}
}
The pins are persisted in CoreData, when the app is started, they are fetched from persistence and annotated on the map. As you can see from the below screenshot, the pins are annotated in purple color (the only red pin is from my hard-coded annotation, which is not from CoreData), I think this suggests that my pins are converted to MKAnnotationView type successfully. However, when I click on the pins, the callout action is not trigger, for which I would expect to see the following printout
"callout accessory control triggered!"
Please note that the code for CoreData was ommitted for brevity, as I think they are irrelevant to my question
I think your code is correct. It works fine for me.
There is something I am a little curious about.
However, when I click on the pins, the callout action is not trigger,
When you click pins, calloutAccessoryControlTapped is NOT called.
When you click UIButton on pins, calloutAccessoryControlTapped is called.