0

I am working in Swift trying to update an organization struct that will need to hold a latitude and longitude. I created a mutating function in the struct that will update the latitude and longitude based on the organization organization struct’s address. I got it to work, but the issue is that when I call the mutating function, I need to manually enter the variable name with the .latitude and .longitude. Is there a way that I can pass the variable struct’s name automatically and reference the .latitude and .longitude without calling the specific variable name with it so I can make it more usable? I included an example below with my code. Thanks for your help!

import UIKit
import PlaygroundSupport
import CoreLocation

PlaygroundPage.current.needsIndefiniteExecution = true

struct organization {
    var name: String
    var address: String
    var latitude: CLLocationDegrees = 0 //default setting for latitude
    var longitude: CLLocationDegrees = 0 //default setting for longitude
    
    mutating func getCoordinateFrom(completion: @escaping(_ coordinate: CLLocationCoordinate2D?, _ error: Error?) -> () ) {
        CLGeocoder().geocodeAddressString(address) { placemarks, error in
            completion(placemarks?.first?.location?.coordinate, error)
        }
    }
}

struct Coordinates {
    var latitude: Double
    var longitude: Double
}

//create an wildernessLodge variable of type organization
var wildernessLodge = organization(name: "Disney's Wilderness Lodge", address: "901 Timberline Dr, Orlando, FL 32830")

wildernessLodge.getCoordinateFrom { coordinate, error in
    guard let coordinate = coordinate, error == nil else { return }
    wildernessLodge.latitude = coordinate.latitude
    wildernessLodge.longitude = coordinate.longitude
    print("update 1 \(wildernessLodge)")
    }

4

1 回答 1

0

我对你的代码有点困惑。为什么被getCoordinateFrom标记为mutating?也许你打算写这样的东西。

mutating func getCoordinatesFromAddress() {
    CLGeocoder().geocodeAddressString(address) { placemarks, error in
        guard let coordinate = placemarks?.first?.location?.coordinate, error == nil else { return }
        self.latitude = coordinate.latitude
        self.longitude = coordinate.longitude
    }
}

现在这个函数正在变异(它修改self),并且

wildernessLodge.getCoordinateFrom { coordinate, error in ... }

可以替换为

wildernessLodge.getCoordinatesFromAddress()

离开该getCoordinateFrom方法的唯一原因是,如果在代码中的某个位置,您打算从地址获取坐标但不更新结构中的坐标。我无法想象这样做的充分理由,所以我建议用其他方法替换该getCoordinateFrom方法。

或者,如果您通常打算在创建此类型的值后立即设置坐标,您可能需要考虑这样的事情。

init(name: String, address: String) {
    self.name = name
    self.address = address
    CLGeocoder().geocodeAddressString(address) { placemarks, error in
        guard let coordinate = placemarks?.first?.location?.coordinate, error == nil else { return }
        self.latitude = coordinate.latitude
        self.longitude = coordinate.longitude
    }
}

或者

init(name: String, address: String) {
    self.name = name
    self.address = address
    self.getCoordinatesFromAddress()
}

然后,您可以创建一个organizationusingorganization(name: name, address: address)并自动正确设置坐标。

如果这些都不令人满意,也许您应该创建两个不同的结构来捕获您想要的行为。

struct Organization {
    var name: String
    var address: String
    func withCoordinates(completion: @escaping(_ coordinate: CLLocationCoordinate2D?, _ error: Error?) -> () ) {
        CLGeocoder().geocodeAddressString(address) { placemarks, error in
            completion(placemarks?.first?.location?.coordinate, error)
        }
    }
}
struct OrganizationWithCoordinates {
    var name: String
    var address: String
    var latitude: CLLocationDegrees
    var longitude: CLLocationDegrees
    init(from organization: Organization) {
        self.name = organization.name
        self.address = organization.address
        organization.withCoordinates { coordinate, error in
            guard let coordinate = coordinate, error == nil else { return }
            self.latitude = coordinate.latitude
            self.longitude = coordinate.longitude
        }
    }
} 

我更喜欢这样的方法,但我喜欢有很多类型。

最后,如评论中所述,如果您真的只关心简洁,您可以替换

var latitude: CLLocationDegrees
var longitude: CLLocationDegrees

var coordinates: CLLocationCoordinate2D
var latitude: CLLocationDegrees { coordinates.latitude }
var longitude: CLLocationDegrees { coordinates.longitude }

然后替换

wildernessLodge.latitude = coordinate.latitude
wildernessLodge.longitude = coordinate.longitude

wildernessLodge.coordinates = coordinate

事实上,您应该可以随意组合这些方法中的任何一种。

编辑:正如所指出的,这些解决方案不能按原样工作。基本的张力是尝试CLGeocoder同步使用 's async 方法。一种解决方案是使用类而不是结构。另一种方法是使用上述withCoordinate方法的修改:

struct Organization {
    var name: String
    var address: String
    func withCoordinate(callback: @escaping (OrganizationWithCoordinate?, Error?) -> Void) {
        CLGeocoder().geocodeAddressString(self.address) { placemarks, error in
            if let coordinate = placemarks?.first?.location?.coordinate, error == nil {
                let orgWithCoord = OrganizationWithCoordinate(name: self.name, address: self.address, latitude: coordinate.latitude, longitude: coordinate.latitude)
                callback(orgWithCoord, nil)
            } else {
                callback(nil, error)
            }
            
        }
    }
}
struct OrganizationWithCoordinate {
    var name: String
    var address: String
    var latitude: CLLocationDegrees
    var longitude: CLLocationDegrees
}

Organization(name: "Disney's Wilderness Lodge", address: "901 Timberline Dr, Orlando, FL 32830").withCoordinate { orgWithCoord, error in
    guard let orgWithCoord = orgWithCoord, error == nil else {
        print("Error")
        return
    }
    print(orgWithCoord)
}

这包含了CLGeocoder.

另一种解决方案可能是使用如下强制CLGeocoder同步。DispatchSemaphore我认为这些在 Playgrounds 中不能正常工作,但这应该在实际应用程序中工作。

struct Organization {
    var name: String
    var address: String
    var latitude: CLLocationDegrees
    var longitude: CLLocationDegrees
    
    init(name: String, address: String) throws {
        self.name = name
        self.address = address
        
        var tempCoordinate: CLLocationCoordinate2D?
        var tempError: Error?
        
        let sema = DispatchSemaphore(value: 0)
        CLGeocoder().geocodeAddressString(address) { placemarks, error in
            tempCoordinate = placemarks?.first?.location?.coordinate
            tempError = error
            
            sema.signal()
        }
        // Warning: Will lock if called on DispatchQueue.main
        sema.wait()
        
        if let error = tempError {
            throw error
        }
        guard let coordinate = tempCoordinate else {
            throw NSError(domain: "Replace me", code: -1, userInfo: nil)
        }
        
        self.longitude = coordinate.longitude
        self.latitude = coordinate.latitude
    }
}

// Somewhere in your app
let queue = DispatchQueue(label: "Some queue")
queue.async {
    let wildernessLodge = try! Organization(name: "Disney's Wilderness Lodge", address: "901 Timberline Dr, Orlando, FL 32830")

    DispatchQueue.main.async {
        print(wildernessLodge)
    }
}

在这里,创建了一个新的队列来做Organization相关的工作,以避免锁定主队列。在我看来,这种方法创建的代码看起来最不笨拙,但可能不是性能最高的选择。位置 API 是异步的是有原因的。

于 2021-05-10T09:38:45.140 回答