Gold Challenge - Button to Cycle Though Pins

#1

I have completed the first part of the Gold challenge, successfully placing pins onto the map view. However, I am stuck with the second part where we are asked to display and cycle through the pins. Has anybody succeeded in implementing this? If so, can you give me some pointers? I really do want to learn how to do this, but all my research has not helped thus far.

#2

My solution was to wait until all the map processing was finished and LocationManager…didUpdateLocations was called. Within that method, I used geocodeAddressString to generate the latitude and longitude for my first location and drop a pin there – and then, within the completion block for that first one, I started up the geocodeAddressString for my second address string, and so on. I created a 0,1,2 cycle counter within a button I stuck at the top of the screen – if the counter is 1 when I tap the button, I move to the first location and increment the counter. If the counter goes over 2, I re-set it to 0.

#3
  1. Declare an array to hold all the annotations you create and initialize it with an empty array. Declare a variable called currentIndex and initialize it with 0.

  2. When you create an annotation, add it to the array.

  3. In the button’s action, retrieve an annotation from the array using the currentIndex(if the index is too high, first set it back to zero). After you retrieve the annotation, advance the currentIndex.

Alternatively, you can let the current index increment up to infinity and use the modulus operator to get an index value. The modulus operator returns the remainder after doing integer division of the left hand side divided by the right hand side:

6 % 3 => 0
5 % 3 => 2
4 % 3 => 1
3 % 3 => 0
2 % 3 => 2  <--
1 % 3 => 1  <---- These left hand sides have zero integer 3's in them.
0 % 3 => 0  <--
  1. An annotation has a coordinate property, and in the Silver challenge you learned how to zoom in on a coordinate.

By the way, this challenge revealed a flaw in my code: the Current Location button only worked once. I had to fix that.

#4

This is my implementation:

/*** MapViewController.swift ***/

// ...

let cycleLocationsButton = UIButton(type: .System)
        cycleLocationsButton.titleLabel?.font = UIFont.systemFontOfSize(14.0)
        cycleLocationsButton.tintColor = UIColor.whiteColor()
        cycleLocationsButton.setTitle("Cycle Locations", forState: .Normal)
        cycleLocationsButton.layer.cornerRadius = 4.0
        cycleLocationsButton.layer.backgroundColor = UIColor(red: 0.0, green: 0.48, blue: 1.0, alpha: 1.0).CGColor
        cycleLocationsButton.translatesAutoresizingMaskIntoConstraints = false
        cycleLocationsButton.addTarget(self, action: "cycleLocations", forControlEvents: .TouchUpInside)
        self.view.addSubview(cycleLocationsButton)
        
        let cycleLocationsButtonHorizontalConstraint = NSLayoutConstraint(item: cycleLocationsButton, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 15.0)
        let cycleLocationsButtonVerticalConstraint = NSLayoutConstraint(item: cycleLocationsButton, attribute: .Bottom, relatedBy: .Equal, toItem: self.bottomLayoutGuide, attribute: .Top, multiplier: 1.0, constant: -28.0)
        let cycleLocationsButtonWidthConstraint = NSLayoutConstraint(item: cycleLocationsButton, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 120.0)
        let cycleLocationsButtonHeightConstriant = NSLayoutConstraint(item: cycleLocationsButton, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 35.0)
        cycleLocationsButtonHorizontalConstraint.active = true
        cycleLocationsButtonVerticalConstraint.active = true
        cycleLocationsButtonWidthConstraint.active = true
        cycleLocationsButtonHeightConstriant.active = true

    func cycleLocations() {
        if self.cycleLocationIndex == nil || self.cycleLocationIndex == 2 {
            self.cycleLocationIndex = 0
            self.pinpointAndAnnotateLocationOnMap(self.university)
        }
        else if self.cycleLocationIndex == 0 {
            self.cycleLocationIndex = 1
            self.pinpointAndAnnotateLocationOnMap(self.workplace)
        }
        else if self.cycleLocationIndex == 1 {
            self.cycleLocationIndex = 2
            self.pinpointAndAnnotateLocationOnMap(self.home)
        }
    }
    
    private func pinpointAndAnnotateLocationOnMap(location: ChosenLocations) {
        let latitudeDeltaZoomLevel: CLLocationDegrees = 0.007
        let longitudeDeltaZoomLevel: CLLocationDegrees = 0.007
        let areaSpannedByMapRegion: MKCoordinateSpan = MKCoordinateSpan(latitudeDelta: latitudeDeltaZoomLevel, longitudeDelta: longitudeDeltaZoomLevel)
        let geographicalCoordinateStruct: CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
        let mapRegionToDisplay: MKCoordinateRegion = MKCoordinateRegionMake(geographicalCoordinateStruct, areaSpannedByMapRegion)
        self.mapView.setRegion(mapRegionToDisplay, animated: true)
        
        let annotation = MKPointAnnotation()
        annotation.coordinate = geographicalCoordinateStruct
        annotation.title = location.title ?? "unspecified"
        self.mapView.addAnnotation(annotation)
    }

// ChosenLocations.swift

import UIKit
import MapKit

class ChosenLocations: NSObject, MKAnnotation {
    var title: String?
    var coordinate: CLLocationCoordinate2D
    
