How to get different icons to show on Annotation View in Swift? - ios

I am new to swift. I am creating an AR app with AR annotation View, where I have created a places object to show on Annotation AR view, but I can't get different icons to show based on locations in AR view. I only get one specific icon to all the places. Please have a look at my code to help me out with this issue. Thank you very much!
Here is my code:
import UIKit
import AVFoundation
protocol AnnotationViewDelegate {
func didTouch(annotationView: AnnotationView)
}
class AnnotationView: ARAnnotationView {
var titleLabel: UILabel?
var distanceLabel: UILabel?
var delegate: AnnotationViewDelegate?
var backgroundView: UIView?
var pinImage: UIImageView?
let pinoImage = ["bakeries", "banks", "barber", "bars", "beaches", "breweries", "cardealer", "carrepair", "church", "cinema",
"coffee", "college", "dentist", "dining", "doctors", "drycleaning", "fastfood", "firetruck", "fitness", "gas",
"grocery", "hospital", "hotel", "library", "lounges", "motorcycledealers", "musicvenues", "park", "petstore",
"pharmacy", "police", "postoffice", "train", "transportation", "zoo"]
override func didMoveToSuperview() {
super.didMoveToSuperview()
loadUI()
}
func getRandomColor() -> UIColor{
let red:CGFloat = CGFloat(drand48())
let green:CGFloat = CGFloat(drand48())
let blue:CGFloat = CGFloat(drand48())
return UIColor(red:red, green: green, blue: blue, alpha: 1.0)
}
func loadUI() {
titleLabel?.removeFromSuperview()
distanceLabel?.removeFromSuperview()
backgroundView?.removeFromSuperview()
pinImage?.removeFromSuperview()
backgroundView = UIView(frame: CGRect(x: 0, y: 0, width: self.frame.size.width, height: 70))
backgroundView?.backgroundColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0.5)
backgroundView?.layer.cornerRadius = 10.0
self.addSubview(backgroundView!)
pinImage = UIImageView(frame: CGRect(x: 16, y: 8, width: 37.76, height: 54))
pinImage?.contentMode = UIViewContentMode.scaleAspectFit
self.backgroundView?.addSubview(pinImage!)
let label = UILabel(frame: CGRect(x: 0, y: 0, width: self.frame.size.width, height: 22.0))
label.font = UIFont(name: "AvenirNext", size: 3)
label.numberOfLines = 0
label.textAlignment = .center
label.textColor = UIColor.white
self.backgroundView?.addSubview(label)
self.titleLabel = label
distanceLabel = UILabel(frame: CGRect(x: 66, y: 47, width: self.frame.size.width, height: 15.0))
distanceLabel?.textColor = UIColor.black
distanceLabel?.font = UIFont(name: "Montserrat-Regular", size: 12)
self.backgroundView?.addSubview(distanceLabel!)
if let annotation = annotation as? Place {
titleLabel?.text = annotation.placeName
distanceLabel?.text = String(format: "%.2f mi", annotation.distanceFromUser * 0.000621371)
pinImage?.image = UIImage(named: "FastFood")
}
}
override func layoutSubviews() {
super.layoutSubviews()
backgroundView?.frame = CGRect(x: 0, y: 0, width: 170, height: 80)
titleLabel?.frame = CGRect(x: 50, y: 8, width: self.frame.size.width - 66, height: 22.0)
distanceLabel?.frame = CGRect(x: 50, y: 30, width: self.frame.size.width, height: 20)
pinImage = UIImageView(frame: CGRect(x: 16, y: 8, width: 37.76, height: 54))
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
delegate?.didTouch(annotationView: self)
}
}
MapViewController
import UIKit
import MapKit
import CoreLocation
class MapViewController: BaseViewController, UITabBarDelegate{
#IBOutlet weak var leadingConstraints: NSLayoutConstraint!
#IBOutlet weak var mapView: MKMapView!
fileprivate let locationManager = CLLocationManager()
fileprivate var startedLoadingPOIs = false
fileprivate var places = [Place]()
fileprivate var arViewController: ARViewController!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
var nearMeIndexSelected = NearMeIndexTitle()
var place: Place?
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
locationManager.requestWhenInUseAuthorization()
}
#IBAction func showARController(_ sender: Any) {
arViewController = ARViewController()
arViewController.dataSource = self
arViewController.maxVisibleAnnotations = 30
arViewController.headingSmoothingFactor = 0.05
arViewController.setAnnotations(places)
self.present(arViewController, animated: true, completion: nil)
}
}
extension MapViewController: CLLocationManagerDelegate, MKMapViewDelegate {
func mapView(_ mapView: MKMapView,
viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
mapView.tintColor = #colorLiteral(red: 0.08235294118, green: 0.7058823529, blue: 0.9450980392, alpha: 1)
return nil
} else {
let pin = mapView.view(for: annotation) ?? MKAnnotationView(annotation: annotation, reuseIdentifier: nil)
pin.image = UIImage(named: "pins")
return pin
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if locations.count > 0 {
let location = locations.last!
print("Accuracy: \(location.horizontalAccuracy)")
if location.horizontalAccuracy < 100 {
manager.stopUpdatingLocation()
let span = MKCoordinateSpan(latitudeDelta: 0.013, longitudeDelta: 0.013)
let region = MKCoordinateRegion(center: location.coordinate, span: span)
mapView.region = region
if !startedLoadingPOIs {
DispatchQueue.main.async {
self.activityIndicator.startAnimating()
}
startedLoadingPOIs = true
let loader = PlacesLoader()
loader.loadPOIS(location: location, radius: 1500) { placesDict, error in
if let dict = placesDict {
guard let placesArray = dict.object(forKey: "results") as? [NSDictionary] else { return }
for placeDict in placesArray {
let latitude = placeDict.value(forKeyPath: "geometry.location.lat") as! CLLocationDegrees
let longitude = placeDict.value(forKeyPath: "geometry.location.lng") as! CLLocationDegrees
let reference = placeDict.object(forKey: "reference") as! String
let name = placeDict.object(forKey: "name") as! String
let address = placeDict.object(forKey: "vicinity") as! String
let location = CLLocation(latitude: latitude, longitude: longitude)
let place = Place(location: location, reference: reference, name: name, address: address)
self.places.append(place)
let annotation = PlaceAnnotation(location: place.location!.coordinate, title: place.placeName)
DispatchQueue.main.async {
self.mapView.addAnnotation(annotation)
}
}
DispatchQueue.main.async {
self.activityIndicator.stopAnimating()
self.mapView.isHidden = false
}
}
}
}
}
}
}
}
extension MapViewController: ARDataSource {
func ar(_ arViewController: ARViewController, viewForAnnotation: ARAnnotation) -> ARAnnotationView {
let annotationView = AnnotationView()
arViewController.title = "MyApp"
annotationView.annotation = viewForAnnotation
annotationView.delegate = self
annotationView.frame = CGRect(x: 0, y: 0, width: 150, height: 50)
return annotationView
}
}

