1

I am new to Swift and iOS development. I have an issue where I have three viewControllers connected via a tab bar navigation controller. The first viewController is empty for now and not in use. In the secondViewController I update the UI with data from a weather API. In the thirdViewController I have a map. When I access the map I need the coordinates to update the url in the secondViewController.

What I'm trying to achieve is retrieving the current latitude and longitude in the thirdViewController (no problem), but I'm unable to send these values through a delegate in order to trigger the function in the secondViewController and thus update the variables in there. The delegate function in the secondViewController just doesn't get triggered. What am I doing wrong? Or is it even possible to do this way?

The code is as follows for both viewControllers:

secondViewController (this is where I need the values from the map in thirdViewController)

import UIKit

class SecondViewController: UIViewController, MapViewControllerDelegate {

    var forecastData: [ForecastData] = []
    var currentLat: String = "59.911166"
    var currentLon: String = "10.744810"
    
    @IBOutlet weak var tableView: UITableView!
    
    var weatherManager = WeatherManager()
    var mapViewController = ThirdViewController()
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        mapViewController.delegate = self
        weatherManager.delegate = self
        weatherManager.fetchWeather(latitude: currentLat, longitude: currentLon)
        tableView.backgroundView = UIImageView(image: UIImage(named: "background"))
        tableView.dataSource = self
        tableView.register(UINib(nibName: "WeatherCell", bundle: nil), forCellReuseIdentifier: "WeatherCell")
        
    }
    
    func viewDidAppear() {
        weatherManager.fetchWeather(latitude: currentLat, longitude: currentLon)
        
    }
    
    
    // THIS FUNCTION SHOULD BE TRIGGERED BUT DOESN'T
    
    func mapViewController(latitude: String, longitude: String) {
        currentLat = latitude
        currentLon = longitude
        print(currentLat)
        print(currentLon)
        print("DELEGATE METHOD TRIGGERED")
    }

}

//MARK: - WeatherManagerDelegate

extension SecondViewController: WeatherManagerDelegate {
    
    func didUpdateWeather(_ weatherManager: WeatherManager, weather: WeatherModel) {
        
        forecastData.append(ForecastData(timespan: "Nå", precipitation: "", tempOrWeather: "Temperatur", tempWeatherLabel: "\(weather.temperatureNow)" + " " + "\(weather.tempUnits)"))
        
        forecastData.append(ForecastData(timespan: "Neste Time", precipitation: "\(weather.precipitationNext1H)" + " " + "\(weather.precipUnits)", tempOrWeather: "vær", tempWeatherLabel: String(weather.conditionNext1H) ))
        
        forecastData.append(ForecastData(timespan: "Neste 6 timer", precipitation: "\(weather.precipitationNext6H)" + " " + "\(weather.precipUnits)", tempOrWeather: "vær", tempWeatherLabel: String(weather.conditionNext6H)))
        
        forecastData.append(ForecastData(timespan: "Neste 12 timer", precipitation: "", tempOrWeather: "vær",  tempWeatherLabel: String(weather.conditionNext12H)))
        
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
        
    }
    
    func didFailWithError(error: Error) {
        print(error)
    }
}

extension SecondViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return forecastData.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "WeatherCell", for: indexPath)
        as! WeatherCell
        cell.TimeframeLabel.text = forecastData[indexPath.row].TimeframeLabel
        cell.TempLabel.text = forecastData[indexPath.row].TempLabel
        cell.WeatherCodeLabel.text = forecastData[indexPath.row].WeatherCodeLabel
        cell.PrecipitationLabel.text = forecastData[indexPath.row].PrecipitationLabel
        return cell
    }
}

thirdViewController (this is where I try to pass the coordinates values through the delegate)

import UIKit
import MapKit
import CoreLocation

protocol MapViewControllerDelegate {
    func mapViewController(latitude: String, longitude: String)
}

class ThirdViewController: UIViewController, MKMapViewDelegate {
    
    var currentLat = ""
    var currentLon = ""
    @IBOutlet weak var mMapView: MKMapView!
    fileprivate let locationManager: CLLocationManager = CLLocationManager()
    var delegate: MapViewControllerDelegate?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        locationManager.requestWhenInUseAuthorization()
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.distanceFilter = kCLDistanceFilterNone
        locationManager.startUpdatingLocation()
        
        mMapView.showsUserLocation = true
               
        currentLat = String((locationManager.location?.coordinate.latitude)!)
        currentLon = String((locationManager.location?.coordinate.longitude)!)
        
        print(currentLat)
        print(currentLon)
        
        //delegate?.mapViewController(latitude: "TESTlon", longitude: "TESTlati")
    }
    
    override func viewDidAppear(_ animated: Bool) {
        updateWeatherView()
    }
    
    // THIS SHOULD TRIGGER THE DELEGATE FUNCTION IN SECOND VIEW CONTROLLER AND PASS THE DATA ALONG
    func updateWeatherView() {
        delegate?.mapViewController(latitude: "TESTlon", longitude: "TESTlati")
    }
    
}


I have tried different thing, but nothing seems to work, the delegate function in secondViewController just doesn't get triggered. Again, I am very new to this so maybe it's a simple thing. Thanks in advance for any help! Also, here is a screenshot of my storyboard if it can help:

Screenshot of storyboard

1 Answers1

2

As @Duncan C said, doing

var weatherManager = WeatherManager()
var mapViewController = ThirdViewController()

just creates a new instance of those view controllers -- these are completely unrelated to the ones you already have. Sure, if you do mapViewController.delegate = self, you are setting mapViewController's delegate... but mapViewController is not the view controller that you want.

To get the view controller that you want, you can access the tab bar controller's view controllers (these are automatically created for you when you make them in the storyboard), and pick the right one:

if 
    let viewControllers = self.tabBarController?.viewControllers, 
    viewControllers.count >= 3,
    let thirdViewController = viewControllers[2] as? ThirdViewController /// [2] because that's the third view controller in the array, which is the one you want
{
    thirdViewController.delegate = self
}

self.tabBarController could be nil, so you need to unwrap it. Then, you should make sure that there are more than 3 view controllers. Finally, you need to cast the view controller you need (self.tabBarController?.viewControllers[2]) to ThirdViewController. You can then set the delegate to that view controller.

aheze
  • 24,434
  • 8
  • 68
  • 125
  • 1
    As a minor note, the code above will crash if the tab bar controller only contains 2 items. It would be safer to break the if statement into multiple parts where it unwaps the tabBarController and fetches its viewControllers property, verify that it contains at least 3 items, then extracts the 3rd item and tries to cast it to a `ThirdViewController`. Given that the setup of the tab bar controller is determined at compile time you could argue that letting it crash with an array out of bounds error is fine, since you'd realize immediately if you broke it. – Duncan C Oct 30 '20 at 12:43
  • 2
    Rewriting it for safety, the if statement might look like this: ```if let viewControllers = self.tabBarController?.viewControllers, viewControllers.count >=3, let thirdViewController = viewControllers[2] as? ThirdViewController``` – Duncan C Oct 30 '20 at 12:45