본문 바로가기

Swift

위치정보를 받아오는 방법

참고 사이트 

https://velog.io/@maddie/iOS-UIKit-CoreLocation-%EC%82%AC%EC%9A%A9%EB%B2%95-%EC%A0%95%EB%A6%AC

 

[iOS] UIKit + CoreLocation 사용법 정리

CoreLocation을 사용해보자 대부분의 프레임워크들은 매니저와 같은 중심부가 구현되어있음 locations - 배열로 들어옴(무슨 해양,, 그런거까지 정보가 많음) Privacy - Location When In Use Usage Description ->

velog.io

 

https://co-dong.tistory.com/73

 

iOS) 위치 정보 사용하기 (CoreLocation)

iOS에서 앱을 사용하다 보면 아래와 같은 권한 요청 메시지를 자주 보게 되는데, 보안을 중요하게 생각하는 애플이 위치를 포함한 카메라, 연락처 등 민감한 개인정보에 대해 사용자에게 먼저 권

co-dong.tistory.com

 

 

전체적인 흐름

  1. 앱 시작 또는 위치 서비스가 필요한 기능 활성화 시:
    • checkUserDeviceLocationServiceAuthorization() 호출하여 위치 서비스가 활성화되어 있는지 확인하고 권한을 요청합니다.
  2. 권한 상태가 변경될 때:
    • locationManagerDidChangeAuthorization(_:) 호출되어 권한 상태에 따라 추가 작업을 수행합니다.
  3. 위치 서비스가 활성화되고 권한이 부여된 경우:
    • locationManager(_:didUpdateLocations:)에서 위치를 업데이트하고, 주소로 변환하며, 관광지 데이터를 가져옵니다.
  4. 위치 서비스가 비활성화된 경우:
    • showRequestLocationServiceAlert() 호출하여 사용자에게 위치 서비스를 활성화하도록 안내합니다.

 

info.plist에 필요한 권한 추가 & Description 작성

 

 

CLLocationManagerDelegate 채택 및 위치 정보에 사용될 인스턴스 생성 

 

  • locationManager: CLLocationManager 타입의 인스턴스입니다.
    • CLLocationManager는 iOS에서 위치 서비스를 관리하는 객체로, 앱이 사용자의 위치를 얻거나 지속적으로 추적할 수 있도록 해줍니다.
    • locationManager를 사용하여 사용자의 현재 위치, 권한 상태, 위치 업데이트 등을 요청하고 처리할 수 있습니다.
    • 예를 들어, locationManager.startUpdatingLocation()을 호출하면 현재 위치를 추적하기 시작하고, 사용자의 위치가 변경될 때마다 locationManager(_:didUpdateLocations:) 델리게이트 메서드가 호출됩니다.
  • geocoder: CLGeocoder 타입의 인스턴스입니다.
    • CLGeocoder는 지오코딩과 리버스 지오코딩을 수행하는 객체로, 위도와 경도를 주소로 변환하거나 그 반대로 주소를 위도와 경도로 변환할 수 있습니다.
    • geocoder.reverseGeocodeLocation(location)을 사용하여 CLLocation 객체를 전달하면 해당 좌표에 대한 주소 정보를 반환할 수 있습니다.
    • 이를 통해 사용자의 위치를 텍스트 주소로 변환하여 UI에 표시할 수 있습니다.

 

class HomeViewController: UIViewController, CLLocationManagerDelegate {

    // 위치 정보
    let locationManager = CLLocationManager()
    let geocoder = CLGeocoder()

 

 

checkUserDeviceLocationServiceAuthorization() 실행

 

