简体   繁体   中英

Add Maker at current location via Button in Mapbox on iOS

I am trying to build an app where I can add a marker to the map by pressing a button. I could already achieve that by pressing the button the current location and a default title are written into an array as type MGLPointAnnotation . But I struggle with the visualization of the new annotation. I always get the error

Type of expression is ambiguous without more context

Here is my code so far:

func makeEntry(){
        guard let locValue: CLLocationCoordinate2D = locationManager.location?.coordinate else { return }

        locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
        locationManager.startUpdatingLocation()

        let point = Annotation()
        point.coordinate = CLLocationCoordinate2D(latitude: (locValue.latitude), longitude: (locValue.longitude))
        point.title = "Entry"
        pointAnnotations.append(point)
        mapView.addAnnotations(pointAnnotations) <-- triggers the error waring
        }

I am a complete newbie with Swift and hope that you can help me.

Edit:

Here is the rest of the core functions of the app:

func addButton(){
        entryButton = UIButton(frame: CGRect(x: (view.frame.width/2)-100 , y: (view.frame.height)-75, width: 200, height: 50))
        entryButton.setTitle("Neuer Eintrag", for: .normal)
        entryButton.setTitleColor(UIColor(red: 100/255, green: 180/255, blue: 40/255, alpha: 1), for: .normal)
        entryButton.backgroundColor=(UIColor(red: 255/255, green: 255/255, blue: 255/255, alpha: 1))
        entryButton.layer.cornerRadius = 20
        entryButton.addTarget(self, action: #selector(entryButtonWasPressed(_sender:)), for: .touchUpInside)
        view.addSubview(entryButton)
    }
    @objc func entryButtonWasPressed(_sender: UIButton){
        makeEntry()
        print(pointAnnotations)   
    }

Edit edit: Here is the full code:

import UIKit
import Mapbox

class ViewController: UIViewController, MGLMapViewDelegate {
    let point = MGLPointAnnotation()
    var entryButton: UIButton!
    let locationManager = CLLocationManager()
    var annotation = MGLPointAnnotation()
    var pointAnnotations = [MGLPointAnnotation]()

    override func viewDidLoad() {
        super.viewDidLoad()
        let mapView = MGLMapView(frame: view.bounds)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.delegate = self

        // Enable heading tracking mode so that the arrow will appear.
        mapView.userTrackingMode = .followWithHeading

        // Enable the permanent heading indicator, which will appear when the tracking mode is not `.followWithHeading`.
        mapView.showsUserHeadingIndicator = true
        view.addSubview(mapView)

        // Set the map view's delegate
        mapView.delegate = self

        // Allow the map view to display the user's location
        mapView.showsUserLocation = true

        addButton()
        }

    func addButton(){
        entryButton = UIButton(frame: CGRect(x: (view.frame.width/2)-100 , y: (view.frame.height)-75, width: 200, height: 50))
        entryButton.setTitle("New Entry", for: .normal)
        entryButton.setTitleColor(UIColor(red: 100/255, green: 180/255, blue: 40/255, alpha: 1), for: .normal)
        entryButton.backgroundColor=(UIColor(red: 255/255, green: 255/255, blue: 255/255, alpha: 1))
        entryButton.layer.cornerRadius = 20
        entryButton.addTarget(self, action: #selector(entryButtonWasPressed(_sender:)), for: .touchUpInside)
        view.addSubview(entryButton)
    }


    @objc func entryButtonWasPressed(_sender: UIButton){
        makeEntry()
        print(pointAnnotations)
    }


    func makeEntry(){
        guard let locValue: CLLocationCoordinate2D = locationManager.location?.coordinate else { return }

        locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
        locationManager.startUpdatingLocation()

        let point = MGLPointAnnotation()
        point.coordinate = CLLocationCoordinate2D(latitude: (locValue.latitude), longitude: (locValue.longitude))
        point.title = "Entry"
        pointAnnotations.append(point)
//        mapView.addAnnotations(pointAnnotations)
    }

        func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
        // Always allow callouts to popup when annotations are tapped.
        return true
        }

        func mapView(_ mapView: MGLMapView, didSelect annotation: MGLAnnotation) {
        let camera = MGLMapCamera(lookingAtCenter: annotation.coordinate, fromDistance: 4500, pitch: 15, heading: 0)
        mapView.fly(to: camera, withDuration: 2,
        peakAltitude: 3000, completionHandler: nil)
        }
    }

    func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
        // Substitute our custom view for the user location annotation. This custom view is defined below.
        if annotation is MGLUserLocation && mapView.userLocation != nil {
            return CustomUserLocationAnnotationView()
        }
        return nil
    }

    // Optional: tap the user location annotation to toggle heading tracking mode.
    func mapView(_ mapView: MGLMapView, didSelect annotation: MGLAnnotation) {
        if mapView.userTrackingMode != .followWithHeading {
            mapView.userTrackingMode = .followWithHeading
        } else {
            mapView.resetNorth()
        }

        // We're borrowing this method as a gesture recognizer, so reset selection state.
        mapView.deselectAnnotation(annotation, animated: false)
    }


