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:
- Create a button called switchPin
- 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 }