4

I have a map in my swift ios app and it is full of markers. Therefore I've decided to use marker clustering taken from here: https://github.com/ribl/FBAnnotationClusteringSwift

After implementing it in my app I see pins and clusters, so that's cool.

This is how I do it - first I call my webservice and fetch all the data for each pin and put it into the array:

func fetchRequests(radius: Double, lat: Double, lon: Double, completionHandler: (() -> Void)?){
    Alamofire.request(.GET, "http://mywebservice.com/fetchdata", parameters: ["param": "1"])
        .responseJSON { response in
            switch response.result {
            case .Success:

                self.array.removeAll()
                if let jsonData = response.result.value as? [[String: AnyObject]] {
                    for requestJSON in jsonData {
                        if let request = SingleRequest.fromJSON(JSON(requestJSON)){

                            let pinOne = FBAnnotation()
                            pinOne.coordinate = CLLocationCoordinate2D(latitude: request.latitude, longitude: request.longitude)
                            pinOne.title = request.title
                            pinOne.subtitle = request.discipline
                            self.array.append(pinOne)
                        }
                    }
                }
                self.clusteringManager.setAnnotations(self.array)
                completionHandler!()

                case .Failure(let error):
                print("SWITCH ERROR")
                print(error)
            }

    }
}

Then I'm actually calling the function from above and doing:

let annotationArray = self?.clusteringManager.clusteredAnnotationsWithinMapRect(self!.mapView.visibleMapRect, withZoomScale:scale)
self?.clusteringManager.displayAnnotations(annotationArray!, onMapView:(self?.mapView)!)

and at the end I'm putting data on map while checking whether each pin is a cluster or a normal pin:

func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
    var reuseId = ""
    if annotation.isKindOfClass(FBAnnotationCluster) {
        reuseId = "Cluster"
        var clusterView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
        clusterView = FBAnnotationClusterView(annotation: annotation, reuseIdentifier: reuseId, options: nil)

        return clusterView
    } else {
        reuseId = "Pin"
        var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId) as? MKPinAnnotationView
        pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
        pinView!.calloutOffset = CGPoint(x: -5, y: 5)
        pinView!.canShowCallout = true
        pinView!.rightCalloutAccessoryView = UIButton(type: .DetailDisclosure) as UIView

        return pinView   
    } 
}

So far so good. It works as expected, after clicking each pin I see the popup above it with a title and subtitle. But I would like to achieve a different effect.

When user taps a pin - instead of a popup above it - some square component appears below the map. I was looking for some similar solutions to give you as an example and I found it in the app called Periscope - this is how their map looks before user clicks any event:

enter image description here

and this is what happens right after clicking any point on the map:

enter image description here

This component of course disappears as soon as user scrolls the map.

Is that achievable in Swift?

user3766930
  • 5,629
  • 10
  • 51
  • 104

1 Answers1

2

You could try to add a UITapGestureRecognizer to your MKMapView. Then for the action, create a function that will display the View at the bottom.

GestureRecognizer added in viewDidLoad():

let showTap : UIGestureRecognizer = UITapGestureRecognizer(target: self, action: "tapShowView:")
    myMapView.addGestureRecognizer(showTap)

Then your function could be something like this.

 func tapShowView(sender:UIGestureRecognizer) {

   //Animate UIView in......

   //Or just add your UIView.
    let viewHeight = CGFloat(80)
    let bottomView = UIView(frame: CGRect(x: 0, y: self.view.frame.height - viewHeight, width: self.view.frame.width, height: viewHeight))
        bottomView.backgroundColor = .whiteColor()

   //add buttons, navbar etc....

    self.myMapView.addSubview(bottomView)
    //OR if you add it in viewDidload and set to bottomView.hidden = true
    bottomView.hidden = false

}

please ignore any syntax errors, I am not sitting at comp with Xcode on it. But this should give you a push. If you have any questions ill be back on when I wake up in a few hours(1:31 am here).. happy coding.

EDIT:

I think I miss read your question.....If you want to show the "BottomView" when user touches an annotation. You can call the "tapShowView" method from the MapViewDelegate method.

    func mapView(mapView: MKMapView, didSelectAnnotationView view: MKAnnotationView) {

    if view.annotation!.isKindOfClass(FBAnnotationCluster) {

        print("cluster selected")

    } else {

        print("single pin selected")
        //Call method here to display BottomView.
        self.tapShowView() //Edit tapShowView to remove sender
    }

}

