MapKit map returns nil inside of function being called inside of different ViewController - ios

I'm currently trying to implement a Map connected with a search function. For the overlay containing the table view, I've decided to go for a library called FloatingPanel.
I have to ViewControllers, namely MapViewController and SearchTableViewController - as the name already says, MapViewController contains a mapView. I assume since FloatingPanel adds SearchTableViewController (STVC) to MapViewController (MVC), that STVC is MVC's child.
Now whenever I want to call the MapViewController's function to add annotation inside of SearchTableViewController, MapViewController's mapView returns nil - calling it inside of MapViewController works fine.
class MapViewController: UIViewController, FloatingPanelControllerDelegate, UISearchBarDelegate {
var fpc: FloatingPanelController!
var searchVC = SearchResultTableViewController()
let locationManager = CLLocationManager()
let regionInMeters: Double = 10000
#IBOutlet private var mapView: MKMapView!
var mapItems: [MKMapItem]?
override func viewDidLoad() {
super.viewDidLoad()
checkLocationServices()
fpc = FloatingPanelController()
fpc.delegate = self
fpc.surfaceView.backgroundColor = .clear
fpc.surfaceView.cornerRadius = 9.0
fpc.surfaceView.shadowHidden = false
searchVC = (storyboard?.instantiateViewController(withIdentifier: "SearchPanel") as! SearchResultTableViewController)
fpc.set(contentViewController: searchVC)
fpc.track(scrollView: searchVC.tableView)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
fpc.addPanel(toParent: self, animated: true)
fpc.move(to: .tip, animated: true)
searchVC.searchController.searchBar.delegate = self
}
func checkLocationServices() {
if CLLocationManager.locationServicesEnabled() {
setupLocationManager()
checkLocationAuthorization()
} else {
}
}
func setupLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func checkLocationAuthorization() {
switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse:
centerViewOnUserLocation()
break
case .denied:
break
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
break
case .restricted:
break
case .authorizedAlways:
break
#unknown default:
break
}
}
func centerViewOnUserLocation() {
if let location = locationManager.location?.coordinate {
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
mapView.setRegion(region, animated: true)
}
}
#IBAction func click(_ sender: Any) {
}
func addPin(title: String, subtitle: String, coordinates: CLLocationCoordinate2D) {
let destination = customPin(pinTitle: title, pinSubTitle: subtitle, location: coordinates)
mapView.addAnnotation(destination)
}
func addAnnotationToMap() {
guard let item = mapItems?.first else { return }
guard let coordinates = item.placemark.location?.coordinate else { return }
addPin(title: item.name!, subtitle: "", coordinates: coordinates)
}
}
SearchTableViewController's function:
func passData() {
guard let mapViewController = storyboard?.instantiateViewController(withIdentifier: "map") as? MapViewController else { return }
guard let mapItem = places?.first else { return }
mapViewController.mapItems = [mapItem]
mapViewController.addAnnotationToMap()
}

This
guard let mapViewController = storyboard?.instantiateViewController(withIdentifier: "map") as? MapViewController else { return }
guard let mapItem = places?.first else { return }
creates a new seperate object other than the actual one that you have to access , use delegate to do it here
searchVC = (storyboard?.instantiateViewController(withIdentifier: "SearchPanel") as! SearchResultTableViewController)
searchVC.delegate = self // here
fpc.set(contentViewController: searchVC)
then declare
weak var delegate:MapViewController?
inside SearchViewController and use it
func passData() {
guard let mapItem = places?.first else { return }
delegate?.mapItems = [mapItem]
delegate?.addAnnotationToMap()
}