In Mapview class add the below code
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if (annotation is MKUserLocation) {
return nil
}
let reuseId = "reuseId"
let anView : AnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) as! AnnotationView
if let annotation = annotation as? Place {
anView.titleLabel?.text = annotation.placeName
anView.distanceLabel?.text = String(format: "%.2f mi", annotation.distanceFromUser * 0.000621371)
if(annotation.placeName == "bakeries"){
anView.pinImage?.image = UIImage(named: "Bakery")
}
else{
anView.pinImage?.image = UIImage(named: "FastFood")
}
}
anView.canShowCallout = false
return anView
}
by the above the method you can change pin custom image

Related

how to use location for Eureka pod for iOS form?

I have added various filed successfully using Eureka form builder for iOS but getting an error while adding locationrow
I imported following framework still getting the issue
import UIKit
import CoreLocation
import MapKit
import Eureka
is there any other way to use locationrow in eureka?
you will have to first add LocationRow in your pod file and then update your pod file so that cocoapod downloads LocationRow. build the project once to refresh the project with the new files.
and then in the above class import LocationRow
LocationRow (Included as custom row in the example project)
These words were taken from https://github.com/xmartlabs/Eureka
So far Location Row can't be included into Eureka framework cause it's required MapKit or something similar, that's why eureka community created separated class/object.
I created a separate folder/file for the location code
import Foundation
import UIKit
import MapKit
import Eureka
//MARK: LocationRow
public final class LocationRow: OptionsRow<PushSelectorCell<CLLocation>>, PresenterRowType, RowType {
public typealias PresenterRow = MapViewController
/// Defines how the view controller will be presented, pushed, etc.
public var presentationMode: PresentationMode<PresenterRow>?
/// Will be called before the presentation occurs.
public var onPresentCallback: ((FormViewController, PresenterRow) -> Void)?
public required init(tag: String?) {
super.init(tag: tag)
presentationMode = .show(controllerProvider: ControllerProvider.callback { return MapViewController(){ _ in } }, onDismiss: { vc in _ = vc.navigationController?.popViewController(animated: true) })
displayValueFor = {
guard let location = $0 else { return "" }
let fmt = NumberFormatter()
fmt.maximumFractionDigits = 4
fmt.minimumFractionDigits = 4
let latitude = fmt.string(from: NSNumber(value: location.coordinate.latitude))!
let longitude = fmt.string(from: NSNumber(value: location.coordinate.longitude))!
return "\(latitude), \(longitude)"
}
}
/**
Extends `didSelect` method
*/
public override func customDidSelect() {
super.customDidSelect()
guard let presentationMode = presentationMode, !isDisabled else { return }
if let controller = presentationMode.makeController() {
controller.row = self
controller.title = selectorTitle ?? controller.title
onPresentCallback?(cell.formViewController()!, controller)
presentationMode.present(controller, row: self, presentingController: self.cell.formViewController()!)
} else {
presentationMode.present(nil, row: self, presentingController: self.cell.formViewController()!)
}
}
/**
Prepares the pushed row setting its title and completion callback.
*/
public override func prepare(for segue: UIStoryboardSegue) {
super.prepare(for: segue)
guard let rowVC = segue.destination as? PresenterRow else { return }
rowVC.title = selectorTitle ?? rowVC.title
rowVC.onDismissCallback = presentationMode?.onDismissCallback ?? rowVC.onDismissCallback
onPresentCallback?(cell.formViewController()!, rowVC)
rowVC.row = self
}
}
public class MapViewController : UIViewController, TypedRowControllerType, MKMapViewDelegate {
public var row: RowOf<CLLocation>!
public var onDismissCallback: ((UIViewController) -> ())?
lazy var mapView : MKMapView = { [unowned self] in
let v = MKMapView(frame: self.view.bounds)
v.autoresizingMask = [.flexibleWidth, .flexibleHeight]
return v
}()
lazy var pinView: UIImageView = { [unowned self] in
let v = UIImageView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
v.image = UIImage(named: "map_pin", in: Bundle(for: MapViewController.self), compatibleWith: nil)
v.image = v.image?.withRenderingMode(.alwaysTemplate)
v.tintColor = self.view.tintColor
v.backgroundColor = .clear
v.clipsToBounds = true
v.contentMode = .scaleAspectFit
v.isUserInteractionEnabled = false
return v
}()
let width: CGFloat = 10.0
let height: CGFloat = 5.0
lazy var ellipse: UIBezierPath = { [unowned self] in
let ellipse = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: self.width, height: self.height))
return ellipse
}()
lazy var ellipsisLayer: CAShapeLayer = { [unowned self] in
let layer = CAShapeLayer()
layer.bounds = CGRect(x: 0, y: 0, width: self.width, height: self.height)
layer.path = self.ellipse.cgPath
layer.fillColor = UIColor.gray.cgColor
layer.fillRule = .nonZero
layer.lineCap = .butt
layer.lineDashPattern = nil
layer.lineDashPhase = 0.0
layer.lineJoin = .miter
layer.lineWidth = 1.0
layer.miterLimit = 10.0
layer.strokeColor = UIColor.gray.cgColor
return layer
}()
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nil, bundle: nil)
}
convenience public init(_ callback: ((UIViewController) -> ())?){
self.init(nibName: nil, bundle: nil)
onDismissCallback = callback
}
public override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(mapView)
mapView.delegate = self
mapView.addSubview(pinView)
mapView.layer.insertSublayer(ellipsisLayer, below: pinView.layer)
let button = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(MapViewController.tappedDone(_:)))
button.title = "Done"
navigationItem.rightBarButtonItem = button
if let value = row.value {
let region = MKCoordinateRegion(center: value.coordinate, latitudinalMeters: 400, longitudinalMeters: 400)
mapView.setRegion(region, animated: true)
}
else{
mapView.showsUserLocation = true
}
updateTitle()
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let center = mapView.convert(mapView.centerCoordinate, toPointTo: pinView)
pinView.center = CGPoint(x: center.x, y: center.y - (pinView.bounds.height/2))
ellipsisLayer.position = center
}
#objc func tappedDone(_ sender: UIBarButtonItem){
let target = mapView.convert(ellipsisLayer.position, toCoordinateFrom: mapView)
row.value = CLLocation(latitude: target.latitude, longitude: target.longitude)
onDismissCallback?(self)
}
func updateTitle(){
let fmt = NumberFormatter()
fmt.maximumFractionDigits = 4
fmt.minimumFractionDigits = 4
let latitude = fmt.string(from: NSNumber(value: mapView.centerCoordinate.latitude))!
let longitude = fmt.string(from: NSNumber(value: mapView.centerCoordinate.longitude))!
title = "\(latitude), \(longitude)"
}
public func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
ellipsisLayer.transform = CATransform3DMakeScale(0.5, 0.5, 1)
UIView.animate(withDuration: 0.2, animations: { [weak self] in
self?.pinView.center = CGPoint(x: self!.pinView.center.x, y: self!.pinView.center.y - 10)
})
}
public func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
ellipsisLayer.transform = CATransform3DIdentity
UIView.animate(withDuration: 0.2, animations: { [weak self] in
self?.pinView.center = CGPoint(x: self!.pinView.center.x, y: self!.pinView.center.y + 10)
})
updateTitle()
}
}
In my form, I just added a new row like this:
<<< LocationRow("Location"){
$0.title = $0.tag
$0.value = CLLocation(latitude: -34.9124, longitude: -56.1594)
}
This is also a good resource to use.
https://fluttergeek.com/blog/eureka-locationrow/

