Server Programmer
Server Programmer

Reputation: 309

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

Here is my setup: ViewController -> SecondViewControoler

Three Goals:

  1. Add image to custom annotation (see code below)
  2. I have a subclass named "Capital", I would like to add the image in #1 and then create additional variables to hold values that will be passed to a new SecondViewController that includes (2) labels and a Picker View: for example label1 = "text1", label2 = "text2", and then grab a string from an array that contains multiple objects (i.e. the title for each row of the Picker)
  3. Once the user taps on the callout button on the custom pin we push the ViewController to a new ViewController named "SecondViewController" and assign the values of subclass "Capital" that are attached to the custom pin that was tapped to the new labels and Picker View in the SecondViewController

Here is my code thus far:

Subclass named "Capital.swift"

import MapKit
import UIKit

class Capital: NSObject, MKAnnotation {
    var title: String?
    var coordinate: CLLocationCoordinate2D
    var info: String

    // here we would add the custom image in Goal #1
    // here we would add the (2) values for label1 and label2 in Goal #2
    // here we would add the array that contains multiple object in Goal #2

    init(title: String, coordinate: CLLocationCoordinate2D, info: String) {
        self.title = title
        self.coordinate = coordinate
        self.info = info

     // add additional lines as needed

    }
}

Here is my code for the ViewController.swift

import MapKit
import UIKit

class ViewController: UIViewController, MKMapViewDelegate {

    @IBOutlet var mapView: MKMapView!
    override func viewDidLoad() {
        super.viewDidLoad()

        let london = Capital(title: "London", coordinate: CLLocationCoordinate2D(latitude: 51.507222, longitude: -0.1275), info: "Home to the 2012 Summer Olympics.")
        let oslo = Capital(title: "Oslo", coordinate: CLLocationCoordinate2D(latitude: 59.95, longitude: 10.75), info: "Founded over a thousand years ago.")
        let paris = Capital(title: "Paris", coordinate: CLLocationCoordinate2D(latitude: 48.8567, longitude: 2.3508), info: "Often called the City of Light.")
        let rome = Capital(title: "Rome", coordinate: CLLocationCoordinate2D(latitude: 41.9, longitude: 12.5), info: "Has a whole country inside it.")
        let washington = Capital(title: "Washington DC", coordinate: CLLocationCoordinate2D(latitude: 38.895111, longitude: -77.036667), info: "Named after George himself.")

        mapView.addAnnotations([london, oslo, paris, rome, washington])
    }

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

        let identifier = "Capital"
        if annotation is Capital {
            if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) {
                annotationView.annotation = annotation
                return annotationView
            } else {
                let annotationView = MKPinAnnotationView(annotation:annotation, reuseIdentifier:identifier)
                annotationView.isEnabled = true
                annotationView.canShowCallout = true

                let btn = UIButton(type: .detailDisclosure)
                annotationView.rightCalloutAccessoryView = btn
                //annotationView.image = UIImage(named: "#imageLiteral(resourceName: ",pin,")")
            return annotationView
         }
       }
        return nil
    }

Here we add the custom callout variables that are specific to the city that was pressed and push these to the SecondViewController

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

        //Add custom image + (2) labels + and the array that contains multiple objects to be passed to the Picker 'view in the SecondViewController

        // Upon the User tapping the above button we push all the variables stored in Capital attached to the current city pin that was pressed to the new SecondViewController

        // Send the View Controller to the SecondViewController programically

        let SecondViewController = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController")
        self.show(SecondViewController!, sender: nil)       
    }
}

Here is my code for the SecondViewController

import UIKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
    @IBOutlet weak var pickerView: UIPickerView!
    var cityName = 0

//the values here are pulled from the custom pin that was pressed in the previous ViewController

var Array = ["object1 from custom pin","object2 from custom pin,","object3 from custom pin"]

@IBOutlet weak var label1: UILabel!
@IBOutlet weak var label2: UILabel!
override func viewDidLoad() {
    super.viewDidLoad()
    pickerView.delegate = self
    pickerView.dataSource = self
}

func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
    return Array[row]
}

func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
    return Array.count
}

func numberOfComponents(in pickerView: UIPickerView) -> Int {
    return 1
}

@IBAction func submit(_ sender: Any) {
    if (cityName == 0){
        label1.text = "object1 from custom pin"
    }
        else if(cityName == 1){
        label1.text = "object2 from custom pin"
    }
    else{
        label1.text = "object3 from custom pin"

continued...

    }
}

func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
    cityName = row   
    }
}

Appreciate any help

Upvotes: 1

Views: 986

Answers (3)

Chi-Hwa Michael Ting
Chi-Hwa Michael Ting

Reputation: 179

  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

}

Upvotes: 4

Cyril
Cyril

Reputation: 2818

Create a reference to the class where you want to pass data to AKA your destination class

    `let vc = self.storyboard!.instantiateViewController(withIdentifier: "Class Identifier") as! YourDestinationClass`

Then you will have access to all the objects inside YourDestinationClass by using vc

Upvotes: 0

Duncan C
Duncan C

Reputation: 131481

Working off of Rob's suggestion, but passing the Capital object in sender rather than the view, your calloutAccessoryControlTapped might look like this:

func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
    guard let capital = view.annotation as? Capital else { return }
    performSegue(withIdentifier: "segue1", sender: capital)
}

func prepare(for segue: UIStoryboardSegue, 
      sender: Any?) {
    guard let destination = segue.destination as? SecondViewController,
        let capital = sender as? Capital else { return }
    destination.capital = capital //Assuming SecondViewController has a capital property
}

Upvotes: 1

Related Questions