'NSInvalidArgumentException' when using MapKit - ios

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

Related

iOS Mapbox Updating the map while dragging annotation reverts the annotation to original coordinates

I'm trying to update some map components while dragging an annotations like highlighting a specific MGLPolygon and panning the map if the annotation is already dragged near the edge. I will use the later for this problem.
I tried the code https://docs.mapbox.com/ios/maps/examples/draggable-views/ and added some lines. Here's the exact copy with my changes.
import Mapbox
// Example view controller
class ViewController: UIViewController, MGLMapViewDelegate {
var mapView: MGLMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView = MGLMapView(frame: view.bounds)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.styleURL = MGLStyle.streetsStyleURL
mapView.tintColor = .darkGray
mapView.zoomLevel = 1
mapView.delegate = self
view.addSubview(mapView)
// Specify coordinates for our annotations.
let coordinates = [
CLLocationCoordinate2D(latitude: 0, longitude: -70),
CLLocationCoordinate2D(latitude: 0, longitude: -35),
CLLocationCoordinate2D(latitude: 0, longitude: 0),
CLLocationCoordinate2D(latitude: 0, longitude: 35),
CLLocationCoordinate2D(latitude: 0, longitude: 70)
]
// Fill an array with point annotations and add it to the map.
var pointAnnotations = [MGLPointAnnotation]()
for coordinate in coordinates {
let point = MGLPointAnnotation()
point.coordinate = coordinate
point.title = "To drag this annotation, first tap and hold."
pointAnnotations.append(point)
}
mapView.addAnnotations(pointAnnotations)
}
// MARK: - MGLMapViewDelegate methods
// This delegate method is where you tell the map to load a view for a specific annotation. To load a static MGLAnnotationImage, you would use `-mapView:imageForAnnotation:`.
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
// This example is only concerned with point annotations.
guard annotation is MGLPointAnnotation else {
return nil
}
// For better performance, always try to reuse existing annotations. To use multiple different annotation views, change the reuse identifier for each.
if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "draggablePoint") {
return annotationView
} else {
let dav = DraggableAnnotationView(reuseIdentifier: "draggablePoint", size: 50)
dav.mapView = mapView
return dav
}
}
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return true
}
}
// MGLAnnotationView subclass
class DraggableAnnotationView: MGLAnnotationView {
var mapView: MGLMapView!
var screen: CGRect!
var mapBounds: CGRect!
init(reuseIdentifier: String, size: CGFloat) {
super.init(reuseIdentifier: reuseIdentifier)
// `isDraggable` is a property of MGLAnnotationView, disabled by default.
isDraggable = true
// This property prevents the annotation from changing size when the map is tilted.
scalesWithViewingDistance = false
// Begin setting up the view.
frame = CGRect(x: 0, y: 0, width: size, height: size)
backgroundColor = .darkGray
// Use CALayer’s corner radius to turn this view into a circle.
layer.cornerRadius = size / 2
layer.borderWidth = 1
layer.borderColor = UIColor.white.cgColor
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.1
screen = UIScreen.main.bounds
mapBounds = CGRect(
x: screen.origin.x + 20,
y: screen.origin.y + 20,
width: screen.size.width - 40,
height: screen.size.height - 40)
}
// These two initializers are forced upon us by Swift.
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// Custom handler for changes in the annotation’s drag state.
override func setDragState(_ dragState: MGLAnnotationViewDragState, animated: Bool) {
super.setDragState(dragState, animated: animated)
switch dragState {
case .starting:
print("Starting", terminator: "")
startDragging()
case .dragging:
let pointCoordinate = self.mapView.convert(center, toCoordinateFrom: nil)
if mapBounds.contains(center) {
DispatchQueue.main.async {
self.mapView.setCenter(pointCoordinate, animated: true)
}
}
print(".", terminator: "")
case .ending, .canceling:
print("Ending")
endDragging()
case .none:
break
#unknown default:
fatalError("Unknown drag state")
}
}
// When the user interacts with an annotation, animate opacity and scale changes.
func startDragging() {
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: [], animations: {
self.layer.opacity = 0.8
self.transform = CGAffineTransform.identity.scaledBy(x: 1.5, y: 1.5)
}, completion: nil)
// Initialize haptic feedback generator and give the user a light thud.
if #available(iOS 10.0, *) {
let hapticFeedback = UIImpactFeedbackGenerator(style: .light)
hapticFeedback.impactOccurred()
}
}
func endDragging() {
transform = CGAffineTransform.identity.scaledBy(x: 1.5, y: 1.5)
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: [], animations: {
self.layer.opacity = 1
self.transform = CGAffineTransform.identity.scaledBy(x: 1, y: 1)
}, completion: nil)
// Give the user more haptic feedback when they drop the annotation.
if #available(iOS 10.0, *) {
let hapticFeedback = UIImpactFeedbackGenerator(style: .light)
hapticFeedback.impactOccurred()
}
}
}
Everytime the self.mapView.setCenter(pointCoordinate, animated: true) gets called, the annotations goes back and forth to its original position.
Here is the code explaining the Adonis's solution. Essentially add a pan gesture to a custom annotation's view and update the coords as and when the annotation is panned.
class CustomDraggableAnnotaionView: MGLAnnotationView {
required init(
reuseIdentifier: String?,
image: UIImage?,
annotation: CustomMapGLAnnotaion
) {
super.init(reuseIdentifier: reuseIdentifier)
setupDraggableAnnotations()
self.layer.zPosition = 10
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Draggable annotation handlers
private func setupDraggableAnnotations() {
addDraggableAnnotationGestureRecognizers()
}
private func addDraggableAnnotationGestureRecognizers() {
let panGesture = UIPanGestureRecognizer(
target: self,
action: #selector(self.draggedView(_:))
)
let tapGesture = UITapGestureRecognizer(
target: self,
action: #selector(self.tappedAnnotation(_:))
)
self.isUserInteractionEnabled = true
self.addGestureRecognizer(panGesture)
self.addGestureRecognizer(tapGesture)
for recognizer in self.gestureRecognizers! where recognizer is UITapGestureRecognizer {
tapGesture.require(toFail: recognizer)
}
for recognizer in self.gestureRecognizers! where recognizer is UIPanGestureRecognizer {
panGesture.require(toFail: recognizer)
}
}
#objc func draggedView(_ sender: UIPanGestureRecognizer) {
annotationObject?.draggable!.isCurrentlyDragging = true
let point = sender.location(in: MapManager.shared.mapView)
let coordinates = MapManager.shared.mapView.convert(
point,
toCoordinateFrom: MapManager.shared.mapView
)
annotationObject?.coordinate = coordinates
if sender.state == .ended {
// endDragging()
} else if sender.state == .began {
// startDragging()
annotationObject?.draggable!.handler.didStartDragging()
} else {
//
}
}
}

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/

