Reverse geocoding in Swift 4

前端 未结 2 1530
佛祖请我去吃肉
佛祖请我去吃肉 2020-12-05 20:19

I\'m attempting to write a simple method that\'s fed CLLocationDegrees and returns a CLPlacemark. Looking at Apple\'s documentation, it seems like

相关标签:
2条回答
  • 2020-12-05 20:53
    import UIKit
    import CoreLocation
    import PlaygroundSupport
    PlaygroundPage.current.needsIndefiniteExecution = true
    

    func geocode(latitude: Double, longitude: Double, completion: @escaping (_ placemark: [CLPlacemark]?, _ error: Error?) -> Void)  {
        CLGeocoder().reverseGeocodeLocation(CLLocation(latitude: latitude, longitude: longitude)) { placemark, error in
            guard let placemark = placemark, error == nil else {
                completion(nil, error)
                return
            }
            completion(placemark, nil)
        }
    }
    

    or simply:

    func geocode(latitude: Double, longitude: Double, completion: @escaping (_ placemark: [CLPlacemark]?, _ error: Error?) -> Void)  {
        CLGeocoder().reverseGeocodeLocation(CLLocation(latitude: latitude, longitude: longitude), completionHandler: completion)
    }
    

    or extending CLLocation:

    extension CLLocation {
        func geocode(completion: @escaping (_ placemark: [CLPlacemark]?, _ error: Error?) -> Void)  {
            CLGeocoder().reverseGeocodeLocation(self, completionHandler: completion)
        }
    }
    

    To format your place mark as a mailing address you can use Contacts framework CNPostalAddressFormatter:

    import Contacts
    
    extension Formatter {
        static let mailingAddress: CNPostalAddressFormatter = {
            let formatter = CNPostalAddressFormatter()
            formatter.style = .mailingAddress
            return formatter
        }()
    }
    
    extension CLPlacemark {
        var mailingAddress: String? {
            return postalAddress?.mailingAddress
        }
    }
    
    extension CNPostalAddress {
        var mailingAddress: String {
            return Formatter.mailingAddress.string(from: self)
        }
    }
    

    placemark

    Contains an array of CLPlacemark objects. For most geocoding requests, this array should contain only one entry. However, forward-geocoding requests may return multiple placemark objects in situations where the specified address could not be resolved to a single location. If the request was canceled or there was an error in obtaining the placemark information, this parameter is nil.

    For more information about the CLPlacemark properties you can check this CLPlacemark


    Usage:

    let location = CLLocation(latitude: -22.963451, longitude: -43.198242)
    location.geocode { placemark, error in
        if let error = error as? CLError {
            print("CLError:", error)
            return
        } else if let placemark = placemark?.first {
            // you should always update your UI in the main thread
            DispatchQueue.main.async {
                //  update UI here
                print("name:", placemark.name ?? "unknown")
    
                print("address1:", placemark.thoroughfare ?? "unknown")
                print("address2:", placemark.subThoroughfare ?? "unknown")
                print("neighborhood:", placemark.subLocality ?? "unknown")
                print("city:", placemark.locality ?? "unknown")
    
                print("state:", placemark.administrativeArea ?? "unknown")
                print("subAdministrativeArea:", placemark.subAdministrativeArea ?? "unknown")
                print("zip code:", placemark.postalCode ?? "unknown")
                print("country:", placemark.country ?? "unknown", terminator: "\n\n")
    
                print("isoCountryCode:", placemark.isoCountryCode ?? "unknown")
                print("region identifier:", placemark.region?.identifier ?? "unknown")
    
                print("timezone:", placemark.timeZone ?? "unknown", terminator:"\n\n")
    
                // Mailind Address
                print(placemark.mailingAddress ?? "unknown")
            }
        }
    }
    

    This will print

    name: Morro da Saudade
    address1: Rua Casuarina
    address2: 597
    neighborhood: Lagoa
    city: Rio de Janeiro
    state: RJ
    subAdministrativeArea: unknown
    zip code: 22011-040
    country: Brazil
    
    isoCountryCode: BR
    region identifier: <-22.96345100,-43.19824200> radius 141.83
    timezone: America/Sao_Paulo (current)
    

    Rua Casuarina, 597

    Lagoa

    Rio de Janeiro RJ

    22011-040

    Brazil

    0 讨论(0)
  • 2020-12-05 21:19

    There are so many questions dealing with 'reverseGeocodeLocation' on Stack Overflow, I've seen so many of them. I'm using Swift 4.2 and I thought my 'reverseGeocodeLocation' requests might be timing out. Well, this is NOT the case, when starting to move with an iPhone the 'didUpdateLocations' cycles need to mature. It can take two or more cycles before 'reverseGeocodeLocation' returns placemarks.

    I wrote a simple app that uses 'reverseGeocodeLocation' until 'placemarks' are returned after repeated 'didUpdateLocations' cycles. This is my own home grown 'SampleCode'. I learned a lot from using the app, it might help others understand how reverse geocode requests work in the real world.

    Please give your comments on possible improvements. This code is freely given and freely taken.

    import Foundation
    import UIKit
    import CoreLocation
    
    class ViewController: UIViewController, CLLocationManagerDelegate {
        @IBAction func RefreshLocationButton(_ sender: Any) {
            self.requestingPlacemark = true
            self.placemarkData = nil
            //^Make another Placemark Request
            self.requestCounter = 0
            self.RequestCounterLabel.text = ""
            self.LocationLabel.text = ""
            self.PlacemarkLabel.text = ""}
        @IBOutlet weak var LocationCounterLabel: UILabel!
        @IBOutlet weak var RequestCounterLabel: UILabel!
        @IBOutlet weak var LocationLabel: UILabel!
        @IBOutlet weak var PlacemarkLabel: UILabel!
        let locationManager = CLLocationManager()
        var placemarkData: CLPlacemark!
        var placemarkString: String!
        var printPlacemarkData: Bool!
        var didUpdateLocationsCounter: Int = 0
        var requestCounter: Int = 0
        var requestingPlacemark: Bool = true
    
    override func viewDidLoad() {
        super.viewDidLoad()
        if CLLocationManager.locationServicesEnabled() {
            locationManager.requestAlwaysAuthorization()
            locationManager.delegate = self
            //locationManager.desiredAccuracy = kCLLocationAccuracyBest
            locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
            locationManager.startUpdatingLocation()
            self.RequestCounterLabel.text = ""
            self.LocationLabel.text = ""
            self.PlacemarkLabel.text = ""}}
    func printPlacemarks() {
        if printPlacemarkData {
            self.placemarkString = "Placemark Data:"
            self.placemarkString = buildPlacemarkString(item: 1)
            self.placemarkString = buildPlacemarkString(item: 2)
            self.placemarkString = buildPlacemarkString(item: 3)
            self.placemarkString = buildPlacemarkString(item: 4)
            self.placemarkString = buildPlacemarkString(item: 5)
            self.placemarkString = buildPlacemarkString(item: 6)
            self.placemarkString = buildPlacemarkString(item: 7)
            self.placemarkString = buildPlacemarkString(item: 8)
            self.placemarkString = buildPlacemarkString(item: 9)
            self.placemarkString = buildPlacemarkString(item: 10)
            self.PlacemarkLabel.text = self.placemarkString
            self.printPlacemarkData = false}}
    func buildPlacemarkString(item: Int) -> String {
        var elementText: String!
        var newString: String!
            switch item {
            case 1: elementText = "name: " + self.placemarkData.name!
            case 2: elementText = "subThoroughfare: " + self.placemarkData.subThoroughfare!
            case 3: elementText = "thoroughfare: " + self.placemarkData.thoroughfare!
            case 4: elementText = "postalCode: " + self.placemarkData.postalCode!
            case 5: elementText = "subLocality: " + self.placemarkData.subLocality!
            case 6: elementText = "locality: " + self.placemarkData.locality!
            case 7: elementText = "subAdministrativeArea: " + self.placemarkData.subAdministrativeArea!
            case 8: elementText = "administrativeArea: " + self.placemarkData.administrativeArea!
            case 9: elementText = "country: " + self.placemarkData.country!
            case 10: elementText = "isoCountryCode: " + self.placemarkData.isoCountryCode!
            default: print("Error: incorrect item number!")}
            newString = self.placemarkString + "\n" + elementText
        return newString
    }
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
            //location prints one time and 'didUpdateLocations' is stopped:
        self.didUpdateLocationsCounter = self.didUpdateLocationsCounter + 1
        let labelString = String(describing: self.didUpdateLocationsCounter)
        self.LocationCounterLabel.text = "Location Update Counter: " + labelString
        if self.placemarkData == nil {
            //location variable:
            self.requestCounter = self.requestCounter + 1
            let textString = String(describing: self.requestCounter)
            self.RequestCounterLabel.text = "Request Counter: " + textString
            if locations.count == 0 {
                self.LocationLabel.text = "Locations: There was NONE"}
            else {
                if locations.count > 0 {
                    let coordinate2D: CLLocation = locations.first!
                    let location = CLLocation(latitude: coordinate2D.coordinate.latitude, longitude: coordinate2D.coordinate.longitude)
                    let printString = String(describing: location)
                    self.LocationLabel.text = "Location: " + printString
                    let geocoder: CLGeocoder = CLGeocoder()
                    geocoder.reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
                        if error != nil {
                            let errorString = String(describing: error?.localizedDescription)
                            print("reverse geodcode fail: \(errorString)")
                            self.LocationCounterLabel.text = ""
                            self.RequestCounterLabel.text = ""
                            self.LocationLabel.text = "Reverse Geodcode fail: \(errorString)"
                            self.PlacemarkLabel.text = ""
                            self.requestingPlacemark = false
                            return}
                        else {
                            let pm = placemarks! as [CLPlacemark]
                            //There is ALWAYS 'placemarks' Data
                            if pm.count > 0 {
                                self.placemarkData = placemarks![0]
                                self.printPlacemarkData = true
                                self.printPlacemarks()
                                self.requestingPlacemark = false}}})}
                else {
                    if self.requestingPlacemark {
                        self.LocationLabel.text = "Problem: There is no 'location.first'"}}}}}}
    

    And then the Storyboard UI:

    Storyboard Image

    View Controller Image

    <?xml version="1.0" encoding="UTF-8"?>
    <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
        <device id="retina4_7" orientation="portrait">
            <adaptation id="fullscreen"/>
        </device>
        <dependencies>
            <deployment identifier="iOS"/>
            <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
            <capability name="Safe area layout guides" minToolsVersion="9.0"/>
            <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
        </dependencies>
        <scenes>
            <!--View Controller-->
            <scene sceneID="tne-QT-ifu">
                <objects>
                    <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="MyThoroughfare" customModuleProvider="target" sceneMemberID="viewController">
                        <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                            <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                            <subviews>
                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Location Counter" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZIg-u9-TMR">
                                    <rect key="frame" x="16" y="20" width="343" height="20.5"/>
                                    <color key="backgroundColor" red="0.80000001190000003" green="0.80000001190000003" blue="0.80000001190000003" alpha="1" colorSpace="calibratedRGB"/>
                                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                    <nil key="textColor"/>
                                    <nil key="highlightedColor"/>
                                </label>
                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Location Count Label" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="csF-KS-xGE">
                                    <rect key="frame" x="16" y="48" width="343" height="20.5"/>
                                    <color key="backgroundColor" red="0.80000001190000003" green="0.80000001190000003" blue="0.80000001190000003" alpha="1" colorSpace="calibratedRGB"/>
                                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                    <nil key="textColor"/>
                                    <nil key="highlightedColor"/>
                                </label>
                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Location String" lineBreakMode="tailTruncation" numberOfLines="8" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qOc-gk-gSp">
                                    <rect key="frame" x="16" y="76" width="343" height="20.5"/>
                                    <color key="backgroundColor" red="0.80000001190000003" green="0.80000001190000003" blue="0.80000001190000003" alpha="1" colorSpace="calibratedRGB"/>
                                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                    <nil key="textColor"/>
                                    <nil key="highlightedColor"/>
                                </label>
                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Placemark String" lineBreakMode="tailTruncation" numberOfLines="14" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="42A-nO-tNf">
                                    <rect key="frame" x="16" y="104" width="343" height="20.5"/>
                                    <color key="backgroundColor" red="0.80000001190000003" green="0.80000001190000003" blue="0.80000001190000003" alpha="1" colorSpace="calibratedRGB"/>
                                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                    <nil key="textColor"/>
                                    <nil key="highlightedColor"/>
                                </label>
                                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="HgN-oB-ds0">
                                    <rect key="frame" x="121.5" y="578" width="132" height="30"/>
                                    <state key="normal" title="Request Placemark"/>
                                    <connections>
                                        <action selector="RefreshLocationButton:" destination="BYZ-38-t0r" eventType="touchUpInside" id="izY-S5-wjg"/>
                                    </connections>
                                </button>
                            </subviews>
                            <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                            <constraints>
                                <constraint firstItem="ZIg-u9-TMR" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="16" id="6HO-kN-l1z"/>
                                <constraint firstItem="qOc-gk-gSp" firstAttribute="top" secondItem="csF-KS-xGE" secondAttribute="bottom" constant="7.5" id="6ux-dx-gJr"/>
                                <constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="42A-nO-tNf" secondAttribute="trailing" constant="16" id="8dG-gV-Cob"/>
                                <constraint firstItem="HgN-oB-ds0" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="A38-Nt-i3D"/>
                                <constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="ZIg-u9-TMR" secondAttribute="trailing" constant="16" id="Bb0-YC-oov"/>
                                <constraint firstItem="csF-KS-xGE" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="16" id="FJU-ha-HkL"/>
                                <constraint firstItem="42A-nO-tNf" firstAttribute="top" secondItem="qOc-gk-gSp" secondAttribute="bottom" constant="7.5" id="SpR-XB-MLG"/>
                                <constraint firstItem="6Tk-OE-BBY" firstAttribute="bottom" secondItem="HgN-oB-ds0" secondAttribute="bottom" constant="59" id="Vr6-QE-230"/>
                                <constraint firstItem="42A-nO-tNf" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="16" id="aHo-kJ-aCb"/>
                                <constraint firstItem="csF-KS-xGE" firstAttribute="top" secondItem="ZIg-u9-TMR" secondAttribute="bottom" constant="7.5" id="c63-Mq-ItW"/>
                                <constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="csF-KS-xGE" secondAttribute="trailing" constant="16" id="dlV-Hc-8XJ"/>
                                <constraint firstItem="qOc-gk-gSp" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="16" id="m5y-Q8-jxI"/>
                                <constraint firstItem="ZIg-u9-TMR" firstAttribute="top" secondItem="6Tk-OE-BBY" secondAttribute="top" id="sdw-WQ-Bx9"/>
                                <constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="qOc-gk-gSp" secondAttribute="trailing" constant="16" id="woB-ig-i5v"/>
                            </constraints>
                            <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
                        </view>
                        <connections>
                            <outlet property="LocationCounterLabel" destination="ZIg-u9-TMR" id="K4g-WT-f82"/>
                            <outlet property="LocationLabel" destination="qOc-gk-gSp" id="Cdw-bw-EWt"/>
                            <outlet property="PlacemarkLabel" destination="42A-nO-tNf" id="dMh-bw-cRE"/>
                            <outlet property="RequestCounterLabel" destination="csF-KS-xGE" id="ai6-zn-Toi"/>
                        </connections>
                    </viewController>
                    <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
                </objects>
                <point key="canvasLocation" x="53.600000000000001" y="66.11694152923539"/>
            </scene>
        </scenes>
    </document>
    
    0 讨论(0)
提交回复
热议问题