  • 사용자의 위치 서비스가 활성화되어 있는지 확인하고, 권한을 요청하는 메서드입니다.
  • 이 메서드는 앱이 시작되거나 위치 서비스가 필요한 기능을 호출하기 전에 먼저 호출되어야 합니다.

 

override func viewDidLoad() {
    super.viewDidLoad()
    ...
    checkUserDeviceLocationServiceAuthorization()

 

func checkUserDeviceLocationServiceAuthorization() {

    // 3.1 디바이스 자체에 위치 서비스가 활성화 상태인지 확인한다.
    DispatchQueue.global().async {
        guard CLLocationManager.locationServicesEnabled() else {
            // 시스템 설정으로 유도하는 커스텀 얼럿
            self.showRequestLocationServiceAlert()
            return
        }
    }

    // 위치 서비스가 활성화 상태라면 권한 오청
    locationManager.delegate = self
    locationManager.requestWhenInUseAuthorization()
}

 

 

 

locationManagerDidChangeAuthorization(_:)  

 

  • 위치 서비스 권한 상태가 변경될 때 호출되는 메서드입니다.
  • 위치 서비스 권한이 변경될 때 필요한 추가 작업을 처리합니다.
  • checkUserDeviceLocationAuthorization() 메서드에서 권한 요청 후 이 메서드가 호출되므로, 권한 상태에 따른 처리가 필요합니다.
// iOS 14 이상에서는 권한 상태를 델리게이트 메서드에서 처리
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
    // 3.2 사용자 디바이스의 위치 서비스가 활성화 상태라면,  앱에 대한 권한 상태를 확인해야 한다.
    let authorizationStatus: CLAuthorizationStatus

    // 앱의 권한 상태 가져오는 코드 (iOS 버전에 따라 분기처리)
    if #available(iOS 14.0, *) {
        authorizationStatus = manager.authorizationStatus
    }else {
        authorizationStatus = CLLocationManager.authorizationStatus()
    }

    // 권한 상태값에 따라 분기처리를 수행하는 메서드 실행
    checkUserCurrentLocationAuthorization(authorizationStatus)
}

 

 

 

checkUserCurrentLocationAuthorization(_:)

 

  • locationManagerDidChangeAuthorization(_:)에서 호출되어 앱에 대한 위치 권한 상태를 확인하고 처리합니다.
  • 사용자의 권한 상태에 따라 위치 서비스를 시작하거나, 권한 요청을 하거나, 위치 서비스가 비활성화된 경우 얼럿을 표시합니다.

 

func checkUserCurrentLocationAuthorization(_ status: CLAuthorizationStatus) {
    switch status {
    case .notDetermined:
        // 사용자가 권한에 대한 설정을 선택하지 않은 상태
        print("Not determained")

        // 권한 요청을 보내기 전에 desiredAccuracy 설정 필요
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.requestWhenInUseAuthorization()
        // 권한 요청을 보낸다.

    case .denied, .restricted:
        // 사용자가 명시적으로 권한을 거부했거나, 위치 서비스 활성화가 제한된 상태
        // 시스템 설정에서 설정값을 변경하도록 유도한다.
        // 시스템 설정으로 유도하는 커스텀 얼럿
        print("Restricted or denied")
        showRequestLocationServiceAlert()

    case .authorizedWhenInUse:
        // 앱을 사용중일 때, 위치 서비스를 이용할 수 있는 상태
        // manager 인스턴스를 사용하여 사용자의 위치를 가져온다.
        print("Authorized")
        locationManager.startUpdatingLocation()

    default:
        print("Default")
    }
}

 

 

showRequestLocationServiceAlert()

 

  • 위치 서비스가 비활성화된 경우 사용자에게 설정으로 이동하도록 안내하는 얼럿을 보여주는 메서드입니다.
  • checkUserCurrentLocationAuthorization(_:) 메서드에서 위치 서비스가 비활성화된 경우 호출됩니다.

 

