How to change default background color of callout bubble with detailCalloutAccessoryView - swift

In my app I have the following sutuation.
I've implemented a custom callout bubble with custom detailCalloutAccessoryView with two labels inside.
I know how to change the color of detailCalloutAccessoryView with this line.
view.detailCalloutAccessoryView?.backgroundColor = UIColor.red
But I can't figure out how to change background color of the main bubble (it is transparent grey/white now). With view.detailCalloutAccessoryView?.backgroundColor = UIColor.red line my calloutbubble looks like this:
But I want my custom bubble to look like this:
Here is my viewFor annotation method:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
let identifier = "pin"
var view : MKAnnotationView
if let dequedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) {
dequedView.annotation = annotation
view = dequedView
} else {
view = MKAnnotationView(annotation: annotation, reuseIdentifier: identifier)
view.canShowCallout = true
}
let pinImage = UIImage.init(named: "customPin")
DispatchQueue.main.async(execute: {
view.detailCalloutAccessoryView?.backgroundColor = UIColor.red
})
view.image = pinImage
configureDetailView(annotationView: view)
return view
}
I'm working in Xcode 8 w/ Swift 3.
It would be also interesting to know how to change font type and default black color of the title from black to another color.
In detail view i can easily change color of my custom labels in xib file but don't know how to access default title properties.

func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
//design your custom view and set it to detailCalloutAccessoryView
view.detailCalloutAccessoryView = yourDetailView
detail.superview?.superview?.backgroundColor = yourColor
// This view is type of MKSmallCalloutView
}

UIViewCallout is a private class. If you want custom callout view:
disable standart callout view.canShowCallout = false
implement MKMapViewDelegate methods with your custom UIView for callout:
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
let redCalloutView = RedCalloutView(view.annotation)
view.addSubview(redCalloutView)
}
func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
view.subviews.forEach {
if $0 is RedCalloutView {
$0.removeFromSuperview()
}
}
}

I had created the code for your requirement please find the below url for download the code and review it.
Link : https://www.dropbox.com/s/o2howwqceq8rsgu/MapInformation.zip?dl=0
Environment : Xcode 8 and Swift3
Highlight the code which I had done it.
I had taken the approach to display the Popup(UIPresentationController) instead of callout. For more information please find the below code.
A) I had used the UIButton to display as annotation on the MapView and display the popup when user click on it.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
let identifier = "pin"
var annotationView = self.mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as! AnnotationView?
if annotationView == nil {
annotationView = AnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView?.canShowCallout = false
}
else {
annotationView?.annotation = annotation
}
//Take the UIButton and implement the touchupinside action for showing the popup.
let pinImage = UIImage.init(named: "customPin")
annotationView?.frame = CGRect(x: 0, y: 0, width: (pinImage?.size.width)!, height: (pinImage?.size.width)!)
annotationView?.mapPin = UIButton(frame: (annotationView?.frame)!);
annotationView?.mapPin.addTarget(self, action: #selector(ViewController.showPopup(sender:)), for: .touchUpInside)
annotationView?.addSubview((annotationView?.mapPin)!)
annotationView?.mapPin.setImage(pinImage, for: .normal)
return annotationView
}
B) Display the popup when user click on the annotation.
func showPopup(sender: UIButton!) {
let popupVC = self.storyboard?.instantiateViewController(withIdentifier: "Popup") as? Popup
popupVC?.preferredContentSize = CGSize(width: 250, height: 150)
popupVC?.modalPresentationStyle = UIModalPresentationStyle.popover
let rect = sender.superview?.convert(sender.frame, to: self.view)
popupVC?.popoverPresentationController?.delegate = self;
popupVC?.popoverPresentationController?.sourceView = self.view
popupVC?.popoverPresentationController?.sourceRect = rect!
popupVC?.popoverPresentationController?.backgroundColor = UIColor.red
self.present(popupVC!, animated: true, completion: nil)
}
Note
If you want to change the popup color from red to other different
color then you can do only single line of coding by changing the color name.
popupVC?.popoverPresentationController?.backgroundColor = UIColor.red
Please look into the below screenshot.