Several things:
When you use a third party library in your project and your readers might want to know about that library in order to understand your problem, you should include a link to the library in your question. I did a Google search and was able to find what I think is the correct library.
The sample code for that library has you call fpc.addPanel() in your view controller's viewDidLoad(), not in viewDidAppear().
The viewDidLoad() function is only called once in the lifetime of a view controller, but viewDidAppear() is called every time a view controller get's re-shown (like when it is redisplayed after being covered by a modal and then uncovered again.) The two are not interchangeable for that reason. I suggest moving that call back to viewDidLoad().
Next, as others have mentioned, your SearchTableViewController's passData() function is wrong. It creates a new, throw-away MapViewController every time it is called. It does not talk to the hosting MapViewController at all.
You should refactor your viewDidLoad() to set up the MapViewController as the delegate of the SearchTableViewController.
Define a protocol (possibly in a separate file
protocol SearchTableViewControllerDelegate {
var mapItems: [MapItem] //Or whatever type
func addAnnotationToMap()
}
Some changes to MapViewController
class MapViewController:
SearchTableViewControllerDelegate,
UIViewController,
FloatingPanelControllerDelegate,
UISearchBarDelegate {
//Your other code...
}
override func viewDidLoad() {
super.viewDidLoad()
checkLocationServices()
fpc = FloatingPanelController()
fpc.delegate = self
fpc.surfaceView.backgroundColor = .clear
fpc.surfaceView.cornerRadius = 9.0
fpc.surfaceView.shadowHidden = false
searchVC = (storyboard?.instantiateViewController(withIdentifier: "SearchPanel") as! SearchResultTableViewController)
//--------------------------
searchVC.delegate = self //This new line is important
//--------------------------
fpc.set(contentViewController: searchVC)
fpc.track(scrollView: searchVC.tableView)
fpc.addPanel(toParent: self) //Probably can't be animated at this point
}
And in SearchTableViewController:
class SearchTableViewController: UITableViewController, <Other protocols> {
weak var delegate: SearchTableViewControllerDelegate?
// other code...
func passData() {
guard let mapItem = places?.first else { return }
delegate?.mapItems = [mapItem]
delegate?.addAnnotationToMap()
}
}

You're instantiating a new MapViewController instead if passing data to the one that exists. There are three ways to do this:
Delegation
Closures
Notifications
An example using a closure, in SearchViewController:
var passData: ((MKMapItem) -> ())?
In MapViewController provide it a closure:
searchVC = (storyboard?.instantiateViewController(withIdentifier: "SearchPanel") as! SearchResultTableViewController)
searchVC.passData = { mapItem in
self.mapItems = [mapItem]
}
In SearchViewController call the closure:
passData?(mapItem)

Related

annotations not appearing on MapKit

Has anyone experienced opening a map in your app and although it shows the area you are in it doesn't show the blue dot. Also I'm using localSearch to find supermarkets in my app. When you originally open the map it shows nothing. When you close and open the app again the annotations magically appear. Am I missing something really obvious?
Also I am receiving these errors in the console:
Wrapped around the polygon without finishing... :-(
List has 10 nodes:
7 8 9 10 4 3 0 1 2 6
2022-02-20 17:55:28.093927+0900 GoferList[15460:5478085] [VKDefault] Building failed to triangulate!
2022-02-20 17:55:28.265877+0900 GoferList[15460:5478094] [Font] Failed to parse font key token: hiraginosans-w6
Here is the view controller I'm using:
import CoreLocation
import MapKit
import UIKit
class MapViewController: UIViewController {
static func createViewController() -> MapViewController? {
UIStoryboard(name: "Main", bundle: Bundle.main)
.instantiateInitialViewController() as? MapViewController
}
#IBOutlet private var mapView: MKMapView!
private let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
configureLocationManager()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
locationManager.startUpdatingLocation()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
locationManager.stopUpdatingLocation()
}
}
// MARK: - Location Manager
extension MapViewController: CLLocationManagerDelegate {
func configureLocationManager() {
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
}
func locationManager(
_ mgr: CLLocationManager,
didUpdateLocations locations: [CLLocation]
) {
guard let location = locations.first else {
return
}
let region = MKCoordinateRegion(
center: location.coordinate,
latitudinalMeters: 1000,
longitudinalMeters: 1000
)
mapView.setRegion(region, animated: false)
findPOIPlaces(query: "supermarket", region: region)
mgr.stopUpdatingLocation()
}
}
// MARK: - MapViewDelegate
extension MapViewController: MKMapViewDelegate {
func findPOIPlaces(query: String, region: MKCoordinateRegion) {
let searchRequest = MKLocalSearch.Request()
searchRequest.naturalLanguageQuery = query
searchRequest.resultTypes = .pointOfInterest
searchRequest.region = region
let search = MKLocalSearch(request: searchRequest)
search.start { [weak self] res, error in
guard let res = res,
let self = self,
error == nil
else {
print("\(String(describing: error))")
return
}
for item in res.mapItems {
let annotation = SuperMarketAnnotation(item: item)
self.mapView.addAnnotation(annotation)
}
}
}
func mapView(
_ mapView: MKMapView,
viewFor annotation: MKAnnotation
) -> MKAnnotationView? {
guard annotation is SuperMarketAnnotation else {
return nil
}
if let view = mapView.dequeueReusableAnnotationView(
withIdentifier: SuperMarketAnnotationView.reuseID)
{
return view
}
let annotationView = SuperMarketAnnotationView(annotation: annotation)
annotationView.delegate = self
return annotationView
}
}
// MARK: - SuperMarketAnnotationViewDelegate
extension MapViewController: SuperMarketAnnotationViewDelegate {
func onTapPin(annotation: SuperMarketAnnotation) {
mapView.setCenter(annotation.coordinate, animated: true)
}
func onTapInfo(annotation: SuperMarketAnnotation) {
if let url = annotation.webURL {
// if place has an web site
if let vc = WebViewController.createViewController() {
vc.url = url
vc.name = annotation.title
present(vc, animated: true, completion: nil)
}
} else if let url = annotation.externalURL {
UIApplication.shared.open(
url, options: [:],
completionHandler: nil
)
}
}
}
Can anyone help? Thank you.
A few observations:
Regarding not seeing the blue dot, the question is whether you set showsUserLocation (either programmatically or in the NIB/storyboard).
Regarding annotations not showing up, you need to narrow down the problem. I would temporarily remove the mapView(_:viewFor:) and see whether you see annotations at that point.
If they do, then the problem is either in mapView(_:viewFor:) (see below) or your custom annotation view subclass.
If they do not appear, then the problem is either in your logic of how/when you added annotation or in your custom annotation class.
With mapView(_:viewFor:) removed, you could then try adding a MKPointAnnotation, rather than your SuperMarketAnnotation. That will help you figure out whether the problem is in your annotation class or the the logic about when and where you add annotations.
FWIW, the mapView(_:viewFor:) has a little bug (which may or may not be related). If you successfully dequeue an existing annotation view, you just return it and never update its annotation property. You must update the annotation of any dequeued annotation view or else it will point to the old annotation (and, specifically, its old coordinate).
if let view = mapView.dequeueReusableAnnotationView(withIdentifier: SuperMarketAnnotationView.reuseID) {
view.annotation = annotation // make sure you update the annotation for dequeued annotation view
return view
}

UI Tableview won't display or update search results in cells, Swift

I am an intermediate Swift developer and I am creating an app that involves using a search function to find an address and pinpoint said address on a map. I followed a tutorial on how to achieve this and everything is functional besides the search function itself. Whenever I type in the UIsearchbar my search results tableview controller is instantiated, however, the table view is blank and does not update as I type. An address API call should be present.
Below is my code for the search table
import UIKit
import MapKit
class LocationSearchTable : UITableViewController {
var resultSearchController:UISearchController? = nil
var handleMapSearchDelegate:HandleMapSearch? = nil
var matchingItems:[MKMapItem] = []
var mapView: MKMapView? = nil
#IBOutlet var searchTableView: UITableView!
}
extension LocationSearchTable : UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
searchController.showsSearchResultsController = true
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
guard let mapView = mapView,
let searchBarText = searchController.searchBar.text else { return }
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = searchBarText
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.start { response, _ in
guard let response = response
else {
return
}
self.matchingItems = response.mapItems
self.tableView.reloadData()
}
}
}
extension LocationSearchTable {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return matchingItems.count
}
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
let selectedItem = matchingItems[indexPath.row].placemark
cell.textLabel?.text = selectedItem.name
cell.detailTextLabel?.text = ""
return cell
}
}
extension LocationSearchTable {
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedItem = matchingItems[indexPath.row].placemark
handleMapSearchDelegate?.dropPinZoomIn(placemark: selectedItem)
dismiss(animated: true, completion: nil)
}
}
This is my code for my initial view controller
import UIKit
import MapKit
import CoreLocation
protocol HandleMapSearch {
func dropPinZoomIn(placemark:MKPlacemark)
}
//for some reason my location delegate is not working (resolved)
class locationViewController: UIViewController, UISearchResultsUpdating, UISearchBarDelegate {
func updateSearchResults(for searchController: UISearchController) {
return
}
var selectedPin:MKPlacemark? = nil
var resultSearchController:UISearchController? = nil
#IBOutlet weak var eventLocationMapView: MKMapView!
#IBOutlet weak var backButton: UIButton!
let locationManager = CLLocationManager()
let regionInMeters: Double = 10000
override func viewDidLoad() {
super.viewDidLoad()
resultSearchController?.searchResultsUpdater = self
let locationSearchTable = storyboard!.instantiateViewController(withIdentifier: "LocationSearchTable") as! LocationSearchTable
resultSearchController = UISearchController(searchResultsController: locationSearchTable)
resultSearchController?.searchResultsUpdater = locationSearchTable
resultSearchController?.searchBar.delegate = self
locationSearchTable.mapView = eventLocationMapView
let searchBar = resultSearchController!.searchBar
searchBar.sizeToFit()
searchBar.placeholder = "Search for places"
//this is the search bar
navigationItem.titleView = resultSearchController?.searchBar
resultSearchController?.hidesNavigationBarDuringPresentation = false
resultSearchController?.obscuresBackgroundDuringPresentation = true
definesPresentationContext = true
locationSearchTable.handleMapSearchDelegate = self
//all of this is in regards to the search functionality
locationManager.startUpdatingLocation()
self.checkLocationAuthorization()
self.checkLocationServices()
self.navigationController?.isNavigationBarHidden = false
// Do any additional setup after loading the view.
locationManager.requestWhenInUseAuthorization()
//this pushes the user request. The issue was most likely in the switch statement
locationManager.delegate = self
locationManager.requestLocation()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
//solved it but we will probably have to go back to this
self.centerViewOnUserLocation()
}
#IBAction func backButtonTapped(_ sender: Any) {
self.transitionBackToCreateEventVC()
}
func transitionBackToCreateEventVC (){
let createEventViewController = self.storyboard?.instantiateViewController(identifier: "createEventVC")
self.view.window?.rootViewController = createEventViewController
self.view.window?.makeKeyAndVisible()
}
func centerViewOnUserLocation() {
if let location = locationManager.location?.coordinate {
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
eventLocationMapView.setRegion(region, animated: true)
//this function centers the map onto the location of the user
}
}
func setUpLocationManager(){
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func checkLocationServices(){
if CLLocationManager.locationServicesEnabled(){
setUpLocationManager()
checkLocationAuthorization()
} else {
//show alert letting the user know they have to turn this on
}
}
func checkLocationAuthorization() {
let locationManager = CLLocationManager()
switch locationManager.authorizationStatus {
case .authorizedWhenInUse:
eventLocationMapView.showsUserLocation = true
// We want the users location while the app is in use
break
case .denied:
//show alert instructing how to turn on permissions
break
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
return
case .restricted:
//show an alert
break
case .authorizedAlways:
// we don't want this
break
#unknown default:
return
//I dont know if we need this code
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
extension locationViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
locationManager.requestLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.first {
let region = MKCoordinateRegion(center: location.coordinate, latitudinalMeters: 0.05 , longitudinalMeters: 0.05)
eventLocationMapView.setRegion(region, animated: true)
}
}
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("error:: \(error)")
}
}
extension locationViewController: HandleMapSearch {
func dropPinZoomIn(placemark:MKPlacemark){
// cache the pin
selectedPin = placemark
// clear existing pins
eventLocationMapView.removeAnnotations(eventLocationMapView.annotations)
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.coordinate
annotation.title = placemark.name
if let city = placemark.locality,
let state = placemark.administrativeArea {
annotation.subtitle = "\(city) \(state)"
}
eventLocationMapView.addAnnotation(annotation)
let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
let region = MKCoordinateRegion(center: placemark.coordinate, span: span)
eventLocationMapView.setRegion(region, animated: true)
}
}
I think the you should make a property to hold the MKLocalSearch object in your LocationSearchTable class.
class LocationSearchTable : UITableViewController {
private var search: MKLocalSearch?
// ...
}
extension LocationSearchTable : UISearchResultsUpdating {
func updateSearchResultsForSearchController(searchController: UISearchController) {
guard let mapView = mapView,
let searchBarText = searchController.searchBar.text else { return }
// if there is an ongoing search, cancel it first.
self.search?.cancel()
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = searchBarText
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.start { response, error in
guard let response = response
else {
return
}
self.matchingItems = response.mapItems
self.tableView.reloadData()
}
// make sure `search` is not released.
self.search = search
}
}
The problem of the original code is that, the search object, instance of MKLocalSearch class will be released when finishes executing updateSearchResultsForSearchController method, since there is no strong reference to it, and start's callback will never be called, so your table view will never be reloaded.
What we do is just make a strong ref to it, and make sure it is not released before completion handler is called.