func showRequestLocationServiceAlert() {
    let requestLocationServiceAlert = UIAlertController(title: "위치 정보 이용", message: "위치 서비스를 사용할 수 없습니다.\n디바이스의 '설정 > 개인정보 보호'에서 위치 서비스를 켜주세요.", preferredStyle: .alert)
    let goSetting = UIAlertAction(title: "설정으로 이동", style: .destructive) { _ in
        if let appSetting = URL(string: UIApplication.openSettingsURLString) {
            UIApplication.shared.open(appSetting)
        }
    }
    let cancel = UIAlertAction(title: "취소", style: .default) { [weak self] _ in
        self?.reloadData()  // 여기에 await는 필요하지 않습니다.
    }
    requestLocationServiceAlert.addAction(cancel)
    requestLocationServiceAlert.addAction(goSetting)

    present(requestLocationServiceAlert, animated: true)
}

 

 

reverseGeocode(location:completion:)

 

  • 위도와 경도를 주소로 변환하는 메서드입니다.
  • 위치 업데이트를 받았을 때 (locationManager(_:didUpdateLocations:)) 호출됩니다.

 

// 3. Reverse Geocoding을 사용하여 위도와 경도를 주소로 변환하는 메서드
// 외부에서 호출할 때 userLocation이 설정된 후 실행할 동작을 정의할 수 있도록 completion handler를 추가합니다.
func reverseGeocode(location: CLLocation, completion: @escaping (String?) -> Void) {
    geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
        if let error = error {
            print("Reverse geocoding failed: \(error.localizedDescription)")
            completion(nil) // 에러가 발생한 경우 nil을 반환
            return
        }

        guard let placemark = placemarks?.first else {
            print("No placemark found")
            completion(nil) // placemark가 없는 경우 nil을 반환
            return
        }

        // 지번 주소 구성
        // let country = placemark.country ?? ""
        let administrativeArea = placemark.administrativeArea ?? ""
        let locality = placemark.locality ?? ""
        let subLocality = placemark.subLocality ?? ""
        // thoroughfare와 subThoroughfare는 생략

        let jibunAddress = "\(administrativeArea) \(locality) \(subLocality)"

        // userLocation에 값을 할당
        self.userLocation = jibunAddress

        // 완료된 후 jibunAddress를 completion handler로 전달
        completion(jibunAddress)
    }
}

 

 

locationManager(_:didUpdateLocations:)

 

  • 위치 업데이트를 받을 때 호출되는 메서드입니다.
  • 이 메서드에서 현재 위치를 가져와 주소로 변환하고, 관광지 데이터를 요청하며, UI를 업데이트합니다.
  • 이 메서드에서 reverseGeocode(location:completion:) 메서드를 호출하여 주소로 변환한 후, NetworkManager.shared.getSpotDataFromLocation()을 호출하여 관광지 데이터를 가져옵니다.

 

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    guard let location = locations.last else { return }
    self.userLatitude = "\(location.coordinate.latitude)"
    self.userLongitude = "\(location.coordinate.longitude)"
    // 경도와 위도를 통해 지번/도로명 주소 변환
    reverseGeocode(location: location) { userLocation in
        if let userLocation = userLocation {
            print("User location: \(userLocation)")
            // 여기서 userLocation을 사용하여 추가 작업 수행 가능
            // 메인 스레드에서 UI 업데이트
            DispatchQueue.main.async {
                self.getHomSubTitleView(main: "동동이님, 근처에는 말이에요 😄", sub: "현재 위치: \(userLocation)")

                // 관광지 데이터 가져오기
                NetworkManager.shared.getSpotDataFromLocation(mapX: self.userLongitude, mapY: self.userLatitude, contentTypeId: self.selectedContentTypeId) { [weak self] result in
                    switch result {
                    case .success(let item):
                        // 데이터를 받아온 후 첫 번째 아이템을 사용하여 configureData 호출
                        self?.locationReceivedItems = item
                        DispatchQueue.main.async {
                            self?.homeView.getHomeContentView().placeTableView.customPlaceTableView.reloadData() // 여기서 테이블 뷰를 다시 로드합니다.
                        }
                    case .failure(let error):
                        print("Failed to fetch attraction data: \(error)")
                    }
                }
            }
        } else {
            print("Failed to retrieve user location")
        }
    }
}