Im trying to create a button within each annotation view to bring the user to a new page custom to the selected annotation. I've successfully implemented the mapview to show the annotations with titles and subtitles loaded from my parse database but am struggling to find a way of 1) adding a button to the annotation view to bring the user to a new view 2) finding a way of creating custom views for each of the annotations when selected. Any help would be greatly appreciated ?
Code
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
var MapViewLocationManager:CLLocationManager! = CLLocationManager()
let btn = UIButton(type: .DetailDisclosure)
override func viewDidLoad() {
super.viewDidLoad()
mapView.showsUserLocation = true
mapView.delegate = self
MapViewLocationManager.delegate = self
MapViewLocationManager.startUpdatingLocation()
mapView.setUserTrackingMode(MKUserTrackingMode.Follow, animated: true)
}
override func viewDidAppear(animated: Bool) {
let annotationQuery = PFQuery(className: "Clubs")
annotationQuery.findObjectsInBackgroundWithBlock {
(clubs, error) -> Void in
if error == nil {
// The find succeeded.
print("Successful query for annotations")
// Do something with the found objects
let myClubs = clubs! as [PFObject]
for club in myClubs {
//data for annotation
let annotation = MKPointAnnotation()
let place = club["location"] as? PFGeoPoint
let clubName = club["clubName"] as? String
let stadiumName = club["stadium"] as? String
annotation.title = clubName
annotation.subtitle = stadiumName
annotation.coordinate = CLLocationCoordinate2DMake(place!.latitude,place!.longitude)
//add annotations
self.mapView.addAnnotation(annotation)
}
} else {
// Log details of the failure
print("Error: \(error)")
}
}
}
For 1st - You can use leftCalloutAccessoryView & rightCalloutAccessoryView on annoationView. You will have to implement MapViewDelegate
mapView(_:viewForAnnotation:).
For 2nd - Similar to calloutAccessoryView, you have access to detailCalloutAccessoryView on annoationView. You can use that to customize your callout.
This is Objective-C solution, hope it may help you.
To set button to annotationView,
UIButton *detailButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annotationView.rightCalloutAccessoryView = detailButton;
When click performed on this button, following method called like delegate, so override following method too,
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
You can do whatever customisation you want in above method because you know that user tapped on accessory view by now.
Related
In my project I am using a mapView to render locations from Lat-Lon received from an API. My project has a button which does the follow:
When clicked, it fires a timer that retrieves coordinates from the web and then plots on the mapview
When clicked again, it stops the timer and no data is retrieved.
However even when the timer is stopped it consumes a lot of memory around 100mbs if not more. So I want to release the memory when user is not using map and when they are map should come back again. I did the following to release memory:
self.mapView.delegate = nil;
self.mapView.removeFromSuperview()
self.mapView = nil;
This removes map and my memory comes back to 20mbs, normal. However is this the correct way to release memory? and how do I get it back once the button is pressed?.
To add a map you can do this:
import UIKit
import MapKit
class ViewController: UIViewController {
var mapView: MKMapView?
#IBOutlet weak var framer: UIView!//uiview to put map into
var coordinate = CLLocationCoordinate2D(){
willSet{
print("removing annotation...")
if let m = mapView{
m.removeAnnotation(anno)
}
}
didSet{
print("did set called, adding annotation...")
anno.coordinate = coordinate
if let m = mapView{
m.addAnnotation(anno)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func start(_ sender: Any) {
let mk = MKMapView()
mk.bounds = framer.bounds
mk.mapType = MKMapType.standard
mk.isZoomEnabled = true
mk.isScrollEnabled = true
// Or, if needed, we can position map in the center of the view
mk.center = framer.center
mapView = mk
if let mk2 = mapView{
framer.addSubview(mk2)
}
}
To remove
#IBAction func stop(_ sender: UIButton) {
if mapView != nil{
if let mk2 = mapView{
mk2.delegate = nil;
mk2.removeFromSuperview()
mapView = nil;
}
}
}
}
I am rather new to swift and object oriented programming and I am trying to switch views from a view that contains a map to another view by tapping on a right accessory callout button in the annotation of a pin that is dropped by a long press. In the storyboard, I have created a segue between the two views and assigned the segue with the identifier mapseg. Unfortunately, after trying everything I could find via google I cannot get the segue to occur when I tap the right accessory callout button and have no idea why.The application itself is a tabbed application with three tabs. The view for the second tab is the one that contains the map. Also, I don't know if this could have something to do with it, but the view I am trying to transition from is embedded in a navigation controller. Here is my code for the view that I am trying to transition from.
import UIKit
import MapKit
class SecondViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var MapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
self.MapView.delegate = self
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
let reuseID = "pin"
var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseID) as? MKPinAnnotationView
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseID)
pinView!.canShowCallout = true
pinView!.animatesDrop = true
pinView!.enabled=true
let btn = UIButton(type: .DetailDisclosure)
btn.titleForState(UIControlState.Normal)
pinView!.rightCalloutAccessoryView = btn as UIView
return pinView
}
#IBAction func dropPin(sender: UILongPressGestureRecognizer) {
if sender.state == UIGestureRecognizerState.Began {
let location = sender.locationInView(self.MapView)
let locCoord = self.MapView.convertPoint(location, toCoordinateFromView: self.MapView)
let annotation = MKPointAnnotation()
annotation.coordinate = locCoord
annotation.title = "City Name"
annotation.subtitle = ""
self.MapView.removeAnnotations(MapView.annotations)
self.MapView.addAnnotation(annotation)
self.MapView.selectAnnotation(annotation, animated: true)
}
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
if control == view.rightCalloutAccessoryView {
self.performSegueWithIdentifier("mapseg", sender: self)
}
}
}
}
I think you need to override prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) method. And check whether if working
I've been trying to code in swift a prepare for segue or to push view controller in order to move a map with a pin i have in a View Controller to another View Controller but in a smaller version. If someone can give an example on how to do it with this code, you would be very helpful.
I've been trying to do this in this function, so that when the user clears the pin title it goes to the other view controller with that same map:
if control == annotationView.rightCalloutAccessoryView {
print("Disclosure Pressed!")
}
Here's the complete code for this View Controller.
import UIKit
import MapKit
protocol HandleMapSearch {
func dropPinZoomIn(placemark:MKPlacemark)
}
class ViewController: UIViewController {
let locationManager = CLLocationManager()
var resultSearchController:UISearchController? = nil
var selectedPin:MKPlacemark? = nil
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.requestLocation()
//
let locationSearchTable = storyboard!.instantiateViewControllerWithIdentifier("LocationSearchTable") as! LocationSearchTable
resultSearchController = UISearchController(searchResultsController: locationSearchTable)
resultSearchController?.searchResultsUpdater = locationSearchTable
//
let searchBar = resultSearchController!.searchBar
searchBar.sizeToFit()
searchBar.placeholder = "Search for places"
navigationItem.titleView = resultSearchController?.searchBar
//
resultSearchController?.hidesNavigationBarDuringPresentation = false
resultSearchController?.dimsBackgroundDuringPresentation = true
definesPresentationContext = true
//
locationSearchTable.mapView = mapView
//
locationSearchTable.handleMapSearchDelegate = self
//
let button = UIButton(type: UIButtonType.System) as UIButton
button.frame = CGRectMake(100, 100, 100, 50)
button.backgroundColor = UIColor.greenColor()
button.setTitle("Button", forState: UIControlState.Normal)
button.addTarget(self, action: Selector("Action:"), forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(button)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension ViewController : CLLocationManagerDelegate {
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == .AuthorizedWhenInUse {
locationManager.requestLocation()
}
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.first {
let span = MKCoordinateSpanMake(0.05, 0.05)
let region = MKCoordinateRegion(center: location.coordinate, span: span)
mapView.setRegion(region, animated: true)
}
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("error:: \(error)")
}
}
extension ViewController: HandleMapSearch {
func dropPinZoomIn(placemark:MKPlacemark){
// cache the pin
selectedPin = placemark
// clear existing pins
mapView.removeAnnotations(mapView.annotations)
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.coordinate
annotation.title = placemark.name
if let city = placemark.locality,
let state = placemark.administrativeArea {
annotation.subtitle = "\(city) \(state)"
}
mapView.addAnnotation(annotation)
let span = MKCoordinateSpanMake(0.05, 0.05)
let region = MKCoordinateRegionMake(placemark.coordinate, span)
mapView.setRegion(region, animated: true)
}
}
extension ViewController : MKMapViewDelegate {
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
//return nil so map view draws "blue dot" for standard user location
return nil
}
let reuseId = "pin"
var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId) as? MKPinAnnotationView
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView?.pinTintColor = UIColor.orangeColor()
pinView?.canShowCallout = true
pinView?.rightCalloutAccessoryView = UIButton(type: UIButtonType.DetailDisclosure) as UIButton
return pinView
}
func mapView(mapView: MKMapView, annotationView: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
if control == annotationView.rightCalloutAccessoryView {
print("Disclosure Pressed!")
}
}
}
In a word, no. Don't do that.
Trying to move views between view controllers is almost certainly the wrong way to do things.
It absolutely will not work to move a view (including a map view) from one view controller to another in prepareForSegue. The destination view controller's views haven't been loaded when prepareForSegue is called.
Instead, if you want to show a map in both your current view controller and the one to which you are segueing, you should define a map view in both view controllers, sized the way you want them in both places. You can then use prepareForSegue to tell the destination view controller the map region to display in it's map view. (Don't try to manipulate the destination view controller's map view from prepareForSegue. It won't work, and it's bad design even if you could make it work.)
Add a property mapRegion of type MKCoordinateRegion to the destination view controller. In the source view controller's prepareForSegue, get the map region of the current map and set the destination view controller's mapRegion property using that value.
Then, in the destination view controller's viewWillAppear:animated method, take the mapRegion property and used it to set the map view's region.
EDIT:
You also mentioned a pin. If you want the same pin to appear in both maps you should be able to fetch the MKAnnotation from the source view controller's map and pass it to the destination view controller, much like I described for setting the map region.
I'm new in Swift. I'm trying to have different color pin or custom pin on specific pin. My code works. I've a purple pin, but I want make a difference between them. How can I do it? I think there something to do in MapView delegate method but I didn't find it.
import UIKit
import MapKit
class MapsViewController: UIViewController , MKMapViewDelegate{
var shops: NSArray? {
didSet{
self.loadMaps()
}
}
#IBOutlet weak var map: MKMapView?
override func viewDidLoad() {
super.viewDidLoad()
loadMaps()
self.title = "Carte"
self.map!.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
// simple and inefficient example
let annotationView = MKPinAnnotationView()
annotationView.pinTintColor = UIColor.purpleColor()
return annotationView
}
func loadMaps(){
// navigationController?.navigationBar.topItem!.title = "Carte"
let shopsArray = self.shops! as NSArray
for shop in shopsArray {
let location = CLLocationCoordinate2D(
latitude: shop["lat"] as! Double,
longitude: shop["long"] as! Double
)
let annotation = MKPointAnnotation()
annotation.coordinate = location
annotation.title = shop["name"] as? String
annotation.subtitle = shop["addresse"] as? String
map?.addAnnotation(annotation)
}
// add point
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
A better approach is to use a custom annotation class that implements the MKAnnotation protocol (an easy way to do that is to subclass MKPointAnnotation) and add whatever properties are needed to help implement the custom logic.
In the custom class, add a property, say pinColor, which you can use to customize the color of the annotation.
This example subclasses MKPointAnnotation:
import UIKit
import MapKit
class ColorPointAnnotation: MKPointAnnotation {
var pinColor: UIColor
init(pinColor: UIColor) {
self.pinColor = pinColor
super.init()
}
}
Create annotations of type ColorPointAnnotation and set their pinColor:
let annotation = ColorPointAnnotation(pinColor: UIColor.blueColor())
annotation.coordinate = coordinate
annotation.title = "title"
annotation.subtitle = "subtitle"
self.mapView.addAnnotation(annotation)
In viewForAnnotation, use the pinColor property to set the view's pinTintColor:
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
let reuseId = "pin"
var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId) as? MKPinAnnotationView
if pinView == nil {
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
let colorPointAnnotation = annotation as! ColorPointAnnotation
pinView?.pinTintColor = colorPointAnnotation.pinColor
}
else {
pinView?.annotation = annotation
}
return pinView
}
I'm new to the swift language, and haven't done an application with mapkit yet. But I have the map and regions set, but I'm hung up on how to allow users to add pins.
Let me clarify, I have no idea of even where to start, All I have at the moment (for the pins) is my variable, but I'm not even sure if that's correct. Any help would be much appreciated!!
What I have...
var MyPins: MKPinAnnotatoinView!
......
override func viewDidLoad() {
super.viewDidLoad()
Mapview code
.....
.....
}
Your pin variable is correct. Now you just need to add this annotation to MKMapView.
You can also create a custom class for MKAnnotation to add custom annotation to map view.
A beta demo for MapExampleiOS8 => Which supports Swift 2.1
Follow steps below:
1. Add MapKit.framework to project.
2. Create Storyboard variable IBOutlet of map view control or create it in view controller. Set delegate for this variable to override it's delegate methods:
Add delegate signature to view controller interface:
class ViewController: UIViewController, MKMapViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Set map view delegate with controller
self.mapView.delegate = self
}
}
3. Override its delegate methods:
Here we need to override mapView(_:viewForAnnotation:) method to show annotation pins on map.
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if (annotation is MKUserLocation) {
return nil
}
if (annotation.isKind(of: CustomAnnotation.self)) {
let customAnnotation = annotation as? CustomAnnotation
mapView.translatesAutoresizingMaskIntoConstraints = false
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "CustomAnnotation") as MKAnnotationView!
if (annotationView == nil) {
annotationView = customAnnotation?.annotationView()
} else {
annotationView?.annotation = annotation;
}
self.addBounceAnimationToView(annotationView)
return annotationView
} else {
return nil
}
}
4. Add MKPointAnnotation to map view.
You can add pin to location in map view. For simplicity add code to viewDidLoad() method.
override func viewDidLoad() {
super.viewDidLoad()
// Set map view delegate with controller
self.mapView.delegate = self
let newYorkLocation = CLLocationCoordinate2DMake(40.730872, -74.003066)
// Drop a pin
let dropPin = MKPointAnnotation()
dropPin.coordinate = newYorkLocation
dropPin.title = "New York City"
mapView.addAnnotation(dropPin)
}
You will need to call a method for when and where the user needs to add the pin. If you want it to add a pin where the user taps and holds on the map, you will need to call a gestureRecognizer, but if you want to do it via a button you will obviously just call that in an action. Either way the documentation for adding pins is throughly discussed Here