func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
for v in view.subviews{
if v.subviews.count > 0{
v.subviews[0].backgroundColor = UIColor.red
}
}
}
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
for v in view.subviews{
if v.subviews.count > 0 {
let colloutView = v.subviews[0]
colloutView.backgroundColor = UIColor(red: 0.0/255.0, green: 0.0/255.0, blue: 0.0/255.0, alpha: 0.8)
if colloutView.subviews.count > 0 {
if colloutView.subviews[0].subviews.count > 0{
colloutView.subviews[0].subviews.forEach { (view) in
if let label = view as? UILabel{
label.textColor = UIColor.white
}
}
}
}
}
}
}

Related

Custom MKAnnotationView want to be tapped again

I'm having problems with my custom MKAnnotationView.
I download the coordinate where to place it from my REST api and I place the pin on map with this code:
private func refreshMapView(andCenterMap: Bool){
DispatchQueue.main.async { [weak self] in
guard let self = self else {
return
}
self.mapView.addAnnotations(self.spotsArray)
if andCenterMap {
self.centerMap(at: self.mapView.userLocation.coordinate)
}
}
}
Ones the pin is placed I zoom automatically the map.
Here the code for the custom annotation creation:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// Do not touch the annotation of the user location
if annotation.isKind(of: MKUserLocation.self){
return nil
}
let annoIdentifier = "SPOT"
var annotationView: MKAnnotationView?
if let dequeued = mapView.dequeueReusableAnnotationView(withIdentifier: annoIdentifier) {
annotationView = dequeued
}else{
let av = MKAnnotationView(annotation: annotation, reuseIdentifier: annoIdentifier)
annotationView = av
}
// Changing the image of the pin
annotationView!.annotation = annotation
if let image = UIImage(named: "map_pin") {
annotationView!.image = image
let deltaY = image.size.height/2
annotationView!.centerOffset = CGPoint(x: 0.0, y: -deltaY)
}else{
annotationView!.image = nil
}
return annotationView
}
As you can notice, my custom pin is using this image (#1x, #2x, #3x)
Ones tapped I want to show the "detail view" and I use the annotation as a sender in order to have all the information I need on the next view.
Here the code:
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
guard let ann = view.annotation else {
return
}
if ann.isKind(of: MKUserLocation.self){
return
}
self.performSegue(withIdentifier: "showDetailSegue", sender: ann)
}
So I present all the data I need to and I can go back to the "map view".
The problem is:
In the map view, if I want to select the same annotation again, this is not tappable anymore.
I can see it (in the right place) but I can select it again only if I zoom a little bit and I tap "around" the annotation.
Any suggestione how to solve this issue?
Thanks in advance!
Add this end of didSelect
mapView.deselectAnnotation(ann,animated:false)

Swift distinguishing between callout buttons

Hi I'm mostly new to coding and this is my first project that I've worked on. I'm making an app that displays pools which are hiring around your address and wanted to make it so that pressing on the callouts will bring you to a unique ViewController depending on which one you pressed.
From what I've seen so far online it looks like it needs to be done in an extension of the ViewController but was stuck on how to differentiate between callouts (as of right now I have it so that pressing it will show the directions, but I'd rather show more information that just that, which to my limited knowledge would mean displaying another view controller unless anyone else has any other ideas)
extension MapViewController : MKMapViewDelegate
{
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
{
if let annotation = annotation as? Pools {
let identifier = "pin"
var view: MKPinAnnotationView
if let dequeuedView =
mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKPinAnnotationView{
dequeuedView.annotation = annotation
view = dequeuedView
}
else {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
view.canShowCallout = true
view.calloutOffset = CGPoint(x: -5, y: 5)
view.rightCalloutAccessoryView = UIButton(type: .detailDisclosure) as UIView
}
return view
}
return nil
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let location = view.annotation as! Pools
let launchOptions = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving]
location.mapItem().openInMaps(launchOptions: launchOptions)
}
}
Any help would be greatly appreciated!

swift, mapView, annotation view wont show upon tap