Adding a callout/speech bubble to the User annotation of a MapView

Right now my app display a customUserAnnotationView with a custom image where the user annotation is (you can see this in ViewController.swift). I have also created a custom UIView that I want to use as an annotation just above the user annotation (the code and image for it are under SpeechBubble.swift).
I want to combine these two objects so that I can show the CustomUserAnnotationView with the Custom UIView(SpeechBubble.swift) placed in an annotation above.
My attempts at making a frankenstein program from multiple mapbox tutorials have not worked out for me. I only want to place the custom annotation class I created above the image, and maybe add a small triangle to make it look like a speech bubble.
ViewController.swift
import Mapbox
class ViewController: UIViewController, MGLMapViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let mapView = MGLMapView(frame: view.bounds)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.delegate = self
// Enable heading tracking mode so that the arrow will appear.
mapView.userTrackingMode = .followWithHeading
// Enable the permanent heading indicator, which will appear when the tracking mode is not `.followWithHeading`.
mapView.showsUserHeadingIndicator = true
view.addSubview(mapView)
let idea = UITextView(frame: CGRect(x: 0, y: 0, width: 100, height: 40))
idea.text = "Hello There"
idea.textAlignment = NSTextAlignment.center
let sb = SpeechBubble(coord: mapView.targetCoordinate, idea: idea)
mapView.addSubview(sb)
}
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return true
}
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
// Substitute our custom view for the user location annotation. This custom view is defined below.
if annotation is MGLUserLocation && mapView.userLocation != nil {
return Avatar()
}
return nil
}
// Optional: tap the user location annotation to toggle heading tracking mode.
func mapView(_ mapView: MGLMapView, didSelect annotation: MGLAnnotation) {
if mapView.userTrackingMode != .followWithHeading {
mapView.userTrackingMode = .followWithHeading
} else {
mapView.resetNorth()
}
// We're borrowing this method as a gesture recognizer, so reset selection state.
mapView.deselectAnnotation(annotation, animated: false)
}
}
SpeechBubble.swift
import UIKit
import Mapbox
class SpeechBubble: UIView, MGLMapViewDelegate{
//var sbView: UIView
init(coord: CLLocationCoordinate2D, idea: UITextView) {
let width = CGFloat(180)
let height = UITextField.layoutFittingExpandedSize.height + 32
super.init(frame: CGRect(x: CGFloat(coord.latitude), y: CGFloat(coord.longitude), width: width, height: height))
self.addSubview(idea)
self.addSubview(buttonsView());
self.addSubview(upvoteView());
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func upvoteView() -> UIView {
let uView = UIView()
let vCnt = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
vCnt.center = CGPoint(x: 10.5, y: 32)
vCnt.textAlignment = .center
vCnt.text = "0"
let uButton = UIButton(type: .custom)
uButton.frame = CGRect(x: vCnt.frame.size.width + 5, y: 0, width: 32, height: 32);
let uImage = UIImage (named: "Upvote")
uButton.setImage(uImage, for: .normal)
uView.frame.size.width = vCnt.frame.size.width + uButton.frame.size.width + 5
uView.frame.size.height = max(vCnt.frame.size.height, uButton.frame.size.height)
uView.frame = CGRect(
x: 0,
y: self.frame.size.height - uView.frame.size.height,
width: uView.frame.size.width,
height: uView.frame.size.height );
uView.addSubview(vCnt)
uView.addSubview(uButton)
return uView
}
func buttonsView() -> UIView {
let bView = UIView()
let jButton = UIButton(type: .custom)
rButton.frame = CGRect(x: 0, y: 0, width: 35, height: 32);
let rImage = UIImage (named: "Rocket")
rButton.setImage(rImage, for: .normal)
let pButton = UIButton(type: .custom)
pButton.frame = CGRect(x: jButton.frame.size.width + 5, y: 0, width: 31, height: 36);
let pImage = UIImage (named: "Profile")
pButton.setImage(pImage, for: .normal)
bView.frame.size.width = rButton.frame.size.width + pButton.frame.size.width + 5
bView.frame.size.height = max(rButton.frame.size.height, pButton.frame.size.height)
bView.frame = CGRect(
x: self.frame.size.width - bView.frame.size.width,
y: self.frame.size.height - bView.frame.size.height,
width: bView.frame.size.width,
height: bView.frame.size.height );
bView.addSubview(rButton)
bView.addSubview(pButton)
return bView
}
}
Avatar.swift
import Mapbox
class Avatar: MGLUserLocationAnnotationView {
let size: CGFloat = 48
var arrow: CALayer!
//var arrow: CAShapeLayer!
// -update is a method inherited from MGLUserLocationAnnotationView. It updates the appearance of the user location annotation when needed. This can be called many times a second, so be careful to keep it lightweight.
override func update() {
if frame.isNull {
frame = CGRect(x: 0, y: 0, width: size, height: size)
return setNeedsLayout()
}
// Check whether we have the user’s location yet.
if CLLocationCoordinate2DIsValid(userLocation!.coordinate) {
setupLayers()
updateHeading()
}
}
private func updateHeading() {
// Show the heading arrow, if the heading of the user is available.
if let heading = userLocation!.heading?.trueHeading {
arrow.isHidden = false
// Get the difference between the map’s current direction and the user’s heading, then convert it from degrees to radians.
let rotation: CGFloat = -MGLRadiansFromDegrees(mapView!.direction - heading)
// If the difference would be perceptible, rotate the arrow.
if abs(rotation) > 0.01 {
// Disable implicit animations of this rotation, which reduces lag between changes.
CATransaction.begin()
CATransaction.setDisableActions(true)
arrow.setAffineTransform(CGAffineTransform.identity.rotated(by: rotation))
CATransaction.commit()
}
} else {
arrow.isHidden = true
}
}
private func setupLayers() {
// This dot forms the base of the annotation.
if arrow == nil {
arrow = CALayer()
let myImage = UIImage(named: "will_smith")?.cgImage
arrow.bounds = CGRect(x: 0, y: 0, width: size, height: size)
arrow.contents = myImage
layer.addSublayer(arrow)
}
}
// Calculate the vector path for an arrow, for use in a shape layer.
private func arrowPath() -> CGPath {
let max: CGFloat = size / 2
let pad: CGFloat = 3
let top = CGPoint(x: max * 0.5, y: 0)
let left = CGPoint(x: 0 + pad, y: max - pad)
let right = CGPoint(x: max - pad, y: max - pad)
let center = CGPoint(x: max * 0.5, y: max * 0.6)
let bezierPath = UIBezierPath()
bezierPath.move(to: top)
bezierPath.addLine(to: left)
bezierPath.addLine(to: center)
bezierPath.addLine(to: right)
bezierPath.addLine(to: top)
bezierPath.close()
return bezierPath.cgPath
}
}
--------------------------------------------------------------------------------------------------------
UPDATE
I tried to create a Frankenstein program of the Answer and my code and am receiving the following the error Property 'self.representedObject' not initialized at super.init call within SpeechBubble.swift. I also move all my old code from speechBubble.swift into insideSpeechBubble.swift
Updated SpeechBubble.swift
import UIKit
import Mapbox
class SpeechBubble: UIView, MGLCalloutView {
// Your IBOutlets //
var representedObject: MGLAnnotation
var annotationPoint: CGPoint
// Required views but unused for this implementation.
lazy var leftAccessoryView = UIView()
lazy var rightAccessoryView = UIView()
var contentView: MGLMapView
weak var delegate: MGLCalloutViewDelegate?
// MARK: - init methods
required init(annotation: MGLAnnotation, frame: CGRect, annotationPoint: CGPoint) {
let idea = UITextView(frame: CGRect(x: 0, y: 0, width: 100, height: 40))
idea.text = "Hello There"
idea.textAlignment = NSTextAlignment.center
self.representedObject = annotation
self.annotationPoint = annotationPoint
contentView = InsideSpeechBubble(coord: annotationPoint, idea: idea )
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
Bundle.main.loadNibNamed("SpeechBubble", owner: self, options: nil)
addSubview(contentView as UIView)
contentView.frame = self.bounds
// Do your initialisation //
}
// MARK: - MGLCalloutView methods
func presentCallout(from rect: CGRect, in view: UIView, constrainedTo constrainedRect: CGRect, animated: Bool) {
// Present the custom callout slightly above the annotation's view. Initially invisble.
self.center = annotationPoint.applying(CGAffineTransform(translationX: 0, y: -self.frame.height - 20.0))
// I have logic here for setting the correct image and button states //
}
func dismissCallout(animated: Bool) {
removeFromSuperview()
}
}
Updated ViewController.swift
import Mapbox
class ViewController: UIViewController, MGLMapViewDelegate {
//let point = MGLPointAnnotation()
override func viewDidLoad() {
super.viewDidLoad()
let mapView = MGLMapView(frame: view.bounds)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.delegate = self
// Enable heading tracking mode so that the arrow will appear.
mapView.userTrackingMode = .followWithHeading
// Enable the permanent heading indicator, which will appear when the tracking mode is not `.followWithHeading`.
mapView.showsUserHeadingIndicator = true
view.addSubview(mapView)
let HighDea = UITextView(frame: CGRect(x: 0, y: 0, width: 100, height: 40))
HighDea.text = "Hello There"
HighDea.textAlignment = NSTextAlignment.center
//let sb = SpeechBubble()
//mapView.addSubview(sb)
}
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return true
}
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
// Substitute our custom view for the user location annotation. This custom view is defined below.
if annotation is MGLUserLocation && mapView.userLocation != nil {
return Avatar()
}
return nil
}
func mapView(_ mapView: MGLMapView, calloutViewFor annotation: MGLAnnotation) -> MGLCalloutView? {
// Do your annotation-specific preparation here //
// I get the correct size from my xib file.
let viewFrame = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 261.0, height: 168.0))
// Get the annotation's location in the view's coordinate system.
let annotationPoint = mapView.convert(annotation.coordinate, toPointTo: nil)
let customCalloutView = SpeechBubble(annotation: annotation, frame: viewFrame, annotationPoint: annotationPoint)
return customCalloutView
}
// func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
// This example is only concerned with point annotations.
// guard annotation is MGLPointAnnotation else {
// return nil
// }
// Use the point annotation’s longitude value (as a string) as the reuse identifier for its view.
// let reuseIdentifier = "\(annotation.coordinate.longitude)"
// For better performance, always try to reuse existing annotations.
// var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier)
// If there’s no reusable annotation view available, initialize a new one.
// if annotationView == nil {
// annotationView = CustomAnnotationView(reuseIdentifier: reuseIdentifier)
// annotationView!.bounds = CGRect(x: 0, y: 0, width: 40, height: 40)
// Set the annotation view’s background color to a value determined by its longitude.
// let hue = CGFloat(annotation.coordinate.longitude) / 100
// annotationView!.backgroundColor = UIColor(hue: hue, saturation: 0.5, brightness: 1, alpha: 1)
// }
// return annotationView
// }
// Optional: tap the user location annotation to toggle heading tracking mode.
func mapView(_ mapView: MGLMapView, didSelect annotation: MGLAnnotation) {
if mapView.userTrackingMode != .followWithHeading {
mapView.userTrackingMode = .followWithHeading
} else {
mapView.resetNorth()
}
// We're borrowing this method as a gesture recognizer, so reset selection state.
mapView.deselectAnnotation(annotation, animated: false)
}
}
When I implemented a custom callout for my Mapbox annotations I used a xib file to design the actual callout. I find that it gives me a lot more instant feedback than than trying to conjure the UI from code (but obviously do whatever your preference is).
Which gives me something like the following.
Using a UIImage for the background allows me to achieve any shape I choose. Here I use transparency around the white to give me the circular elements and the bottom triangle you mention in your question.
The Swift file for this UIView (your SpeechBubble) needs to conform to the MGLCalloutView protocol not MGLMapViewDelegate as you have it currently. Your ViewController is the MGLMapViewDelegate, not your custom callout. Pair the xib file and the Swift file in the usual way in Identity Inspector in IB. So would be something like this:
import UIKit
import Mapbox
class SpeechBubble: UIView, MGLCalloutView {
// Your IBOutlets //
#IBOutlet var contentView: UIView! // The custom callout's view.
var representedObject: MGLAnnotation
var annotationPoint: CGPoint
// Required views but unused for this implementation.
lazy var leftAccessoryView = UIView()
lazy var rightAccessoryView = UIView()
weak var delegate: MGLCalloutViewDelegate?
// MARK: - init methods
required init(annotation: YourAnnotation, frame: CGRect, annotationPoint: CGPoint) {
self.representedObject = annotation
self.annotationPoint = annotationPoint
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
Bundle.main.loadNibNamed("SpeechBubble", owner: self, options: nil)
addSubview(contentView)
contentView.frame = self.bounds
// Do your initialisation //
}
// MARK: - MGLCalloutView methods
func presentCallout(from rect: CGRect, in view: UIView, constrainedTo constrainedRect: CGRect, animated: Bool) {
// Present the custom callout slightly above the annotation's view. Initially invisble.
self.center = annotationPoint.applying(CGAffineTransform(translationX: 0, y: -self.frame.height - 20.0))
// I have logic here for setting the correct image and button states //
}
func dismissCallout(animated: Bool) {
removeFromSuperview()
}
Then you just seem to be missing the MGLMapViewDelegate method to actually return your SpeechBubble view when requested. It should be in your ViewController file.
func mapView(_ mapView: MGLMapView, calloutViewFor annotation: MGLAnnotation) -> MGLCalloutView? {
// Do your annotation-specific preparation here //
// I get the correct size from my xib file.
let viewFrame = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 261.0, height: 168.0))
// Get the annotation's location in the view's coordinate system.
let annotationPoint = mapView.convert(annotation.coordinate, toPointTo: nil)
let customCalloutView = SpeechBubble(annotation: YourAnnotation, frame: viewFrame, annotationPoint: annotationPoint)
return customCalloutView
}
Hopefully this will get you closer to achieving what you're trying to do. BTW this version of your question is miles ahead of the first one.
EDIT +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
It's going to be almost impossible to work this through without sight of your project so I have put together a bare bones implementation. It is based on the Mapbox example here: Mapbox Custom Callout which for some reason doesn't show how to actually supply the callout view. I've also extended it to allow for a custom annotation image. If you can get this working you should be able to move the relevant parts into your own project.
I strongly recommend that if you try to implement the stuff below that you do it in a fresh project.
The view controller.
import Mapbox
class ViewController: UIViewController, MGLMapViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let mapView = MGLMapView(frame: view.bounds, styleURL: MGLStyle.lightStyleURL)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.tintColor = .darkGray
view.addSubview(mapView)
// Set the map view‘s delegate property.
mapView.delegate = self
// Initialize and add the marker annotation.
let coordinate = CLLocationCoordinate2D(latitude: 0, longitude: 0)
let marker = MyAnnotation(coordinate: coordinate, title: "Bingo", subtitle: "Bongo")
// Add marker to the map.
mapView.addAnnotation(marker)
}
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return true
}
func mapView(_ mapView: MGLMapView, calloutViewFor annotation: MGLAnnotation) -> MGLCalloutView? {
// Instantiate and return our custom callout view.
let annotationPoint = mapView.convert(annotation.coordinate, toPointTo: nil)
let viewFrame = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 250.0, height: 178.0))
return CustomCalloutView(representedObject: annotation, frame: viewFrame, annotationPoint: annotationPoint)
}
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "myAnnotationView") {
return annotationView
} else {
let annotationView = MyAnnotationView(reuseIdentifier: "myAnnotationView", size: CGSize(width: 45, height: 45), annotation: annotation)
return annotationView
}
}
func mapView(_ mapView: MGLMapView, tapOnCalloutFor annotation: MGLAnnotation) {
// Optionally handle taps on the callout.
print("Tapped the callout for: \(annotation)")
// Hide the callout.
mapView.deselectAnnotation(annotation, animated: true)
}
}
CustomCalloutView.swift
import UIKit
import Mapbox
class CustomCalloutView: UIView, MGLCalloutView {
#IBOutlet var contentView: UIView!
weak var delegate: MGLCalloutViewDelegate?
var representedObject: MGLAnnotation
var annotationPoint: CGPoint
// Required views but unused for this implementation.
lazy var leftAccessoryView = UIView()
lazy var rightAccessoryView = UIView()
required init(representedObject: MGLAnnotation, frame: CGRect, annotationPoint: CGPoint) {
self.representedObject = representedObject
self.annotationPoint = annotationPoint
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
let coordinate = CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0)
self.representedObject = MyAnnotation(coordinate: coordinate, title: "", subtitle: "")
self.annotationPoint = CGPoint(x: 50.0, y: 50.0)
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
Bundle.main.loadNibNamed("CustomCalloutView", owner: self, options: nil)
addSubview(contentView)
}
func presentCallout(from rect: CGRect, in view: UIView, constrainedTo constrainedRect: CGRect, animated: Bool) {
// Present the custom callout slightly above the annotation's view. Initially invisble.
self.center = annotationPoint.applying(CGAffineTransform(translationX: 0.0, y: -120.0))
view.addSubview(self)
}
func dismissCallout(animated: Bool) {
removeFromSuperview()
}
}
This is associated/identified with a xib file. It just contains a simple image shape for now. I had to (re)introduce the contentView IBOutlet as I was having trouble loading things from the Bundle and adding it to self in commonInit() made everything happy.
The custom annotation class.
import UIKit
import Mapbox
// MGLAnnotation protocol reimplementation
class MyAnnotation: NSObject, MGLAnnotation {
// As a reimplementation of the MGLAnnotation protocol, we have to add mutable coordinate and (sub)title properties ourselves.
var coordinate: CLLocationCoordinate2D
var title: String?
var subtitle: String?
// Custom properties that we will use to customize the annotation.
var image: UIImage?
var reuseIdentifier: String?
init(coordinate: CLLocationCoordinate2D, title: String?, subtitle: String?) {
self.coordinate = coordinate
self.title = title
self.subtitle = subtitle
self.reuseIdentifier = "myAnnotation"
}
}
The MGLAnnotationView subclass.
import UIKit
import Mapbox
class MyAnnotationView: MGLAnnotationView {
init(reuseIdentifier: String, size: CGSize, annotation: MGLAnnotation) {
super.init(reuseIdentifier: reuseIdentifier)
// This property prevents the annotation from changing size when the map is tilted.
scalesWithViewingDistance = false
// Begin setting up the view.
frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
let imageView = UIImageView(frame: frame)
var image = UIImage()
if annotation is MyAnnotation {
image = UIImage(named: "frog")!
}
imageView.image = image
addSubview(imageView)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Naturally there is a lot of hard coded numbers and the requirement for an image called frog but you can change all of that and improve it as you wish. The CustomCalloutView.swift and CustomCalloutView.xib need to be linked in the usual way in the identity inspector, etc.

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

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

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:

Resources