Доступ к @IBOutlet из другого класса

#ios #swift #cocoa-touch #uiviewcontroller #iboutlet

#iOS #swift #cocoa-touch #uiviewcontroller #iboutlet

Вопрос:

В настоящее время я создаю приложение для отслеживания маршрутов, чтобы научить себя быстрому. У меня была базовая функциональность, работающая с отслеживаемым маршрутом и полилиниями, которые рисовались из одного класса SessionController, но я хотел разделить класс на отдельные объекты.

Вот тут у меня возникли проблемы. Я поместил весь код, который обновляет местоположение и рисует полилинии и т.д., В его собственный класс под названием MapView.swift и оставил @IBOutlets для кнопок, MapView и т.д. В SessionController.swift но теперь я не могу получить доступ к @IBOutlet для MapView, чтобы разрешить новому классу MapView.swift обновлять текущее местоположение, полилинии обновления и т.д.

Когда я пытаюсь ctrl перетащить MapView @IBOutlet в MapView.swift ViewController, ничего не происходит.

Итак, я спрашиваю, как мне связать два класса, чтобы разрешить доступ к MapView в SessionController для обновления текущего местоположения.

Я просматриваю протоколы и делегатов, но я не слишком уверен, как их реализовать, или даже если это правильный путь.

Код для SessionController можно увидеть ниже:

 class SessionController: UIViewController 
{

    // Creates an outlet link to the corrosponding interfaces on the storyBoard
    @IBOutlet weak var mapView: MKMapView!
    @IBOutlet weak var startButton: UIButton!
    @IBOutlet weak var stopButton: UIButton!

    var mapScreen: MapView! = nil

    // Had to change from viewOnLoad so that the custom alert class could be utilised.
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidLoad()
        startButton.layer.cornerRadius = startButton.frame.size.height/2
        stopButton.layer.cornerRadius = stopButton.frame.size.height/2

        mapScreen = (storyboard?.instantiateViewController(withIdentifier: "mapScreen") as! MapView)

        // does the initial check whether the location services are enabled
        mapScreen.checkLocationServices()
    }

    // ends the current session, resets buttons, stops updating location and changes to history tab.
    func endSession() {
        stopButton.isHidden = true
        startButton.isHidden = false
        mapScreen.stopUpdatingLocation()
    }

    // action for when the start button is presssed.
    @IBAction func startButtonPressed(_ sender: Any) {
        mapScreen!.startUpdatingLocation()
        startButton.isHidden = true
        stopButton.isHidden = false
    }

    // action for when the stop button is pressed.
    @IBAction func stopButtonPressed(_ sender: Any) {
        presentAlertWithTitle(title: "End Session?", message: "Do you wish to end your session?", options: "Cancel", "Yes") { (option) in
            switch(option) {
            case 0:
                break
            case 1:
                self.endSession()
            default:
                break
            }
        }
    }
}
  