Then to prevent any callout showing when you touch an annotation in this delegate method..

   func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {

ADD:

 annotationView?.canShowCallout = true   

EDIT 2: If you need to toggle the view when the user scrolls map etc. You can do that in the region did change delegate method.

 func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool){
  //Just toggle the hidden property of the view.
  BottomView.hidden = true
 }

I would suggest you have a good look at the MKMapView delegate functions. There is a whole bunch of cool stuff you can do with them. Anytime you add something like a UITableView/UICollectionView/MKMapView etc... Take a few minutes to scroll through the delegate functions. Its really amazing how powerful some of the delegate functions are. If you need anything else, Ill be around the rest of the day.

the_pantless_coder
  • 2,297
  • 1
  • 17
  • 30
  • 1
    How to pass data from annotations to the new view on the bottom? – wtznc Mar 08 '16 at 07:19
  • Thanks, that looks promising! And as wtznc asked above - how can I pass the data to the view in the bottom? Also - is there a way of hiding the white square when user moves the map? Currently it stays open all the time.. – user3766930 Mar 08 '16 at 10:08
  • If you need to pass data to the view in the bottom. You can add a parameter to your function. "func tapToShow(objID:String, randomInt: Int)". Then when you call the function you can pass the data like so "tapToShow("myIDString", 007) . – the_pantless_coder Mar 08 '16 at 11:55
  • 2
    I am going to add one more thing, not to complicate things....I hope lol. You could also create the view in storyboard. You would do this by adding a "Container View" to your current ViewController. This will "embed" a ViewController in your current view. Giving greater control and ease to create your desired layout. – the_pantless_coder Mar 08 '16 at 12:15
  • To do this, drag a Container View into your current view controller(size how you want). Then wire up to current view controller. That will allow you to toggle hidden true/false like with the basic UIVIew example. Next create a new ViewController class file. Attach to the new view controller that was added to storyboard when you created the container view. Now your code is much cleaner, easier to manage down the road...also more future proof when new devices are released. Happy coding. – the_pantless_coder Mar 08 '16 at 12:22
  • Thank you, now it clears a lot! I followed your advice with embedding the view controller and now in `tapToShow` I'm just doing `hidden = false` on my container. That works smooth! One last question - I added a label to my new view controller, so now with that approach - how can I change its value to the one I want? In other words - how can I pass the data from method `tapToShow` to the new view controller? – user3766930 Mar 08 '16 at 16:56
  • Btw, as you can see in my original question - this is how I corelate pins with their data: `pinOne.title = request.title; pinOne.subtitle = request.discipline` - now is there a way of fetching somehow this data for each pin and print it as a label in the new embedded ui view controller? – user3766930 Mar 08 '16 at 17:03
  • There are a few ways....Best is to read this question, it pretty much will show you what to do. http://stackoverflow.com/questions/34348275/pass-data-between-viewcontroller-and-containerviewcontroller If that does not work, try to search "swift pass data to container view". Tons of examples :) Try it out and if you have problems post a new question and let me know and ill try my best to help you answering the new question. A lot depends on how you have your project setup. – the_pantless_coder Mar 08 '16 at 17:25
  • ok, I managed to pass the data and now everything works great, this was the solution I needed and I'm really grateful for that! The very last question is - is there a way of animating the bottom component? Now when I just change it's property `hidden` to true it appears or disappears, but is there a way of sliding it to the screen from the bottom when user taps a pin and sliding it down in other case? – user3766930 Mar 09 '16 at 10:07
  • Great! If you need to animate a view. Do a search for "swift animate view". It will give you tons of exampls/tutorials. I would give you the code, but Its a good thing to learn from scratch, once you learn how to animate views, your final project will look much more polished. :) If my answer was correct, please mark it as accepted :) If you have any other questions start a new question and post link here....and ill try to help you. Happy coding. – the_pantless_coder Mar 09 '16 at 13:48
  • Thank you very much for all that help! – user3766930 Mar 09 '16 at 14:50