I implemented map view and created simple data model for annotations.
The pins are shown on the map, but I am still unable to get the details by tapping any pin.
My Code:
import UIKit
import MapKit
class MapViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var topView: MapDataView!
var dummyModel: DummyModel?
override func viewDidLoad() {
super.viewDidLoad()
dummyModel = DummyModel()
mapView.delegate = self
mapView.showsUserLocation = true
let region = MKCoordinateRegionMakeWithDistance(mapView.userLocation.coordinate, 1000, 1000)
mapView.setRegion(region, animated: true)
let range = 0..<Int((dummyModel?.objectsArray.count)!)
Array is valid and has no nil:
for i in range {
mapView.addAnnotation((dummyModel?.objectsArray[i])!)
}
The way i add annotation to the map:
mapView.selectAnnotation(mapView.annotations[0], animated: true)
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
print("viewForAnnotation")
let identifier = "PubMapObject"
if annotation is PubMapObject {
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView!.canShowCallout = true
let btn = UIButton(type: .detailDisclosure)
annotationView!.rightCalloutAccessoryView = btn
} else {
annotationView!.annotation = annotation
}
return annotationView
}
return nil
}
This method never called and i dont know why. i guess the problem is here, but i am unable to find it ):
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
print("annotationView")
let pub = view.annotation as! PubMapObject
}
}
Try to add set frame of annotation view before returning annotation view
//my annotation class and provide width and height to annotation view before returning the annotation view and give frame to
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if (annotation.isKind(of: MKUserLocation.self)) {
return nil
}
let reuseId = "PubMapObject"
if (annotation.isKind(of: PubMapObject.self)) {
let anView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
anView.isEnabled = true
anView.isUserInteractionEnabled = true
let btn = UIButton(type: .detailDisclosure)
btn.frame = CGRect(x: 0, y: 0, width: 50, height: 70)
anView!.rightCalloutAccessoryView = btn
anView.frame = CGRect(x: 0, y: 0, width: 50, height: 70)
return anView
}
return nil
}
Try using didSelect function instead of calloutAccessoryControlTapped.
func mapView(_ mapView: MKMapView, didSelect annotationView: MKAnnotationView) { ...

How to set MGLAnnotationView as sourceView for popover

What I want to do is present a popover with the annotation as its source view/anchor
By using delegate function didSelect annotationView I should be able to achieve this, but it doesn't seem to run at all.
For now I'm just presenting my popover via didSelect annotation and have set the sourceView to nav bar, just for showing it somewhere...
FYI:
I have implemented the Mapbox SDK into the project.
I have no problems performing the same task using MapKit.
Does anyone have any idea on what I can do to achieve this?
code snippets below:
import UIKit
import Mapbox
class ViewController: UIViewController, MGLMapViewDelegate, UIPopoverPresentationControllerDelegate {
#IBOutlet var theMap: MGLMapView!
override func viewDidLoad() {
super.viewDidLoad()
theMap.delegate = self
let point = MGLPointAnnotation()
point.coordinate = CLLocationCoordinate2D(latitude: 55.6, longitude: 13.0)
point.title = "Some place"
point.subtitle = "Malmö, Sweden"
theMap.addAnnotation(point)
}
func mapView(_ mapView: MGLMapView, didSelect annotationView: MGLAnnotationView) {
print("annotation view: ", annotationView)
// this method doesn't seem to get called at all...
// but ideally this is the place to present the popover.
}
func mapView(_ mapView: MGLMapView, didSelect annotation: MGLAnnotation) {
print("annotation: ", annotation)
// present the popover
presentPopover()
}
func presentPopover(){
let popover = storyboard?.instantiateViewController(withIdentifier: "MyCalloutVC") as! MyCallout
popover.modalPresentationStyle = UIModalPresentationStyle.popover
popover.popoverPresentationController?.backgroundColor = UIColor.white
popover.popoverPresentationController?.delegate = self
// I would like to set the source anchor to the selected annotation view.
popover.popoverPresentationController?.sourceView = UINavigationBar() // set to nav bar for now...
popover.popoverPresentationController?.permittedArrowDirections = .any
// popover size set in MyCallout
self.present(popover, animated: true)
}
}
The function does not get called because there is no annotation view, which can be selected.
It means that you simply add an point annotation and not an annotation view. Therefore you will have to do this:
When you add the annotation the func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {} gets called, where you can define an annotation view an return it. Then the did selected function will be called. Have a look at the example code : https://www.mapbox.com/ios-sdk/examples/annotation-views/
import Mapbox
// Example view controller
class ViewController: UIViewController, MGLMapViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let mapView = MGLMapView(frame: view.bounds)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.styleURL = MGLStyle.darkStyleURL(withVersion: 9)
mapView.tintColor = .lightGray
mapView.centerCoordinate = CLLocationCoordinate2D(latitude: 0, longitude: 66)
mapView.zoomLevel = 2
mapView.delegate = self
view.addSubview(mapView)
// Specify coordinates for our annotations.
let coordinates = [
CLLocationCoordinate2D(latitude: 0, longitude: 33),
CLLocationCoordinate2D(latitude: 0, longitude: 66),
CLLocationCoordinate2D(latitude: 0, longitude: 99),
]
// 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 = "\(coordinate.latitude), \(coordinate.longitude)"
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
}
// 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!.frame = 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
}
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return true
}
}
//
// MGLAnnotationView subclass
class CustomAnnotationView: MGLAnnotationView {
override func layoutSubviews() {
super.layoutSubviews()
// Force the annotation view to maintain a constant size when the map is tilted.
scalesWithViewingDistance = false
// Use CALayer’s corner radius to turn this view into a circle.
layer.cornerRadius = frame.width / 2
layer.borderWidth = 2
layer.borderColor = UIColor.white.cgColor
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Animate the border width in/out, creating an iris effect.
let animation = CABasicAnimation(keyPath: "borderWidth")
animation.duration = 0.1
layer.borderWidth = selected ? frame.width / 4 : 2
layer.add(animation, forKey: "borderWidth")
}
}