// Create a subclass of MGLUserLocationAnnotationView.
class CustomUserLocationAnnotationView: MGLUserLocationAnnotationView {
    let size: CGFloat = 48
    var dot: CALayer!
    var arrow: CAShapeLayer!

    // -update is a method inherited from MGLUserLocationAnnotationView. It updates the appearance of the user location annotation when needed. This can be called many times a second, so be careful to keep it lightweight.
    override func update() {
        if frame.isNull {
            frame = CGRect(x: 0, y: 0, width: size, height: size)
            return setNeedsLayout()
        }

        // Check whether we have the user’s location yet.
        if CLLocationCoordinate2DIsValid(userLocation!.coordinate) {
            setupLayers()
            updateHeading()
        }
    }

    private func updateHeading() {
        // Show the heading arrow, if the heading of the user is available.
        if let heading = userLocation!.heading?.trueHeading {
            arrow.isHidden = false

            // Get the difference between the map’s current direction and the user’s heading, then convert it from degrees to radians.
            let rotation: CGFloat = -MGLRadiansFromDegrees(mapView!.direction - heading)

            // If the difference would be perceptible, rotate the arrow.
            if abs(rotation) > 0.01 {
                // Disable implicit animations of this rotation, which reduces lag between changes.
                CATransaction.begin()
                CATransaction.setDisableActions(true)
                arrow.setAffineTransform(CGAffineTransform.identity.rotated(by: rotation))
                CATransaction.commit()
            }
        } else {
            arrow.isHidden = true
        }
    }

    private func setupLayers() {
        // This dot forms the base of the annotation.
        if dot == nil {
            dot = CALayer()
            dot.bounds = CGRect(x: 0, y: 0, width: size, height: size)

            // Use CALayer’s corner radius to turn this layer into a circle.
            dot.cornerRadius = size / 2
            dot.backgroundColor = super.tintColor.cgColor
            dot.borderWidth = 4
            dot.borderColor = UIColor.white.cgColor
            layer.addSublayer(dot)
        }

        // This arrow overlays the dot and is rotated with the user’s heading.
        if arrow == nil {
            arrow = CAShapeLayer()
            arrow.path = arrowPath()
            arrow.frame = CGRect(x: 0, y: 0, width: size / 2, height: size / 2)
            arrow.position = CGPoint(x: dot.frame.midX, y: dot.frame.midY)
            arrow.fillColor = dot.borderColor
            layer.addSublayer(arrow)
        }
    }

    // Calculate the vector path for an arrow, for use in a shape layer.
    private func arrowPath() -> CGPath {
        let max: CGFloat = size / 2
        let pad: CGFloat = 3

        let top =    CGPoint(x: max * 0.5, y: 0)
        let left =   CGPoint(x: 0 + pad,   y: max - pad)
        let right =  CGPoint(x: max - pad, y: max - pad)
        let center = CGPoint(x: max * 0.5, y: max * 0.6)

        let bezierPath = UIBezierPath()
        bezierPath.move(to: top)
        bezierPath.addLine(to: left)
        bezierPath.addLine(to: center)
        bezierPath.addLine(to: right)
        bezierPath.addLine(to: top)
        bezierPath.close()

        return bezierPath.cgPath
    }
}

EDIT ***************************************EDIT

I copy/pasted your entire code into a fresh Mapbox project and ran it on the simulator. It compiled and showed the usual (small) blue dot at my simulated location. Pressing the button did nothing - no new annotation and no error message.

Looking at your code you can see that you have two methods sitting outside the ViewController class. They are just floating in no man's land. One of them is the delegate method:

func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView?

If you move this method back inside the ViewController class it will be called when needed and provide the custom look for the user location that you want, ie. the large blue dot.

The other method outside the class is:

func mapView(_ mapView: MGLMapView, didSelect annotation: MGLAnnotation)

However, you have another version of this method inside the class already so you will probably want to combine the functionality of the two into the one inside the class then get rid of the external one.

The reason for the 'ambiguous' error message you were encountering is that you declare mapView inside ViewDidLoad so it isn't available inside any other methods inside that class. Instead, declare mapView outside the method along with your other class properties and just use it as normal inside ViewDidLoad like this:

var annotation = MGLPointAnnotation()
var pointAnnotations = [MGLPointAnnotation]()

var mapView: MGLMapView! // Declared here

override func viewDidLoad() {
    super.viewDidLoad()

    mapView = MGLMapView(frame: view.bounds) // Instantiated here. (No `let`)
    mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    mapView.delegate = self

Now your button will drop a standard annotation at the user's location. You can just see it behind your big blue dot. If any of this is unclear let me know.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM