Pass Variables to a New View Controller via a Subclass and Dozens of Map Pins

Pass Variables to a new View Controller via a Subclass and Dozens of Map Pins

It seems that you're very close. In calloutAccessoryControlTapped, you're get getting the place name and info. I'm assuming that's what you want to pass to the second view controller, so go ahead and do so before you show it:

func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let capital = view.annotation as! Capital
let placeName = capital.title
let placeInfo = capital.info

let secondViewController = sUIKeyInputUpArrowtoryboard!.instantiateViewController(withIdentifier: "SecondViewController") // I'm not sure why you're not just doing `storyboard.instantiateViewController(...); do you really have multiple storyboards floating around?
secondViewController.placeName = placeName
secondViewController.placeInfo = placeInfo
show(secondViewController, sender: self)
}

That presumes, of course, that your second view controller is has those placeName and placeInfo properties, e.g.

class SecondViewController {

var placeName: String!
var placeInfo: String!

override func viewDidLoad() {
// use placeName and placeInfo to populate UI controls as necessary
}
}

I confess, though, that your question has a ton of unrelated code that's hard to make sense of, so it's not clear precisely what you need to do. But the idea is clear, that calloutAccessoryControlTapped should

  • figure out what needs to get passed to the next view controller;
  • instantiate that view controller;
  • set the appropriate properties in that next view controller;
  • then show it; and
  • that second view controller should use whatever properties you set in the preceding view controller to configure it's UI.

Note, calloutAccessoryControlTapped in the first view controller cannot update the UI controls in the second view controller directly (since the controls for that view controller have not yet been hooked up to the outlets in the storyboard), but rather just passes whatever data that second view controller needs. Then that second view controller will configure its controls in its viewDidLoad.

How to Pass Variables and Objects from a Subclass via a Segue in Swift 3.0

  1. Another option is to call

    "func performSegue(withIdentifier identifier: String, sender: Any?)"

    which will trigger a segue from ViewController to
    SecondViewController. This is you like to keep the code of moving between ViewControllers in the storyboard ie, you controlled dragged from ViewController to SecondViewController to create a segue and gave it a unique id.

  2. Every UIViewController (subclass of) inherits a function

    "func prepare(for segue: UIStoryboardSegue, sender: Any?)"

    which will be called by the system, this is where you can add
    implementation to as the name suggest prepare anything needed before
    a particular segue is performed. At this time the next
    ViewController have been loaded from the Storyboard into memory (but
    have not begin to be displayed). And the "segue"* parameter of
    "prepare(for segue: UIStoryboardSegue, sender: Any?)" actually has a
    property "destination" which is actually the next ViewController.

    Be Careful though, as you may have more than 1 segue from this
    ViewController to different next ViewController.

    So "segue.destination" may not be your desired SecondViewController if
    you have more than 1 segue setup. Because the system calls
    "prepare(for segue: UIStoryboardSegue, sender: Any?)" for every
    segue leaving this current ViewController. Make sure you check
    "segue.identifier" to make sure your subsequent code are dealing
    with the same segue you think you are.

  3. Now you are finally able to do what your headline question is about.
    with a pointer to the SecondViewController you are free to set any
    property it has, which is the particular instance of your Capital
    object. To come full circle, the "sender" parameter of
    "performSegue(withIdentifier identifier: String, sender: Any?)"* and
    "prepare(for segue: UIStoryboardSegue, sender: Any?)" are actually the
    samething. So you can actually pass any object/struct you like from
    "performSegue()" to "prepare(for:)" Simply cast the sender object into the
    type you passed after confirming the "segue.identifier."


func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let capital = view.annotation as! Capital
let placeName = capital.title
let placeInfo = capital.info

// Option 1
perform segue.performSegue(withIdentifier: "SegueToSecondID", sender: capital)

//Option 2 programmatically create SecondViewController and show.
let SecondViewController = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController")
SecondViewController.capital = capital
self.show(SecondViewController!, sender: nil)
}

// If you are doing option 1
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "SegueToSecondID" && sender is Capital {
let destination = segue.destination as SeconViewController
destination.capital = sender
}
}

class SecondViewController {
//........
var capital: Capital? //Up to you if you want this as an optional

}

How to add Custom Image to Annotation via a Subclass - Swift 3.0