use popToRootViewController and pass Data

I'm applying for a junior developer position and I've got a very specific task, that already took me 3 days to complete. Sounds easy - pass data to rootViewController.
That's what I've done:
1)
private func userDefaultsToRootController() {
let input = textField.text!
defaults.set(input, forKey: "SavedLabel")
navigationController?.popViewController(animated: true)
}
private func segueToRootViewController() {
let destinationVC = MainScreen1()
let input = textField.text!
if input == "" { self.navigationController?.popToRootViewController(animated: true) }
destinationVC.input = input
navigationController?.pushViewController(destinationVC, animated: true)
}
private func popToNavigationController() {
let input = textField.text!
if let rootVC = navigationController?.viewControllers.first as? MainScreen1 {
rootVC.input = input
}
navigationController?.popToRootViewController(animated: true)
}
I've used CoreData
But here is the difficult part - I've got an email, that all these methods are not good enough and I need to use delegate and closure. I've done delegation and closures before, but when I popToRootViewController delegate method passes nil. Could you at least point where to find info about this?
** ADDED **
There are 2 View Controllers: Initial and Second one.
That's what I have in the Initial View Controller:
var secondVC = MainScreen2()
override func viewDidLoad() {
super.viewDidLoad()
secondVC.delegate = self
}
That's how I push SecondViewController
#objc private func buttonTapped(_ sender: CustomButton) {
let nextViewController = MainScreen2()
navigationController?.pushViewController(nextViewController, animated: true)
}
In SecondViewController I've got this protocol
protocol PassData {
func transferData(text: String)
}
Also a delegate:
var delegate: PassData?
This is how I go back to initial view controller
#objc private func buttonTapped(_ sender: CustomButton) {
if let input = textField.text {
print(input)
self.delegate?.transferData(text: input)
self.navigationController?.popToRootViewController(animated: true)
}
}
Back to the Initial view controller where I've implemented delegate method
extension MainScreen1: PassData {
func transferData(text: String) {
print("delegate called")
label.text = text
}
}
Delegate doesn't get called.
BASED ON YOUR EDIT:
You must set the delegate in buttonTapped
#objc private func buttonTapped(_ sender: CustomButton) {
let nextViewController = MainScreen2()
nextViewController.delegate = self // HERE WHERE YOU SET THE DELEGATE
navigationController?.pushViewController(nextViewController, animated: true)
}
You can delete the second instance and your code in viewDidLoad. That's not the instance you push.
This should point you in the right direction to use delegation and completion handler.
protocol YourDelegateName {
func passData(data:YourDataType)
}
class SecondViewController: UIViewController {
var delegate: YourDelegateName?
func passDataFromSecondViewController(){
YourCoreDataClass.shared.getCoreData { (yourStringsArray) in
self.delegate?.passData(data: yourStringsArray)
self.navigationController?.popToRootViewController(animated: true)
}
}
class InitialViewController: UIViewController, YourDelegateName {
override func viewDidLoad() {
super.viewDidLoad()
// or whenever you instantiate your SecondViewController
let secondViewController = SecondViewController()
secondViewController.delegate = self //VERY IMPORTANT, MANY MISS THIS
self.navigationController?.pushViewController(createVC, animated: true)
}
func passData(data:YourDataType){
//user your data
}
}
class YourCoreDataClass: NSObject {
static let shared = YourCoreDataClass()
func getCoreData (completion: ([String]) -> ()){
........... your code
let yourStringsArray = [String]() // let's use as example an array of strings
//when you got the data your want to pass
completion(yourStringsArray)
}
}

I can't execute functions from a ViewController in an different one

Can someone help me execute functions from one VC in another VC.
The function from the first VC needs to be executed once I press a button in the second VC.
Im trying with "viewcontroller().function()" function but it's not working properly, printing and basic stuff works but when it comes to stuff like drawing direction it's not working.
The function that draws directions is:
func directionToPin() {
guard let currentPlacemark = currentPlacemark else {
print("Error, the current Placemark is: \(self.currentPlacemark)")
return
}
let directionRequest = MKDirections.Request()
let destinationPlacemark = MKPlacemark(placemark: currentPlacemark)
directionRequest.source = MKMapItem.forCurrentLocation()
directionRequest.destination = MKMapItem(placemark: destinationPlacemark)
directionRequest.transportType = .walking
//calculate route
let directions = MKDirections(request: directionRequest)
directions.calculate{ (directionsResponse, error) in
guard let directionsResponse = directionsResponse else {
if let error = error {
print("error getting directions: \(error.localizedDescription)")
}
return
}
let route = directionsResponse.routes[0]
if self.drawedDriection == false {
self.drawedDriection = true
if self.didSelectAnnotation == true {
self.mapView.addOverlay(route.polyline, level: .aboveRoads)self.navigationBarController.directionButtonOutlet.setImage(UIImage(named: "navigationBarDirectionButtonRed")?.withRenderingMode(.alwaysOriginal), for: .normal)
self.mapView.setRegion(MKCoordinateRegion(routeRect), animated: true)
}
} else {
self.drawedDriection = false
self.mapView.removeOverlays(self.mapView.overlays)
if self.didSelectAnnotation == true {
self.navigationBarController.directionButtonOutlet.setImage(UIImage(named: "navigationBarDirectionButtonBlue")?.withRenderingMode(.alwaysOriginal), for: .normal)
} else {
self.navigationBarController.directionButtonOutlet.setImage(UIImage(named: "navigationBarDirectionButtonGray")?.withRenderingMode(.alwaysOriginal), for: .normal)
}
}
}
}
I'm calling the function in the second VC once I press a button:
#IBAction func directionButton(_ sender: Any) {
MapViewController().directionToPin()
}
When I run the app and press the button the currentPlacemark is nil, if I run the same function via a button in my first VC (the VC with the directionToPin function inside)
here is my repo if you need it: https://github.com/octavi42/xCodeMapsApp
Thanks!
I think that you need to use Protocols and Delegates to achieve what you desire.
#IBAction func directionButton(_ sender: Any) {
MapViewController().directionToPin()
}
In the above code snippet, you are instantiating a new instance of MapViewController which upon initialization resets currentPlacemark and hence you've encountered nil.
My suggestion is to create a new protocol to communicate from MapViewController to CardViewController just like this
Add these in MapViewController.swift
protocol MapNavigationDelegate: AnyObject {
func didTapDirectionButton()
}
class MapViewController: UIViewController {
// .... Some code ....
override func viewDidLoad() {
// . .... Some more code .......
navigationBarController.mapNavigationDelegate = self
}
}
extension MapViewController: MapNavigationDelegate {
func didTapDirectionButton() {
self.directionToPin()
}
}
Add these in CardViewController.swift
class CardViewController: UIView {
// .... Some Code ....
weak var mapNavigationDelegate: MapNavigationDelegate!
#IBAction func directionButton(_ sender: Any) {
self.mapNavigationDelegate.didTapDirectionButton()
}
}