GMSMarker infoWindow not updating

I'm using Google map and it's marker. For displaying information on the marker I'm using custom View to display it. But values are not updating once I init it.Below is my code for that.
func mapView(_ mapView: GMSMapView!, markerInfoWindow marker: GMSMarker) -> UIView? {
let location = CLLocation(latitude: marker.position.latitude, longitude: marker.position.longitude
var markerView : MarkerInfoView = Bundle.main.loadNibNamed("MarkerInfoView", owner: self, options: nil)![0] as! MarkerInfoView
let geoCoder = CLGeocoder()
geoCoder.reverseGeocodeLocation(location) { (placemarkers, error) in
if let placemarker = placemarkers?.last {
var strAddress = ""
if let str = placemarker.name {
strAddress += str
}
if let str = placemarker.subAdministrativeArea {
strAddress += ", " + str
}
print(strAddress)
markerView.deviceInfo.text = "HELLO TESTING"
print(markerView.deviceInfo.text!) // This is printing "HELLO TESTING", but not updating on marker
markerView.addressInfo.text = strAddress
}
}
if let str = marker.snippet {
markerView.deviceInfo.text = str.components(separatedBy: "|")[0]
//TODO: add time
markerView.dateInfo.text = str.components(separatedBy: "|")[1]
// markerView.addressInfo.text = ""
}
else {
markerView.deviceInfo.text = ""
markerView.dateInfo.text = ""
// markerView.addressInfo.text = ""
}
return markerView
}
Please guide me how to update values in infoWindow.
Try this code
// MARKER - GoogleMaps delegate
func mapView(_ mapView: GMSMapView, markerInfoContents marker: GMSMarker) -> UIView? {
print("title \(markerTitle)")
var infoView:UIView!
infoView = UIView()
infoView.frame = CGRect(x: 0, y: 0, width: 300, height: 75)
infoView.backgroundColor = .black
let orderIDLbl = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 75))
orderIDLbl.text = "markerTitle" //Your title here
orderIDLbl.font = UIFont.systemFont(ofSize: 15)
orderIDLbl.textAlignment = NSTextAlignment.center
orderIDLbl.numberOfLines = 0
orderIDLbl.textColor = .white
infoView.addSubview(orderIDLbl)
return infoView
}