Setting custom image - MKPinAnnotationView - Swift 3

I'm using MapKit and I'm currently trying to set a custom pin annotation image on my pins. However, I'm having a hard time figuring out why it doesn't work. All of the other code is working perfectly, and when I try printing the frame of the image after setting it, it does show the correct dimensions for my "pinImage", so it seems to be able to set the image on the property.
Also, the delegate is set correctly, this is verified by setting a custom color on the default pin.
I've also tried using "pinImage.png", without luck. And since MKPinAnnotationView is a subclass of MKAnnotationView, I see no problem with why that should be the issue, and to be sure, I tried to use the MKAnnotationView also, without luck.
Here's my code:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if let annotation = annotation as? Pin {
let identifier = LocalConstants.pinIdentifier
var view: MKPinAnnotationView
if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKPinAnnotationView {
dequeuedView.annotation = annotation
view = dequeuedView
} else {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
view.canShowCallout = true
view.calloutOffset = CGPoint(x: -5, y: 5)
let detailButton = UIButton(type: .detailDisclosure) as UIView
view.rightCalloutAccessoryView = detailButton
//view.pinTintColor = Util.Colors.pluppPurple
}
view.image = UIImage(named: "pinImage")
return view
}
Thanks in advance!
The answer was indeed to use MKAnnotationView and not MKPinAnnotationView. I do not know what I messed up when trying it out yesterday. Final working copy below, for future reference:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if let annotation = annotation as? Pin {
let identifier = LocalConstants.pinIdentifier
var view: MKAnnotationView
if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) {
dequeuedView.annotation = annotation
view = dequeuedView
} else {
view = MKAnnotationView(annotation: annotation, reuseIdentifier: identifier)
view.canShowCallout = true
view.calloutOffset = CGPoint(x: -5, y: 5)
view.tintColor = Util.Colors.pluppGrey
let detailButton = UIButton(type: .detailDisclosure) as UIView
view.rightCalloutAccessoryView = detailButton
}
view.image = UIImage(named: LocalConstants.pluppPin)
return view
}
return nil
}