Код для MapView можно увидеть ниже:

     class MapView: UIViewController 
    {

        let locationManager = CLLocationManager()
        var myLocations: [CLLocation] = []
        let regionInMeters: Double = 500
        var mapView: MKMapView!

        //  Checks the users location services are enabled otherwise give them an alert.
        func checkLocationServices() {
            if CLLocationManager.locationServicesEnabled() {
                setupLocationManager()
                checkLocationAuthorisation()
            } else {
                // show an alert instructing on how to enable location services.
                presentAlertWithTitle(title: "Location Services Disabled", message: "Please enable your location services by navigating to Settings/Privacy/Location Services and turning on.", options: "OK") { _ in }
            }
        }

        // Setup the location manager
        func setupLocationManager() {
            locationManager.delegate = self
            locationManager.desiredAccuracy = kCLLocationAccuracyBest
        }

        // Checks whether the user has authorised location tracking via permissions.
        func checkLocationAuthorisation() {
            switch CLLocationManager.authorizationStatus() {
            case .authorizedWhenInUse:
                centerViewOnUserLocation()
            case .denied:
                // show alert detailing that the user has denied access to the location services.
                presentAlertWithTitle(title: "Access Denied", message: "Request of access has been denied to the location services", options: "OK") { _ in }
            case .notDetermined:
                locationManager.requestWhenInUseAuthorization()
            case .restricted:
                // show an alert instructing that the location services are restricted i.e. child account
                presentAlertWithTitle(title: "Access Restricted", message: "Access has been restricted to the location services", options: "OK") { _ in }
                break
            case .authorizedAlways:
                break
            }
        }

        // Centers the position onto the at the specified height by using the regionInMeters variable
        func centerViewOnUserLocation() {
            if let location = locationManager.location?.coordinate {
                let region = MKCoordinateRegion.init(center: location, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
                mapView.setRegion(region, animated: true)
            }
        }

        func updatePolyLine() {
            if (myLocations.count > 1){
                // get my old location
                let sourceIndex = myLocations.count - 1
                // get the new location
                let destinationIndex = myLocations.count - 2

                // get the coordinates for both the old and the new locations
                let sourceIndexCoordinate = myLocations[sourceIndex].coordinate
                let destinationIndexCoordinate = myLocations[destinationIndex].coordinate

                // put these coordinates in to a new array so that we can get a reference to a pointer so that the MKPolyline can make use of it's position in the registry.
                var sourcePlusDestination = [sourceIndexCoordinate, destinationIndexCoordinate]

                // pass the reference of the array pointer into the constructor of MKPolyline so that a line can be drawn between the two points.
                let polyline = MKPolyline(coordinates: amp;sourcePlusDestination, count: sourcePlusDestination.count)

                // adds and then updates the polyline on the map.
                mapView.addOverlay(polyline)
            }
        }

        func startUpdatingLocation()
        {
            locationManager.startUpdatingLocation()
        }

        func stopUpdatingLocation()
        {
            locationManager.stopUpdatingLocation()
            tabBarController?.selectedIndex = 1

            for overlay in mapView.overlays {
                mapView.removeOverlay(overlay)
            }
        }
    }

// an extension for SessionController that uses delegates to listen for changes in the location and authorisation
extension MapView: CLLocationManagerDelegate 
{

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) 
    {
        guard let location = locations.last else { return }
        let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
        let region = MKCoordinateRegion.init(center: center, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
        mapView.setRegion(region, animated: true)

        myLocations.append(locations[0] as CLLocation)
        updatePolyLine()
    }

    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) 
    {
        checkLocationAuthorisation()
    }
}

extension MapView: MKMapViewDelegate 
{

    func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer 
    {
        if overlay is MKPolyline 
        {
            let polylineRenderer = MKPolylineRenderer(overlay: overlay)
            polylineRenderer.strokeColor = UIColor.blue
            polylineRenderer.lineWidth = 4
            return polylineRenderer
        }
        return MKPolygonRenderer()
    }
}
  

Комментарии:

1. Неясно, о чем вы спрашиваете.

2. Ваша идея протокола / делегирования кажется разумной, за исключением: почему ваш MapView класс должен быть UIViewController ?

3. Честно говоря, я не слишком уверен! Какой была бы рекомендация?

Ответ №1:

Я немного сбит с толку вашим кодом и вопросом, но, надеюсь, это может направить вас по правильному пути…

Вы создаете свой MapScreen здесь:

 mapScreen = (storyboard?.instantiateViewController(withIdentifier: "mapScreen") as! MapView)
  

но никогда не устанавливайте это mapView свойство таким образом:

 mapScreen = (storyboard?.instantiateViewController(withIdentifier: "mapScreen") as! MapView)
mapScreen.mapView = mapView
  

Также похоже, что вы также хотите установить делегат вашего MKMapView для MapScreen, учитывая, что вы его реализовали ( extension MapView: MKMapViewDelegate )

например

 mapScreen = (storyboard?.instantiateViewController(withIdentifier: "mapScreen") as! MapView)
mapScreen.mapView = mapView
mapView.delegate = mapScreen
  

Кроме того, я думаю, вам нужна слабая ссылка на MKMapView inside MapView .

Я также не вижу, как вы представляете mapScreen , вы, кажется, создаете его экземпляр, но на самом деле не представляете его. Я думаю, что скриншот вашей раскадровки или еще какой-то части вашего кода может помочь.

Комментарии:

1. Хотя это останавливает выдачу любых исключений, местоположение не обновляется. Начальный центр в положении пользователя работает, но не обновляется при перемещении

2. @Stimo кое-что добавил к моему ответу. Я не вижу, где представлен ваш mapScreen