The Gold challenge

Has anybody done this challenge? I’m writing code and it shows annotations but I don’t know how to make the button to change them.

May be not the correct solution but here’s my attempt:

In AppDelegate.swift
import CoreLocation
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
locationManager.requestWhenInUseAuthorization()
return true
}

In MapViewController.swift

//
// Copyright © 2015 Big Nerd Ranch
//

import UIKit
import MapKit

class MapViewController: UIViewController, MKMapViewDelegate {

var mapView: MKMapView!
let homeLocation: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 52.5857, longitude: 13.2259)
let bornLocation: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 48.0485, longitude: 10.8552)
let niceLocation = CLLocationCoordinate2D(latitude: 47.8427, longitude: 8.3191)

override func loadView() {
    mapView = MKMapView()
    view = mapView
    let myPositionButton = MKUserTrackingBarButtonItem(mapView: mapView)
    let myBirthPlaceButton = UIBarButtonItem(title: "Birth Place", style: .plain, target: self, action:#selector(MapViewController.gotoBirthPlace))
    let whereIamNowButton = UIBarButtonItem(title: "I'm now here", style: .plain, target: self, action: #selector(MapViewController.gotoWhereIamNow))
    let placeOfInterestButton = UIBarButtonItem(title: "I was there", style: .plain, target: self, action: #selector(MapViewController.gotoPlaceOfInterest))
    let toolbarFrame = CGRect(origin: CGPoint(x: 50, y: 10) , size: CGSize(width: 44, height: 44))
    let toolBar = UIToolbar.init(frame: toolbarFrame)
    toolBar.items = [myPositionButton,myBirthPlaceButton,whereIamNowButton,placeOfInterestButton]
    let segmentedControl = UISegmentedControl(items: ["Standard", "Hybrid", "Satellit"])
    segmentedControl.backgroundColor = UIColor.white.withAlphaComponent(0.5)
    segmentedControl.selectedSegmentIndex = 0
    segmentedControl.addTarget(self, action: #selector(MapViewController.mapTypeChanged(_:)), for: .valueChanged)

    segmentedControl.translatesAutoresizingMaskIntoConstraints = false
    toolBar.translatesAutoresizingMaskIntoConstraints = false
    toolBar.isTranslucent = true
    addPinsToMap(location: homeLocation, title: "My home", subtitle: "is my castle")
    addPinsToMap(location: bornLocation, title: "Landsberg", subtitle: "am Lech")
    addPinsToMap(location: niceLocation, title: "The german Grand Canyon", subtitle: "Wutach Schlucht")
    view.addSubview(segmentedControl)
    view.addSubview(toolBar)

    let topContraint = segmentedControl.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor, constant: 8)
    let margins = view.layoutMarginsGuide
    let leadingContraint = segmentedControl.leadingAnchor.constraint(equalTo: margins.leadingAnchor)
    let trailingContraint = segmentedControl.trailingAnchor.constraint(equalTo: margins.trailingAnchor)

    let toolbarBottomConstraint = toolBar.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor)
    let toolbarLeadingContraint = toolBar.leadingAnchor.constraint(equalTo: view.leadingAnchor)
    let toolbarTrailingContraint = toolBar.trailingAnchor.constraint(equalTo: view.trailingAnchor)

    toolbarLeadingContraint.isActive = true
    toolbarBottomConstraint.isActive = true
    toolbarTrailingContraint.isActive = true
    topContraint.isActive = true
    leadingContraint.isActive = true
    trailingContraint.isActive = true
    
}

func addPinsToMap(location: CLLocationCoordinate2D, title: String, subtitle:String) {
    let annotation = MyAnnotation(coordinate: location, title: title, subtitle: subtitle)
    mapView.addAnnotation(annotation)
}

func setCenterOfMapToLocation(location: CLLocationCoordinate2D) {
    let span = MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
    let region = MKCoordinateRegion(center: location, span: span)
    mapView.setRegion(region, animated: true)
}

func gotoBirthPlace() {
    print("I was born here")
    setCenterOfMapToLocation(location:bornLocation)
}

func gotoWhereIamNow() {
    print("now I'm here")
    setCenterOfMapToLocation(location: homeLocation)
}

func gotoPlaceOfInterest() {
    print("I have been here")
    setCenterOfMapToLocation(location: niceLocation)
}

func mapTypeChanged(_ segControl: UISegmentedControl) {
    switch segControl.selectedSegmentIndex{
    case 0:
        mapView.mapType = .standard
    case 1:
        mapView.mapType = .hybrid
    case 2:
        mapView.mapType = .satellite
    default:
        break
    }
}

override func viewDidLoad() {
    super.viewDidLoad()
    

}

}

This was missing
//
// MyPlaces.swift
// WorldTrotter
//
// Copyright © 2017 Big Nerd Ranch. All rights reserved.
//

import UIKit
import MapKit

class MyAnnotation: NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D = CLLocationCoordinate2DMake(0,0)
var title: String!
var subtitle: String!

init(coordinate: CLLocationCoordinate2D, title: String, subtitle: String) {
    self.coordinate = coordinate
    self.title = title
    self.subtitle = subtitle
    super.init()
}

}

I did something a little different. It’s not the best approach (I probably should have a new structure for the pin data instead of separate data items within MapViewController, and I should have tried to use a better button style) but, functionally at least, it works the way I’d hoped it would. It drops a new pin the first 3 times you hit the Pin button (which I positioned in the lower right of the screen) and, thereafter, cycles through the pins. I couldn’t get it to work with MKPinAnnotationView as suggested in the challenge. Here’s what I did:

At the top of MapViewController:

import CoreLocation

In MapViewController, add a few new vars:

var pinLocs = [CLLocationCoordinate2D]()
var pinIndex = 0
var labels = [String]()

and a couple of functions to create the pin button and show the pins (replace the xxx’s with your own coordinates):

func initPinButton(_ anyView: UIView!) {
    let pinButton = UIButton.init(type: .system)
    pinButton.setTitle("Show Pin", for: .normal)
    pinButton.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(pinButton)
    
    //Constraints
    let bottomConstraint = pinButton.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor, constant: -48 )
    let trailingConstraint = pinButton.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor)
    
    bottomConstraint.isActive = true
    trailingConstraint.isActive = true
    
    pinButton.addTarget(self, action: #selector(MapViewController.showPin(sender:)), for: .touchUpInside)
}

func showPin(sender: UIButton!) {
    let pinCount = pinLocs.count
    switch pinLocs.count {
    case 0: // Create and display first pin
        pinLocs.append(CLLocationCoordinate2D(latitude: xxx, longitude: xxx))     // Birth
        labels.append("I was born here")
        
    case 1: // Create and display second pin
        pinLocs.append(CLLocationCoordinate2D(latitude: xxx, longitude: xxx))     // Live
        labels.append("I live here")
    case 2: // Create and display third pin
        pinLocs.append(CLLocationCoordinate2D(latitude: xxx, longitude: xxx))       // Visited
        labels.append("I visited here")
    default:
        break
    }
    let pinLoc = pinLocs[pinIndex]
    let label = labels[pinIndex]
    let region = MKCoordinateRegion(center: pinLoc, span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1))
    mapView.setRegion(region, animated: true)
    
    // Drop a pin at this Location the first time through
    if pinCount < 3 {
        let myAnnotation: MKPointAnnotation = MKPointAnnotation()
        myAnnotation.coordinate = CLLocationCoordinate2DMake(pinLoc.latitude, pinLoc.longitude);
        myAnnotation.title = label
        mapView.addAnnotation(myAnnotation)
    }
    
    pinIndex += 1
    if pinIndex > 2 {
        pinIndex = 0
    }
}

And in loadView(), add a call to the initPinButton function:

    initPinButton(segmentedControl)

I did something similar to @retireehavingfun:

At the top of MapViewController:

var pinIndex = 0
var pinLocations: [CLLocationCoordinate2D] = []
var pinLabels = ["Birth Place", "Current Location", "Somewhere I've Been"]
var pinAddresses = ["Street, City, State Zip","Street, City, State Zip","Street, City, State Zip"]

Then, a little further down:

    func initPinButton(_ anyView: UIView)
	{
		let button = UIButton.init(type: .system)
		button.setTitle("Show Pin", for: .normal)
		button.translatesAutoresizingMaskIntoConstraints = false
		view.addSubview(button)

		let margins = view.layoutMarginsGuide
		let topConstraint = button.topAnchor.constraint(equalTo: anyView.bottomAnchor, constant:8)
		let trailingConstraint = button.trailingAnchor.constraint(equalTo: margins.trailingAnchor)

		topConstraint.isActive = true
		trailingConstraint.isActive = true
		
		button.addTarget(self, action: #selector(MapViewController.moveToNextPin(_:)), for: .touchUpInside)
	}
	
	func setupPins()
	{
		let geocoder = CLGeocoder()
		
		let operationQueue = OperationQueue()
		// set it to 1 so we queue up any additional requests
		// I believe this is important for CLGeocoder, as I don't
		// believe you can attempt to call geocodeAddressString
		// when the CLGeocode object is already in use
		operationQueue.maxConcurrentOperationCount = 1
		
		for address in pinAddresses
		{
			let operation = BlockOperation(block: {
				let semaphore = DispatchSemaphore(value: 0)
				
				geocoder.geocodeAddressString(address, completionHandler: { (placemarks, error) in
					if (error != nil)
					{
						print("\(String(describing: error))")
					}
					else if (placemarks!.count > 0)
					{
						let topResult: CLPlacemark = placemarks![0]
						let placemark = MKPlacemark(placemark: topResult)
					
						self.pinLocations.append(placemark.coordinate)
						print("\(address) added")
					}
					else
					{
						print("No placemark found for address \(address)")
					}
					semaphore.signal()
				})
				
				_  = semaphore.wait(timeout: .distantFuture)
				
			})
			
			operationQueue.addOperation(operation)
		}
	}

    @objc func moveToNextPin(_ sender: UIButton)
	{
		// create the annotation
		let annotation = MKPointAnnotation()
		annotation.coordinate = pinLocations[pinIndex]
		annotation.title = pinLabels[pinIndex]
		
		// add it to the view
		mapView.addAnnotation(annotation)
		
		// move to the pin
		let region = MKCoordinateRegionMakeWithDistance(annotation.coordinate, 500, 500)
		mapView.setRegion(region, animated: true)
		
		// increment the index
		pinIndex += 1
		
		// make sure we don't run into any issues when we loop back around
		if (pinIndex > (pinLocations.count - 1))
		{
			pinIndex = 0
		}
	}

Then, at the bottom of loadView() add the following:

setupPins()
initPinButton(segmentedControl)

I figured being able to use an actual address rather than having to figure out the lat and long coordinates would be a little easier for customization, and would prove to be a little more customizable if needed (i.e. you can now prompt the user for those locations in address-form and add pins from there)

It is fairly simple actually. What I have done is:

  1. Create a button called switchPin
  2. Create a function that show the next pin location every time you press:
var counter = 0
func pinChange(sender: UIButton) {
    counter += 1
    switch counter {
    case 1:
     // Show your first pin
    case 2:
     // Show your second pin
    case 3:
     // Show your third pin
        counter = 0
    default:
        break
     }