I am creating my first IOS app and am not a developer and am really stuck with Map Annotations.
I am trying to get Fire data from a GeoJSON URL end point and display the fires as Annotations on a Map using URLSession and a custom MKAnnotationView and custom fire Pins.
The problem is the Annotations with the GeoJSON Fire data from the URL end point are not appearing on the Map, although data is being returned by the URL session. However, if I manually create a single Fire annotation it is appearing correctly on the map with the custom pin.
Any help would be immensely appreciated, I have spent days trying to figure this out :(
Here is the ViewController.Swift file
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
var locationManager:CLLocationManager!
var lat = Double()
var lon = Double()
var fires: [Fire] = []
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
mapView.register(FireMarkerView.self,forAnnotationViewWithReuseIdentifier:
MKMapViewDefaultAnnotationViewReuseIdentifier)
if let url = URL(string: "https://services3.arcgis.com/T4QMspbfLg3qTGWY/arcgis/rest/services/Active_Fires/FeatureServer/0/query?outFields=*&where=1%3D1&f=geojson") {
URLSession.shared.dataTask(with: url) {data, response, error in
if let data = data {
do {
let features = try MKGeoJSONDecoder().decode(data)
.compactMap { $0 as? MKGeoJSONFeature }
let validWorks = features.compactMap(Fire.init)
self.fires.append(contentsOf: validWorks)
print([self.fires])
}
catch let error {
print(error)
}
}
}.resume()
}
//This code works an annotation appears correctly on map
/* let fire = Fire(
title: "Ford Fire",
incidentShortDescription: "Hwy 35",
incidentTypeCategory: "WF",
coordinate: CLLocationCoordinate2D(latitude: 37.7993, longitude: -122.1947))
mapView.addAnnotation(fire)*/
mapView.addAnnotations(fires)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
determineMyCurrentLocation()
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .authorizedAlways , .authorizedWhenInUse:
mapView.showsUserLocation = true
followUserLocation()
locationManager.startUpdatingLocation()
break
case .notDetermined , .denied , .restricted:
locationManager.requestWhenInUseAuthorization()
break
default:
break
}
switch manager.accuracyAuthorization {
case .fullAccuracy:
break
case .reducedAccuracy:
break
default:
break
}
}
func followUserLocation() {
if let location = locationManager.location?.coordinate {
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: 4000, longitudinalMeters: 4000)
mapView.setRegion(region, animated: true)
}
}
func determineMyCurrentLocation() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.startUpdatingLocation()
//locationManager.startUpdatingHeading()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
let userLocation = locations.first! as CLLocation
lat = userLocation.coordinate.latitude
lon = userLocation.coordinate.longitude
let region = MKCoordinateRegion.init(center: location.coordinate, latitudinalMeters: 400000, longitudinalMeters: 400000)
self.mapView.setRegion(region, animated: true)
// Call stopUpdatingLocation() to stop listening for location updates,
// other wise this function will be called every time when user location changes.
// Need a solution for this.
manager.stopUpdatingLocation()
print("user latitude = \(userLocation.coordinate.latitude)")
print("user longitude = \(userLocation.coordinate.longitude)")
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error)
{
print("Error \(error)")
}
}
Here is the Model Class, Fire.swift
import Foundation
import MapKit
class Fire: NSObject, MKAnnotation {
let title: String?
let incidentShortDescription: String?
let incidentTypeCategory: String?
let coordinate: CLLocationCoordinate2D
init(
title: String?,
incidentShortDescription: String?,
incidentTypeCategory: String?,
coordinate: CLLocationCoordinate2D
) {
self.title = title
self.incidentShortDescription = incidentShortDescription
self.incidentTypeCategory = incidentTypeCategory
self.coordinate = coordinate
super.init()
}
init?(feature: MKGeoJSONFeature) {
// 1
guard
let point = feature.geometry.first as? MKPointAnnotation,
let propertiesData = feature.properties,
let json = try? JSONSerialization.jsonObject(with: propertiesData),
let properties = json as? [String: Any]
else {
return nil
}
// 3
title = properties ["IncidentName"] as? String
incidentShortDescription = properties["IncidentShortDescription"] as? String
incidentTypeCategory = properties["IncidentTypeCategory"] as? String
coordinate = point.coordinate
super.init()
}
var subtitle: String? {
return (incidentTypeCategory)
}
var image: UIImage {
guard let name = incidentTypeCategory else {
return #imageLiteral(resourceName: "RedFlame")
}
switch name {
case "RX":
return #imageLiteral(resourceName: "YellowFlame")
default:
return #imageLiteral(resourceName: "RedFlame")
}
}
Here is the custom MKAnnotation Class: FileMarkerView.swift
import Foundation
import MapKit
class FireMarkerView: MKAnnotationView {
override var annotation: MKAnnotation? {
willSet {
guard let fire = newValue as? Fire else {
return
}
canShowCallout = true
calloutOffset = CGPoint(x: -5, y: 5)
rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
image = fire.image
}
}
}
URLSession.shared.dataTask is an asynchronous task, meaning it calls its callback function at some indeterminate time in the future. Code executed outside of its callback (the { }) will end up getting called before the data task has actually completed. Right now, you're setting the annotations outside of that callback.
To solve this, you need to set the annotations inside of that callback function. So, where you have print([self.fires]), you can do:
DispatchQueue.main.async {
self.mapView.addAnnotations(self.fires)
}
The DispatchQueue.main.async is to make sure that an update to the UI gets called on the main thread (the URL task may return on a different thread).
Related
I am creating a yelp API app, but I am having trouble with my current location code. It seems the problem is my call to the API is happening before my values of Longitude and latitude are set, but I don't know how to rearrange my code to fix this issue. I have attached my code below lmk if you see anything I can do.
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var venuesTableView: UITableView!
let locationManager = CLLocationManager()
var long: Double = 0.0
var lat: Double = 0.0
/// Central Park, NYC coordinates
let CPLatitude: Double = 40.782483
let CPLongitude: Double = -73.963540
// Menlo Park, California Coordinates
let MPLatitude: Double = 37.4539910200113
let MPLongitude: Double = -122.19097843112596
var venues: [Venue] = []
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations:[CLLocation]) {
guard let locValue: CLLocationCoordinate2D = manager.location?.coordinate else{return}
long = locValue.longitude
lat = locValue.latitude
print(long)
print(lat)
}
override func viewDidLoad() {
super.viewDidLoad()
venuesTableView.delegate = self
venuesTableView.dataSource = self
venuesTableView.register(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: "customCell")
venuesTableView.separatorStyle = .none
retrieveVenues(latitude: lat, longitude: long, category: "eventservices",
limit: 20, sortBy: "distance", locale: "en_US") { (response, error) in
if let response = response {
self.venues = response
DispatchQueue.main.async {
self.venuesTableView.reloadData()
}
}
print("Does this work?")
print(self.long)
print(self.lat)
}
locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled(){
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
}
func userCurrentLocation(){
}
}
Create a separate function for the api call.
private func getVenues() {
retrieveVenues(latitude: lat, longitude: long, category: "eventservices",
limit: 20, sortBy: "distance", locale: "en_US") { (response, error) in
if let response = response {
self.venues = response
DispatchQueue.main.async {
self.venuesTableView.reloadData()
}
}
print("Does this work?")
print(self.long)
print(self.lat)
}
}
Then call this from the delegate method.
func locationManager(_ manager: CLLocationManager, didUpdateLocations
locations: [CLLocation]) {
guard let locValue: CLLocationCoordinate2D = manager.location?.coordinate else{return}
long = locValue.longitude
lat = locValue.latitude
getVenues()
}
I am creating my first IOS app and am having a very difficult time understanding why the CalloutAccessoryControlTapped Control function is not working.
The Info button is appearing on the annotation, but when the user taps on the Info button, nothing happens. See screenshot here: Screenshot of Annotation.
I created a function in the View Controller to add the Callout accessory view and suspect there is something wrong with the function.
Any help would be greatly appreciated!
Here is the Function:
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let annotation = view.annotation as? Fire
let status = "Fire Status: Active"
let distance = "Fire Location: 25 miles away"
let ac = UIAlertController(title: status, message: distance, preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "Close", style: .default))
self.present(ac, animated: true, completion: nil)
}
Here is the code from my View Controller -- updated 4/20/2021:
import MapKit
import UIKit
import CoreLocation
class FireMapViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var FireMapView: MKMapView!
let mapView = MKMapView()
var locationManager = CLLocationManager()
var lat = Double()
var lon = Double()
var fires: [Fire] = []
override func viewDidLoad() {
super.viewDidLoad()
setupMapView()
checkLocationServices()
mapView.register(FireMarkerView.self,forAnnotationViewWithReuseIdentifier:
MKMapViewDefaultAnnotationViewReuseIdentifier)
//Initiate URL Session to get Fire data and add Fire data to the Map Annotations
if let url = URL(string: "https://services3.arcgis.com/T4QMspbfLg3qTGWY/arcgis/rest/services/Active_Fires/FeatureServer/0/query?outFields=*&where=1%3D1&f=geojson") {
URLSession.shared.dataTask(with: url) {data, response, error in
if let data = data {
do {
let features = try MKGeoJSONDecoder().decode(data)
.compactMap { $0 as? MKGeoJSONFeature }
let validWorks = features.compactMap(Fire.init)
self.fires.append(contentsOf: validWorks)
DispatchQueue.main.async {
self.mapView.addAnnotations(self.fires)
}
}
catch let error {
print(error)
}
}
}.resume()
}
}
func setupMapView() {
view.addSubview(mapView)
mapView.translatesAutoresizingMaskIntoConstraints = false
mapView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
mapView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
mapView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
mapView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
}
func checkLocationServices() {
if CLLocationManager.locationServicesEnabled() {
setupLocationManager()
locationManagerDidChangeAuthorization(locationManager)
} else {
// the user didn't turn it on
}
}
//Get the current location permissions for User
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .authorizedAlways , .authorizedWhenInUse:
mapView.showsUserLocation = true
followUserLocation()
locationManager.startUpdatingLocation()
break
case .notDetermined , .denied , .restricted:
locationManager.requestWhenInUseAuthorization()
break
default:
break
}
switch manager.accuracyAuthorization {
case .fullAccuracy:
break
case .reducedAccuracy:
break
default:
break
}
}
func setupLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
let region = MKCoordinateRegion.init(center: location.coordinate, latitudinalMeters: 160934, longitudinalMeters: 160934)
mapView.setRegion(region, animated: true)
print("Fire Map User Location on Map:", location.coordinate)
// Call stopUpdatingLocation() to stop listening for location updates,
// other wise this function will be called every time when user location changes.
// Need a solution for this.
manager.stopUpdatingLocation()
}
func followUserLocation() {
if let location = locationManager.location?.coordinate {
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: 4000, longitudinalMeters: 4000)
mapView.setRegion(region, animated: true)
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
locationManagerDidChangeAuthorization(locationManager)
}
//Function to display additional Fire data after User selects the Callout Info button on the Annotation
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let annotation = view.annotation as? Fire
print("callout Accessory Tapped!")
let status = "Fire Status: Active"
let distance = "Fire Location: 25 miles away"
let ac = UIAlertController(title: status, message: distance, preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "Close", style: .default))
self.present(ac, animated: true, completion: nil)
}
}
Here is the Class for the Annotation:
import Foundation
import MapKit
class FireMarkerView: MKAnnotationView {
override var annotation: MKAnnotation? {
willSet {
guard let fire = newValue as? Fire else {
return
}
canShowCallout = true
calloutOffset = CGPoint(x: -5, y: 5)
rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
image = fire.image
}
}
}
Here is the Class for the Model:
import Foundation
import MapKit
class Fire: NSObject, MKAnnotation {
let title: String?
let county: String?
let city: String?
let incidentTypeCategory: String?
let coordinate: CLLocationCoordinate2D
let percentContained: Int?
let lastUpdateDateTime: Int?
init(
title: String?,
county: String?,
city: String?,
incidentTypeCategory: String?,
coordinate: CLLocationCoordinate2D,
percentContained: Int?,
lastUpdateDateTime: Int?
) {
self.title = title
self.county = county
self.city = city
self.incidentTypeCategory = incidentTypeCategory
self.coordinate = coordinate
self.percentContained = percentContained
self.lastUpdateDateTime = lastUpdateDateTime
super.init()
}
init?(feature: MKGeoJSONFeature) {
// 1
guard
let point = feature.geometry.first as? MKPointAnnotation,
let propertiesData = feature.properties,
let json = try? JSONSerialization.jsonObject(with: propertiesData),
let properties = json as? [String: Any]
else {
return nil
}
// 2
title = ((properties["IncidentName"] as? String ?? "Unknown") + " Wildfire")
county = ((properties["POOCounty"] as? String ?? "Unknown") + " County")
city = properties["POOCity"] as? String
incidentTypeCategory = properties["IncidentTypeCategory"] as? String
coordinate = point.coordinate
percentContained = properties["PercentContained"] as? Int
lastUpdateDateTime = properties["ModifiedOnDateTime_dt"] as? Int
super.init()
}
var subtitle: String? {
return (county)
}
var image: UIImage {
guard let name = incidentTypeCategory else {
return #imageLiteral(resourceName: "RedFlame")
}
switch name {
case "RX":
return #imageLiteral(resourceName: "YellowFlame")
default:
return #imageLiteral(resourceName: "RedFlame")
}
}
}
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'm getting this error "Value of type 'UIGestureRecognizer' has no member 'numberOfTapsRequired' with my code as I'm adding double tap, I read many other posts on the subject and I can't find where is the problem. Can you spot what I'm doing wrong in the addDoubleTap function? This is the entire code
import UIKit
import MapKit
import CoreLocation
class MapViewController: UIViewController, MKMapViewDelegate, UIGestureRecognizerDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var dropPinButton: UIButton!
#IBOutlet weak var centerMApButton: UIButton!
var pin: AnnotationPinn!
// variables that hold values for selected icon, to be used for displaying the pin
var dataReceived: String?
// udemy location manager couse
var locationManager = CLLocationManager()
let authorizationStatus = CLLocationManager.authorizationStatus()
let regionRadius: Double = 1000.0
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
locationManager.delegate = self
configureLocationServices()
setCoordinates()
// addDoubleTap()
let latitude: Double = setCoordinates().lat //44.498955
let longitude: Double = setCoordinates().lon //11.327591
let title: String? = dataReceived
var subtitle: String? = dataReceived
let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
let region = MKCoordinateRegionMakeWithDistance(coordinate, 1000, 1000)
mapView.setRegion(region, animated: true)
// title may be to be taken from iconNames[indexpath.row]
pin = AnnotationPinn(title: "", subtitle: "", coordinate: coordinate)
}
func addDoubleTap() { //not finding numberOfTapsRequired
let doubleTap = UIGestureRecognizer(target: self, action: #selector(MapViewController.dropPin))
doubleTap.numberOfTapsRequired = 2
doubleTap.delegate = self
mapView.addGestureRecognizer(doubleTap)
}
func centerMapOnLocation() {
guard let coordinate2 = locationManager.location?.coordinate else {
return
}
let coordinateRegion = MKCoordinateRegionMakeWithDistance(coordinate2, regionRadius * 2.0, regionRadius * 2.0)
mapView.setRegion(coordinateRegion, animated: true)
}
//custom pin image , named: iconsImages[indexPath.row]
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = MKAnnotationView(annotation: pin, reuseIdentifier: "strada chiusa")
//added if statement for displaying user location blue dot
if annotation is MKUserLocation{
return nil
} else {
annotationView.image = UIImage(named: dataReceived!) // here choose the image to load
let transform = CGAffineTransform(scaleX: 0.22, y: 0.22)
annotationView.transform = transform
return annotationView
}
}
// get coordinates into variables for the dropPin()
func setCoordinates() -> ( lat: Double, lon:Double) {
let location = locationManager.location?.coordinate
let lat:Double = (location?.latitude)!
let lon:Double = (location?.longitude)!
print(lat,lon)
return (lat, lon)
}
func dropPin() {
// print("3\(String(describing: dataReceived))")
mapView.addAnnotation(pin)
}
#IBAction func dropPinButton(_ sender: Any) {
performSegue(withIdentifier: "chooseIconSegue", sender: self)
}
#IBAction func centerMapButton(_ sender: Any) {
if authorizationStatus == .authorizedAlways || authorizationStatus == .authorizedWhenInUse{
centerMapOnLocation()
}
}
#IBAction func unwindHere(sender:UIStoryboardSegue) { // datas coming back
if let sourceViewController = sender.source as? IconsViewController {
// MyVariables.dataReceived = sourceViewController.dataPassed
dataReceived = sourceViewController.dataPassed
// title = sourceViewController.dataPassed
print(dataReceived!)
dropPin()
mapView.addAnnotation(pin)
}
}
func configureLocationServices() {
if authorizationStatus == .notDetermined{
locationManager.requestAlwaysAuthorization()
} else {
return
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
centerMapOnLocation()
}
}
extension MapViewController: CLLocationManagerDelegate{
}
UIGestureRecognizer doesn't have a property called numberOfTapsRequired, this is why you're getting the compilation error.
What you're after is UITapGestureRecognizer.
I have been having a real tough time making multiple annotations on a MapView. So far I have been able to create the relevant classes to download, parse and store the data into an array that can be used. Yet I am still struggling to use the said data and make the annotations required.
HomeModel Class - download and parse the required information from the server
import UIKit
import Foundation
protocol HomeModelProtocol: class {
func itemsDownloaded(items: NSArray)
}
class HomeModel: NSObject, URLSessionDataDelegate {
weak var delegate: HomeModelProtocol!
var data = Data()
let urlPath: String = "https://FAKEDATABASEURL.XYZ"
func downloadItems() {
let url: URL = URL(string: urlPath)!
let defaultSession = Foundation.URLSession(configuration: URLSessionConfiguration.default)
let task = defaultSession.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Failed to download data")
} else {
print("Data downloaded")
self.parseJSON(data!)
}
}
task.resume()
}
func parseJSON(_ data:Data) {
var jsonResult = NSArray()
do {
jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as! NSArray
} catch let error as NSError {
print(error)
}
var jsonElement = NSDictionary()
let locations = NSMutableArray()
for i in 0 ..< jsonResult.count {
jsonElement = jsonResult[i] as! NSDictionary
let location = LocationModel()
if let name = jsonElement["Name"] as? String,
let address = jsonElement["Address"] as? String,
let latitude = jsonElement["Latitude"] as? String,
let longitude = jsonElement["Longitude"] as? String {
location.name = name
location.address = address
location.latitude = latitude
location.longitude = longitude
}
locations.add(location)
}
DispatchQueue.main.async(execute: { ()-> Void in
self.delegate.itemsDownloaded(items: locations)
})
}
}
LocalModel Class - To store the data into an array to be used by the app
import UIKit
import Foundation
class LocationModel: NSObject {
// Properties
var name: String?
var address: String?
var latitude: String?
var longitude: String?
// Empty constructor
override init() { }
// Construct with #name, #address, #latitude and #longitude.
init(name: String, address: String, latitude: String, longitude: String) {
self.name = name
self.address = address
self.latitude = latitude
self.longitude = longitude
}
// Print the object's current state
override var description: String {
return "Name: \(String(describing: name)), Address:\(String(describing: address)), Latitude: \(String(describing: latitude)), Longitude: \(String(describing: longitude))"
}
}
Map View Controller - Controls the map for the application
import UIKit
import MapKit
import CoreLocation
class HotPlacesViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
var isFirstTime = true
var locationManager = CLLocationManager()
let newPin = MKPointAnnotation()
var selectedLocation:LocationModel?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Setup the location services delegate in this class.
locationManager.delegate = self
// This little method requests the users permission for location services whilst in this view controller.
if CLLocationManager.authorizationStatus() == .notDetermined {
self.locationManager.requestAlwaysAuthorization()
let alert = UIAlertController(title: "You can change this option in the Settings App", message: "So keep calm your selection is not permanent. 🙂",
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
// Drops the pin on the users current location.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
mapView.removeAnnotation(newPin)
let location = locations.last! as CLLocation
let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
if(self.isFirstTime) {
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
// Set the region on the map.
mapView.setRegion(region, animated: true)
self.isFirstTime = false
}
newPin.coordinate = location.coordinate
mapView.addAnnotation(newPin)
}
}
Make your let locations = NSMutableArray() array accessible by other classes so that you can use this array in your HotPlacesViewController class.
Then in your HotPlacesViewController class declare a property for holding locations data. And load the data inside viewDidLoad() like:
class HotPlacesViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
// declare a property which will hold the locations data
override func viewDidLoad() {
...
...
locations = // load your data here
}
}
Then, for multiple annotations follow this logic:
for location in locations {
let annotation = MKPointAnnotation()
annotation.title = location.name
annotation.coordinate = CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)
mapView.addAnnotation(annotation)
}