    init(title: String = "Favorite Location", coordinate: CLLocationCoordinate2D) {
        self.title = title
        self.coordinate = coordinate
    }
}

I hope that helps

#5

I looked at these posts after stumbling through to a solution. Seems like I almost exactly follow 7’s flow above. But my code started from the end of chapter 6, not the Silver solution, so it looks rather sparse. However, I think I met the Gold requirements. Here’s a crude solution.

Set up an array of annotations:

[code]class MapViewController: UIViewController, MKMapViewDelegate {

var mapView:MKMapView!
var anns:[MKPointAnnotation]=[]
var annIndex:Int=0[/code]

Load up a few annotations:

override func viewDidLoad() { super.viewDidLoad() print("MapViewController loaded its view.") var ann=MKPointAnnotation() var annLoc=CLLocationCoordinate2DMake(39.9, -75.9) ann.coordinate=annLoc ann.title="Here I am" ann.subtitle="Ann 0" mapView.addAnnotation(ann) anns.append(ann) ann=MKPointAnnotation() annLoc=CLLocationCoordinate2DMake(39.7, -75.7) ann.coordinate=annLoc ann.title="Here I am" ann.subtitle="Ann 1" mapView.addAnnotation(ann) anns.append(ann) ann=MKPointAnnotation() annLoc=CLLocationCoordinate2DMake(39.5, -75.5) ann.coordinate=annLoc ann.title="Here I am" ann.subtitle="Ann 2" mapView.addAnnotation(ann) anns.append(ann) print(anns.count,"annotations")
This gets the 3 pins onto the map. And I’m using a nil MKAnnotationView, so I get the default red pin.
Set up a button (modeled after Silver solutions/examples):

let button = UIButton(type: UIButtonType.System) as UIButton button.frame = CGRectMake(100, 100, 50, 50) button.backgroundColor = UIColor.grayColor() button.setTitle("NextLoc", forState: UIControlState.Normal) button.addTarget(self, action: "buttonAction:", forControlEvents: UIControlEvents.TouchUpInside) button.translatesAutoresizingMaskIntoConstraints=false self.view.addSubview(button) let widthConstraint = button.widthAnchor.constraintEqualToAnchor(nil, constant: 100.0) let heightConstraint = button.heightAnchor.constraintEqualToAnchor(nil, constant: 35.0) let horizontalConstraint = button.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor) let verticalConstraint = button.bottomAnchor.constraintEqualToAnchor(self.bottomLayoutGuide.topAnchor, constant: -28.0) NSLayoutConstraint.activateConstraints([widthConstraint, heightConstraint, horizontalConstraint, verticalConstraint])
Cycle through the pins, although I could have used a “region” or “setCenterCoordinate” related function since the showAnnotations zooms in rather close.

func buttonAction(sender:UIButton!) { print("Button tapped. annIndex=",annIndex) mapView.showAnnotations([anns[annIndex]], animated: true) annIndex=(++annIndex)%3 }

#6

Here is how I implemented the challenge:

Works really well :wink:

struct Locations {
    var name: String
    var lat: Double
    var long: Double
    
    init(name: String, lat: Double, long: Double) {
        self.name = name
        self.lat = lat
        self.long = long
    }
}

override func loadView() {
    super.loadView()

    ...

    //create array of location objects:
    var locations = [Locations]()
    locations.append(Locations(name: "New York City", lat: 40.730872, long: -74.003066))
    locations.append(Locations(name: "London", lat: 51.5074, long: 0.1278))
    locations.append(Locations(name: "Tokyo", lat: 35.6895, long: 139.6917))

    //drop location pins onto map:
    for location in locations {
        let dropPin = MKPointAnnotation()
        dropPin.coordinate = CLLocationCoordinate2DMake(location.lat, location.long)
        dropPin.title = location.name
        mapView.addAnnotation(dropPin)
    }

    //create button to toggle pins:
    let pinButton = UIButton(type: .system)
    pinButton.setTitle("Pins", for: .normal)
    pinButton.backgroundColor = UIColor.white.withAlphaComponent(0.5)
    pinButton.frame = CGRect(x: 0, y: 0, width: 100, height: 50)
    pinButton.translatesAutoresizingMaskIntoConstraints = false
    pinButton.addTarget(self, action: #selector(selectPin(_:)), for: .touchUpInside)
    view.addSubview(pinButton)
    pinButton.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor, constant: -8).isActive = true
    pinButton.leftAnchor.constraint(equalTo: margins.leftAnchor).isActive = true

}

//keeps track of current pin index:
var selectedAnnotationIndex: Int = -1

//function call when button is pressed:
func selectPin(_ button: UIButton) {
    
    //data checks:
    if !(mapView.annotations.count > 0) {
        return
    }
    
    //go to next annotation or back to start if last one:
    selectedAnnotationIndex += 1
    if selectedAnnotationIndex >= mapView.annotations.count {
        selectedAnnotationIndex = 0
    }
    
    //select pin and animate map:
    let annotation = mapView.annotations[selectedAnnotationIndex]
    let zoomedInCurrentLocation = MKCoordinateRegionMakeWithDistance(annotation.coordinate, 5000, 5000)
    mapView.setRegion(zoomedInCurrentLocation, animated: true)
}