add a permanent label below a marker swift

I want to add multiple markers to the map view. Each marker having a text label below it always i.e it should always be visible. Also I want to add my own image as its icon.
Herewith I am attaching a screenshot of what I want.
Code Work
func addGroundOverlay(position: CLLocationCoordinate2D, veh_num: String) {
let overlay = GMSGroundOverlay(position: position, icon: newImage(text: veh_num, size: CGSize(width: 150.0, height: 150.0)), zoomLevel: 10)
overlay.bearing = 0
overlay.map = (self.view as! GMSMapView)
}
func newImage(text: String, size: CGSize) -> UIImage {
let data = text.data(using: String.Encoding.utf8, allowLossyConversion: true)
let drawText = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
let textFontAttributes = [
NSAttributedStringKey.font: UIFont(name: "Helvetica Bold", size: 10)!,
NSAttributedStringKey.foregroundColor: UIColor.red,
]
UIGraphicsBeginImageContextWithOptions(size, false, 0)
drawText?.draw(in: CGRect(x: 0,y: 0, width: size.width, height: size.height), withAttributes: textFontAttributes)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
Image for what I have tried
result image
I found out the solution, as lack of time I tried for Apple Map, but you try for google map also.
Steps
Get the location where you wanted to show annotation.
Add this point annotation on Map.
Create a UILabel(says, lbl) with text as you wanted.
Add this text on a view (says, viewAn).
Now capture the viewAn and make it image.
Use this image for location marker.
Below is the code work for Apple Map and out of simulator is added below it and it is working properly. Follow the above steps and definatly it will work for google map also.
Code Work
import UIKit
import MapKit
class MapVC: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView! // Apple mapview Outlet
var location: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 28.5961279, longitude: 77.1587375)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let anno = MKPointAnnotation();
anno.coordinate = location;
mapView.addAnnotation(anno);
}
// To capture view
func captureScreen(_ viewcapture : UIView) -> UIImage {
UIGraphicsBeginImageContextWithOptions(viewcapture.frame.size, viewcapture.isOpaque, 0.0)
viewcapture.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!;
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// Don't want to show a custom image if the annotation is the user's location.
guard !(annotation is MKUserLocation) else {
return nil
}
// Better to make this class property
let annotationIdentifier = "AnnotationIdentifier"
var annotationView: MKAnnotationView?
if let dequeuedAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationIdentifier) {
annotationView = dequeuedAnnotationView
annotationView?.annotation = annotation
}
else {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
annotationView?.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
}
if let annotationView = annotationView {
// Configure your annotation view here
// view for annotation
let viewAn = UIView()
viewAn.frame = CGRect(x: 0, y: 0, width: 80, height: 18)
// label as required
let lbl = UILabel()
lbl.text = "ABC 123"
lbl.textColor = UIColor.black
lbl.backgroundColor = UIColor.cyan
// add label to viewAn
lbl.frame = viewAn.bounds
viewAn.addSubview(lbl)
// capture viewAn
let img = self.captureScreen(viewAn)
annotationView.canShowCallout = true
// set marker
annotationView.image = img
}
return annotationView
}
}
OutPut :
Edit : image trasparency
use this below func
func changeWhiteColorTransparent(_ image: UIImage) -> UIImage {
let rawImageRef = image.cgImage as! CGImage
let colorMasking : [CGFloat] = [222, 255, 222, 255, 222, 255]
UIGraphicsBeginImageContext(image.size)
let maskedImageRef: CGImage = rawImageRef.copy(maskingColorComponents: colorMasking)!
do {
//if in iphone
UIGraphicsGetCurrentContext()?.translateBy(x: 0.0, y: image.size.height)
UIGraphicsGetCurrentContext()?.scaleBy(x: 1.0, y: -1.0)
}
UIGraphicsGetCurrentContext()?.draw(maskedImageRef, in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))
let result = UIGraphicsGetImageFromCurrentImageContext() as! UIImage
UIGraphicsEndImageContext()
return result ?? UIImage()
}
Func callie
Replace code of upper annotationview with below
let viewAn = UIView()
viewAn.frame = CGRect(x: 0, y: 0, width: 80, height: 18)
let lbl = UILabel()
lbl.text = "ABC 123"
lbl.textColor = UIColor.black
lbl.backgroundColor = UIColor.clear
viewAn.backgroundColor = UIColor.white
lbl.frame = viewAn.bounds
viewAn.addSubview(lbl)
let img = self.captureScreen(viewAn)
let aImgNew = self.changeWhiteColorTransparent(img)
annotationView.backgroundColor = UIColor.clear
annotationView.canShowCallout = true
annotationView.image = aImgNew
Output:

Display array of text in annotationView using index

I can't display all the addresses in my variable
var allAddress: [Address] = []
In this variable i get from firebase a small amount of addresses and i want to display all addresses in my annotationView, but when i try to display all addresses i see only one address in all annotationView though if i make printed my using for..in.. index i see index0, index1, index2, index3 and other... as well if i printed this:
print("address - \(allAddress[index].address)")
i get all address which i have in firebase, their total 6.
It's print my index and allAddress[index].address
index 1
address - Москва, ул. Правды д.24, строение 3
index 2
address - Москва, ул.Электрозаводская д.21
index 3
address - Москва, ул.Бутырская д.8
index 4
address - Москва, 2-Я Звенигородская улица 12 строение 21
index 5
address - Москва, Николоямская 52, стр. 1
It's my code:
let centerInfo = UILabel(frame: CGRect(x: 0, y: 0, width: 150, height: 100))
for index in 0..<allAddress.count {
print("index \(index)")
centerInfo.text = allAddress[index].address
centerInfo.numberOfLines = 0
centerInfo.lineBreakMode = .byWordWrapping
print("address - \(allAddress[index].address)")
}
annotationView?.detailCalloutAccessoryView = centerInfo
How i can display all addresses in my centerInfo.text?
And pls check .gif which shows the same address on all annotationView
P.S. in annotation.title don't need to use, me do not fit
UPDATED. All code:
class AllAddressMapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
var allAddress: [Address] = []
var studioRef: DatabaseReference!
override func viewDidLoad() {
super.viewDidLoad()
studioRef = Database.database().reference(withPath: "Photo1")
studioRef.observe(.value, with: { (snapshot) in
for imageSnap in snapshot.children {
let studioObj = Studio(snapshot: imageSnap as! DataSnapshot)
self.allAddress.append(studioObj)
for index in 0..<self.allAddress.count {
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(self.allAddress[index].address, completionHandler: { (placemarks, error) in
guard error == nil else { return }
guard let placemarks = placemarks else { return }
if let placemark = placemarks.first {
let annotation = MKPointAnnotation()
guard let address = placemark.location else { return }
annotation.coordinate = address.coordinate
self.mapView.addAnnotation(annotation)
}
geocoder.cancelGeocode()
})
}
}
})
mapView.delegate = self
mapView.mapType = .standard
mapView.isZoomEnabled = true
mapView.isScrollEnabled = true
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard !(annotation is MKUserLocation) else { return nil }
let annotationID = "PinMap"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationID) as? MKPinAnnotationView
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotationID)
annotationView?.canShowCallout = true
} else {
annotationView?.annotation = annotation
}
let leftImageNavigationButton = UIImage(named: "auto")
let tintedColorleftImageNavigationButton = leftImageNavigationButton?.withRenderingMode(.alwaysTemplate)
let leftNavigationButton = UIButton(type: .custom)
leftNavigationButton.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
leftNavigationButton.setImage(tintedColorleftImageNavigationButton, for: UIControlState())
leftNavigationButton.tintColor = #colorLiteral(red: 0, green: 0.4784313725, blue: 1, alpha: 1)
annotationView?.leftCalloutAccessoryView = leftNavigationButton
let rightButtonInfo = UIButton(type: .detailDisclosure)
annotationView?.rightCalloutAccessoryView = rightButtonInfo
let centerInfo = UILabel(frame: CGRect(x: 0, y: 0, width: 150, height: 100))
for index in 0..<allAddress.count {
print("index \(index)")
centerInfo.text = allAddress[index].address
centerInfo.numberOfLines = 0
centerInfo.lineBreakMode = .byWordWrapping
print("address - \(allAddress[index].address)")
}
annotationView?.detailCalloutAccessoryView = centerInfo
annotationView?.calloutOffset = CGPoint(x: -8, y: 0)
return annotationView
}
}
From your code, it looks , you are using same UILabel for all annotation and in loop you are setting the text property of the same UILabel every time. That's why it's showing the last address on all annotation.
Try to assign a separate UILabel for each annotation and set text property to the designated address
Well, the idea is to get all annotations on your map like that and try to find the right annotation index to reuse it into your allAddress array :
var indexValue = 0
for annotationInMap in mapView.annotations {
if annotation == annotationInMap {
let centerInfo = UILabel(frame: CGRect(x: 0, y: 0, width: 150, height: 100))
centerInfo.text = allAddress[indexValue].address
centerInfo.numberOfLines = 0
centerInfo.lineBreakMode = .byWordWrapping
annotationView?.detailCalloutAccessoryView = centerInfo
}
indexValue = indexValue + 1
}
How about setting address to annotation.title in viewDidLoad, and get annotation.title in viewFor method.
class AllAddressMapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
var allAddress: [Address] = []
var studioRef: DatabaseReference!
override func viewDidLoad() {
super.viewDidLoad()
studioRef = Database.database().reference(withPath: "Photo1")
studioRef.observe(.value, with: { (snapshot) in
for imageSnap in snapshot.children {
let studioObj = Studio(snapshot: imageSnap as! DataSnapshot)
self.allAddress.append(studioObj)
for index in 0..<self.allAddress.count {
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(self.allAddress[index].address, completionHandler: { (placemarks, error) in
guard error == nil else { return }
guard let placemarks = placemarks else { return }
if let placemark = placemarks.first {
let annotation = MKPointAnnotation()
guard let address = placemark.location else { return }
annotation.coordinate = address.coordinate
annotation.title = self.allAddress[index].address
self.mapView.addAnnotation(annotation)
}
geocoder.cancelGeocode()
})
}
}
})
mapView.delegate = self
mapView.mapType = .standard
mapView.isZoomEnabled = true
mapView.isScrollEnabled = true
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard !(annotation is MKUserLocation) else { return nil }
let annotationID = "PinMap"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationID) as? MKPinAnnotationView
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotationID)
annotationView?.canShowCallout = true
} else {
annotationView?.annotation = annotation
}
let leftImageNavigationButton = UIImage(named: "auto")
let tintedColorleftImageNavigationButton = leftImageNavigationButton?.withRenderingMode(.alwaysTemplate)
let leftNavigationButton = UIButton(type: .custom)
leftNavigationButton.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
leftNavigationButton.setImage(tintedColorleftImageNavigationButton, for: UIControlState())
leftNavigationButton.tintColor = #colorLiteral(red: 0, green: 0.4784313725, blue: 1, alpha: 1)
annotationView?.leftCalloutAccessoryView = leftNavigationButton
let rightButtonInfo = UIButton(type: .detailDisclosure)
annotationView?.rightCalloutAccessoryView = rightButtonInfo
let centerInfo = UILabel(frame: CGRect(x: 0, y: 0, width: 150, height: 100))
for index in 0..<allAddress.count {
print("index \(index)")
centerInfo.text = annotation.title
centerInfo.numberOfLines = 0
centerInfo.lineBreakMode = .byWordWrapping
print("address - \(allAddress[index].address)")
}
annotationView?.detailCalloutAccessoryView = centerInfo
annotationView?.calloutOffset = CGPoint(x: -8, y: 0)
return annotationView
}