Delegate won't work using three view controllers

Is use delegate to pass data between three view controllers. I set delegate for both view controllers I want to pass data from to self inside of MapViewController - MapViewController shall receive data from both VCs.
I also added weak var delegate: MapViewController? to both VCs - but somehow it's only working for one of them.
MapViewController:
var newStartItem: MKMapItem?
override func viewDidLoad() {
super.viewDidLoad()
searchVC = (storyboard?.instantiateViewController(withIdentifier: "SearchPanel") as! SearchResultTableViewController)
searchVC.delegate = self
newVC = (storyboard?.instantiateViewController(withIdentifier: "newLocation") as! NewLocationTableViewController)
newVC.delegate = self
...
}
func addStartAnnotationToMap() {
guard let item = newStartItem else { return }
guard let coordinates = item.placemark.location?.coordinate else { return }
addPin(title: item.name!, subtitle: "", coordinates: coordinates)
}
}
NewLocationViewController: (this one is not working)
class NewLocationTableViewController: UIViewController {
weak var delegate: MapViewController?
...
func passData() {
guard let mapItem = starts?.first else { return }
delegate?.newStartItem = mapItem
delegate?.addStartAnnotationToMap()
}
Xcode won't return any errors either!
You should declare a protocol:
protocol MapViewControllerDelegate: class {
func addStartAnnotationToMap()
}
Then change your delegate variable to:
weak var delegate: MapViewControllerDelegate?
And make MapViewController conform to MapViewControllerDelegate protocol like this:
class MapViewController: UIViewController, MapViewControllerDelegate {
Your delegate must be a reference from the type of your protocol
In this case: weak var delegate: MapViewControllerDelegate?
For this
newVC = (storyboard?.instantiateViewController(withIdentifier: "newLocation") as! NewLocationTableViewController)
newVC.delegate = self
and
weak var delegate: MapViewController?
to work you need to verify that at some point inside the map vc you present that location vc like
self.present(newVC,animated:true,completion:nil)
or push it , plus print it to verify it's not nil and guard doesn't return if starts is empty
func passData() {
print("delegate \(delegate) item \(starts?.first)")
guard let mapItem = starts?.first else { return }
delegate?.newStartItem = mapItem
delegate?.addStartAnnotationToMap()
}

Resources