The map view's delegate, not the annotation object itself, decides the annotation view for an annotation. So you need to set the image in your implementation of the map view delegate method:

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?

If you want, your Capitol class could vend the correct image, for example:

class Capital: NSObject, MKAnnotation {
var title: String?
var coordinate: CLLocationCoordinate2D
var info: String
var imageForAnnotationView: UIImage? {
guard let title = title else { return nil }
return UIImage(named: "\(title).jpg")
}

You could rewrite your delegate implementation and obtain the image like so:

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "Capital"
guard let annotation = annotation as? Capital else { return nil }

let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) ?? MKPinAnnotationView(annotation:annotation, reuseIdentifier:identifier)

annotationView.annotation = annotation
annotationView.isEnabled = true
annotationView.canShowCallout = true
annotationView.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)

// set the image to the annotation view
annotationView.image = annotation.imageForAnnotationView

return annotationView
}

Navigate to New View Controller after Animation is Completed in Swift 3.0

Use DispatchQueue.main.asyncAfter.

EDITED

Set Storyboard ID to SecondViewController.

Sample Image

Example #1:

...
imageView.animationRepeatCount = 1 // <-- here
imageView.startAnimating()

DispatchQueue.main.asyncAfter(deadline: .now() + imageView.animationDuration) {
let secondViewController = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController")
self.show(secondViewController, sender: nil)
}

Example #2:

...
imageView.animationRepeatCount = 1 // <-- here
imageView.startAnimating()
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
let secondViewController = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController")
self.show(secondViewController, sender: nil)
}

iOS MKMapView Center on User Location and then allow unlimited Scrolling in Swift 3.0

1. Should work with the code you have now.

2. Add check for subsequent location updates

In the didUpdateLocations method, add a Bool to check whether the region was centered on the user already or not.

var regionHasBeenCentered = false

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]

if !regionHasBeenCentered {
let span: MKCoordinateSpan = MKCoordinateSpanMake(40.0, 40.0)
let userLocation: CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
let region: MKCoordinateRegion = MKCoordinateRegionMake(userLocation, span)

mapView.setRegion(region, animated: true)
regionHasBeenCentered = true
}

self.mapView.showsUserLocation = true
}

Now the map will no longer center on the user after the first update, until you change regionHasBeenCentered back to false. This will allow the user to scroll and zoom freely.

3. Implement MKMapViewDelegate method to detect map region changes

Implement MKMapViewDelegate on your view controller so that you can check for region changes.

class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {

…and set the view controller as the delegate:

override func viewDidLoad() {
// other things…

mapView.delegate = self
}

Then implement the following method which will be called right before the region changes. Here you can check to see if the span's dimensions are too small, and set them to a minimum appropriate.

func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
if mapView.region.span.latitudeDelta <= 40 && mapView.region.span.longitudeDelta <= 40 {
let minimumSpan = MKCoordinateSpan(latitudeDelta: 40, longitudeDelta: 40)
let minimumRegion= MKCoordinateRegion(center: mapView.centerCoordinate, span: minimumSpan)
mapView.setRegion(minimumRegion, animated: false)
}
}

Important note: From the MKCoordinateSpan documentation, the longitudeDelta will change as you move toward/away from the equator.

longitudeDelta

The amount of east-to-west distance (measured in degrees) to display for the map region. The number of kilometers spanned by a longitude range varies based on the current latitude. For example, one degree of longitude spans a distance of approximately 111 kilometers (69 miles) at the equator but shrinks to 0 kilometers at the poles.

Furthermore, MKCoordinateSpan's dimensions are measured in degrees, and 40 degrees is quite a bit so you probably want to change these values, otherwise the user will not be able to zoom in much at all.

Passing custom data with map pin segue

Subclass the "MKPointAnnotation" class and add your custom property in it.

class MyAnnotation : MKPointAnnotation {
var customProperty : String?
}

And you can use MyAnnotation instead of MKPointAnnotation. Like following

 let pointAnnotation:MyAnnotation = MyAnnotation()
pointAnnotation.coordinate = coordinates
pointAnnotation.title = storeName
pointAnnotation.subtitle = location
pointAnnotation.customProperty = "your value"
self.mapView.addAnnotation(pointAnnotation)


Related Topics



Leave a reply



Submit