'NSInvalidArgumentException' when using MapKit

i'm creating a simple view controller with a map and 100-200 MKPointAnnotation using the iOS 11 MKMarkerAnnotationView
This is the viewDidLoad of the controller
override func viewDidLoad() {
super.viewDidLoad()
self.mapView.register(StationAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
self.mapView.register(StationClusterView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultClusterAnnotationViewReuseIdentifier)
locationDelegate.delegate = self
self.mapView.delegate = self
self.mapView.showsUserLocation = true
self.refreshData()
self.establishUserPosition()
}
Then i download the stations from a JSON (network object) and i add all of them to the mapview
func reloadViews(){
if let network = network{
for station in network.stations{
let annotation = StationAnnotation(station: station)
annotations.append(annotation) // I add the annotations to an array to prevent them to be deallocated
mapView.addAnnotation(annotation)
}
}
}
This is my personal annotation
class StationAnnotation : MKPointAnnotation{
var station : Station?
var tintColor : UIColor?{
if self.station?.free_bikes ?? 0 > 0 {
return .green
}else{
return .red
}
}
var glyphImage : UIImage?{
if self.station?.extra.status == "online"{
return UIImage(named: "Bicycle")
}else{
return UIImage(named: "Ban")
}
}
override init() {
super.init()
}
convenience init(station : Station){
self.init()
self.title = station.name
self.coordinate = CLLocationCoordinate2D(latitude: station.latitude, longitude: station.longitude)
self.station = station
if station.extra.status == "online"{
self.subtitle = "Bikes: \(station.free_bikes) - Slots: \(station.empty_slots)"
}else{
self.subtitle = station.extra.status
}
}
}
And my customs Views
class StationAnnotationView : MKMarkerAnnotationView{
override var annotation: MKAnnotation? {
willSet {
if let annotation = newValue as? StationAnnotation{
self.markerTintColor = annotation.tintColor
self.clusteringIdentifier = "station"
self.glyphImage = annotation.glyphImage
}
}
}
}
class StationClusterView: MKAnnotationView {
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var annotation: MKAnnotation? {
willSet {
if let cluster = newValue as? MKClusterAnnotation {
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 40, height: 40))
let count = cluster.memberAnnotations.count
let onlineCount = cluster.memberAnnotations.filter { member -> Bool in
return (member as! StationAnnotation).station?.extra.status == "online"
}.count
image = renderer.image { _ in
// Fill full circle with tricycle color
UIColor(named: "Forbidden")?.setFill()
UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 40, height: 40)).fill()
// Fill pie with unicycle color
UIColor(named: "Available")?.setFill()
let piePath = UIBezierPath()
piePath.addArc(withCenter: CGPoint(x: 20, y: 20), radius: 20,
startAngle: 0, endAngle: (CGFloat.pi * 2.0 * CGFloat(onlineCount)) / CGFloat(count),
clockwise: true)
piePath.addLine(to: CGPoint(x: 20, y: 20))
piePath.close()
piePath.fill()
// Fill inner circle with white color
UIColor.white.setFill()
UIBezierPath(ovalIn: CGRect(x: 8, y: 8, width: 24, height: 24)).fill()
// Finally draw count text vertically and horizontally centered
let attributes = [ NSAttributedStringKey.foregroundColor: UIColor.black,
NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 20)]
let text = "\(count)"
let size = text.size(withAttributes: attributes)
let rect = CGRect(x: 20 - size.width / 2, y: 20 - size.height / 2, width: size.width, height: size.height)
text.draw(in: rect, withAttributes: attributes)
}
}
}
}
}
I don't know why the app while pinching , zooming, or panning, crash with SIGABRT signal and this exception
*** Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '*** -[__NSDictionaryM setObject:forKey:]: key cannot be nil'
I've tried every kind of debug system and the use of exception breakpoint didn't helped... have you any suggestions?
Hllo everybody, i find solutions.
At first - it s..t happens when we use
mapView.register(AnyClass?, forAnnotationViewWithReuseIdentifier: String)
and
mapView.dequeueReusableAnnotationView(withIdentifier: String)
returns nil.
So hot fix:
Add:
ViewController: UIViewController, MKMapViewDelegate
add
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
mapView.register(MarkerPointView.self, forAnnotationViewWithReuseIdentifier: "marker")
mapView.register(ClusterView.self, forAnnotationViewWithReuseIdentifier: "cluster")
}
and finaly:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if let marker = annotation as? MarkerAnnotation{
var view = mapView.dequeueReusableAnnotationView(withIdentifier: "marker") as? MarkerPointView
if view == nil {
//Very IMPORTANT
print("nil for Marker")
view = MarkerPointView(annotation: marker, reuseIdentifier: "marker")
}
return view
}else if let cluster = annotation as? MKClusterAnnotation{
var view = mapView.dequeueReusableAnnotationView(withIdentifier: "cluster") as? ClusterView
if view == nil{
//Very IMPORTANT
print("nil for Cluster")
view = ClusterView(annotation: cluster, reuseIdentifier: "cluster")
}
return view
}
else{
return nil
}
}
hope it's help for somebody, and on next revs apple fix it, because we can use it like they said on wwdc2017 on 36:50 - we CAN'T delete it!!!!!!!!
original post on forums.developer.apple.com

Resources