[iOS] UIKit + CoreLocation 사용법 정리

Madeline👩🏻‍💻·2024년 1월 25일
0

iOS study

목록 보기
34/50
post-thumbnail

🗺️ 썸네일 사진은 해저케이블 먹는 상어

// MARK: How to Use CoreLocation

CoreLocation을 사용해서 위치 데이터를 받아와보자

1. import CoreLocation

import CoreLocation
import MapKit
import UIKit

당연히 import 해주는 것부터 시작한다. 가져온 위치 데이터는 지도에 띄워야 하니 MapKit도 같이 써두었따.

2. CLLocationManager

대부분의 프레임워크들은 매니저와 같은 중심부가 구현되어있다. CoreLocation도 마찬가지로 위치에 대한 대부분을 담당하는 매니저인 CLLocationMAnager()

class ViewController: UIViewController {
	let manager = CLLocationManager()
}

3. 위치 프로토콜 선언 - 머식이 Delegate

위치관련 프로토콜인 CLLocationManagerDelegate를 채택해서 기본적인 함수를 가져온다.

extension ViewController: CLLocationManagerDelegate {
   // 사용자의 위치를 성공적으로 가져온 경우 실행됨!
   func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
       
   }
   // 실패했을 때에는 디폴트 위치 or 에러 메세지를 띄워야 함
   func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
       
   }
}

didUpdateLocations

  • 사용자의 위치를 성공적으로 가져온 경우 실행됨!

  • locations - 배열로 들어옴(무슨 해양,, 그런거까지 정보가 많기 때문)

    didFailWithError

  • 실패했을 때에는 디폴트 위치 or 에러 메세지를 띄워야 함

    4. 위치 프로토콜 - Delegate 연결

필요한 시점에 delegate 연결까지 해준다.

locationManager.delegate = self

5. info.plist - 위치 권한 privacy 등록

info.plist에 위치 권한 privacy를 등록한다.
Privacy - Location When In Use Usage Description -> 앱 사용하는 동안 허용하겠다
보통 얘를 디폴트로 사용함

주의사항
등록한 권한만 사용가능하다. 아래에 권한 설정 종류를 보면 알겠지만, 종류가 다양하다. 내가 사용할 종류를 등록해야지 사용할 수 있다. 등록해놓은 권한 사항이 다르면 실행이 안될 수 있다.

6. 권한 설정 - 사용자에게 권한 요청하기 위해 iOS 위치 서비스 활성화여부 체크

  • 위치 권한 알럿을 언제 띄울지 정해야 한다.
    // viewDidLoad, 버튼 액션, ...

  • 사용자에게 권한 요청하기 위해, 기존에 iOS 위치 서비스를 활성화했었는지도 체크해야 한다.

    func checkDeviceLocationAuthorization() {
           // 위치권한 혀용여부 확인
           if CLLocationManager.locationServicesEnabled() { 
           
               // 현재 사용자의 권한 허용 상태
               let authorization: CLAuthorizationStatus
               
               if #available(iOS 14.0, *) {
                   authorization = locationManager.authorizationStatus
               } else {
                   authorization = CLLocationManager.authorizationStatus()
               }
               
               print("현재 사용자의 authorization status: \(authorization)")
               
           } else {
               print("위치 권한 허용 꺼져있음")
           }
       }
  • CLLocationManager.locationServicesEnabled()는 타입메서드로 정의되어있어서 인스턴스로 접근이 안된다ㅠㅠ그래서 locationmanager.~ 이렇게 안쓰고 새로 만들어준다.

  • CLAuthorizationStatus: 현재 사용자의 권한 허용 상태를 말한다

    근데 이렇게 하면 메인스레드에서 쓰지 말아라~ 다른애 시켜라~ 하는 에러가 다음과 같이 뜬다.

    This method can cause UI unresponsiveness if invoked on the main thread. Instead, consider waiting for the -locationManagerDidChangeAuthorization: callback and checking authorizationStatus first.

    무시무시한 😈보라색 에러👿가 떴다? 그러면 디스패치 큐 써야된다.

    func checkDeviceLocationAuthorization() {
           // 위치권한 혀용여부 확인
           
           DispatchQueue.global().async {
               if CLLocationManager.locationServicesEnabled() { // <- 타입메서드로 정의되어있어서 인스턴스로 접근 안됨ㅠㅠ그래서 locationmanager. 이렇게 안씀
                   
                   // 현재 사용자의 권한 허용 상태
                   let authorization: CLAuthorizationStatus
                   
                   if #available(iOS 14.0, *) {
                       authorization = self.locationManager.authorizationStatus
                   } else {
                       authorization = CLLocationManager.authorizationStatus()
                   }
                   
                   print("현재 사용자의 authorization status: \(authorization)")
                   
               } else {
                   print("위치 권한 허용 꺼져있음")
               }
           }
       }

그럼 이제 허용 상태에 대해 알아보자

CLAuthorizationStatus

public enum CLAuthorizationStatus : Int32, @unchecked Sendable {

   
   // User has not yet made a choice with regards to this application
   case notDetermined = 0

   
   // This application is not authorized to use location services.  Due
   // to active restrictions on location services, the user cannot change
   // this status, and may not have personally denied authorization
   case restricted = 1

   
   // User has explicitly denied authorization for this application, or
   // location services are disabled in Settings.
   case denied = 2

   
   // User has granted authorization to use their location at any
   // time.  Your app may be launched into the background by
   // monitoring APIs such as visit monitoring, region monitoring,
   // and significant location change monitoring.
   //
   // This value should be used on iOS, tvOS and watchOS.  It is available on
   // MacOS, but kCLAuthorizationStatusAuthorized is synonymous and preferred.
   
   @available(iOS 8.0, *)
   case authorizedAlways = 3

   
   // User has granted authorization to use their location only while
   // they are using your app.  Note: You can reflect the user's
   // continued engagement with your app using
   // -allowsBackgroundLocationUpdates.
   //
   // This value is not available on MacOS.  It should be used on iOS, tvOS and
   // watchOS.
   @available(iOS 8.0, *)
   case authorizedWhenInUse = 4

   
   // User has authorized this application to use location services.
   //
   // This value is deprecated or prohibited on iOS, tvOS and watchOS.
   // It should be used on MacOS.
   
   @available(iOS, introduced: 2.0, deprecated: 8.0, message: "Use kCLAuthorizationStatusAuthorizedAlways")
   public static var authorized: CLAuthorizationStatus { get }
}

CLAuthorizationStatus의 코드를 살펴보면, 사용자가 위치 권한 허용을 할 수 있는 종류가 있다.

  • notDetermined: 앱 처음 실행한 것처럼 아직 정하지 않은 상태

  • restricted: 위치에 대한 제어를 아예 못하는 경우(자녀 보호 기능 같은거)

  • denied: 유저가 위치 권한 사용을 거부한 경우

  • authorizedAlways: 항상 허용. 앱 사용 여부와 상관없이 위치 이벤트를 수신할 수 있다.
    애플 입장에서는 데이터 수집을 항상 허용한다는 점이 크리티컬하다고 생각한다.
    그래서 사용자가 앱을 "사용하고 있는 동안 허용"하고, 어느정도 시점이 지난 이후에 다시 한번 물어본다. 이제 "항상 허용"할거냐고. 처음부터 항상 허용할거냐는 선택지는 주지 않는다.

  • authorizedWhenInUse: 앱을 사용할 때만 허용

  • authorized: 옛날거임. 이제는 안씀

    7. 사용자 위치 권한 상태 확인 후에 권한 요청하기

       func checkCurrentLocationAuthorization(_ status: CLAuthorizationStatus) {
           
       }

    위와 같이 사용자가 위치 권한을 허용했는지의 여부에도 종류가 다양하기 때문에, 각 경우에 따른 액션을 취하는 것이 필요하다.
    예를 들어, 사용자가 허용을 했다면 위치정보를 가져올 수 있고,
    미결정이라면 권한을 요청할 수 있고,
    거부했었다면 허용해달라고 알럿을 띄우거나 설정으로 이동할 수 있다.

    self.checkCurrentLocationAuthorization(authorization)

    요녀석은 enum으로 구성되어 있으니까 switch 구문을 자동으로 만들어보면

    func checkCurrentLocationAuthorization(_ status: CLAuthorizationStatus) {
           
           switch status {
           case .notDetermined:
               print("notDetermined")
           case .restricted:
               print("restricted")
           case .denied:
               print("denied")
           case .authorizedAlways:
               print("authorizedAlways")
           case .authorizedAlways:
               print("authorizedAlways")
           case .authorizedWhenInUse:
               print("authorizedWhenInUse")
           case .authorized:
               print("authorized")
           }
       }

    authorizedAlways가 2개들어잇는 버그를 볼 수 있음ㅎㅎ

    그리고 미래에 뭐 생길수도있으니까 대비하라면서

@unknown default:
            <#fatalError()#>
        }

이런거 만들어두라고 한다.

default는 아는데, unknown은 뭔디?

알뜰지식: Enum에 대해..

@unknown 에 대해 알려면, Enum에 대해 알아야 한다.
다음과 같은 키워드를 공부해보자

  • case
  • rawValue
  • CaseIterable: 각 케이스를 배열로 쓸 수 있다.
  • @unknown: 추후에 멤버가 추가될 가능성이 있는 열거형
    -> Unfrozen Enum 에 붙이는 키워드임
    -> 그러면 Frozen Enum도 있나?
    (Optional - 얘도 enum임. 앞으로도 변하지 않을 것임 => @frozen

이렇게 각각 역할에 대해 알고 있으면 된다..
연관으로 into the unknown과 frozen으로 외워보자,,

그리고 @! 골뱅이를 언제붙이냐,
@ 골뱅이 뒤에는 unknown, frozen, available, action, discardableResult, escaping, objc, ...가 붙는다.
이런걸 어트리뷰트, Attribute라고 한다.

지도에 띄워보자

다시 프로젝트로 돌아와서, 사용자가 권한 허용까지 했다면, 현재 위치를 알아내서 지도에 띄워보자.

@IBOutlet weak var mapView: MKMapView!

mapView를 하나 만들어둔다.

locationManager에 위치를 가져오고 mapView에 띄워보자

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        print(locations)
        
        if let coordinate = locations.last?.coordinate {
            print(coordinate)
            print(coordinate.latitude)
            print(coordinate.longitude)
            
            let center = CLLocationCoordinate2D(latitude: coordinate.latitude, longitude: coordinate.longitude)
            setRegionAndAnnotation(center)
        }
        
        locationManager.stopUpdatingLocation()
    }
func setRegionAndAnnotation(_ center: CLLocationCoordinate2D) {
        
        let region = MKCoordinateRegion(center: center, latitudinalMeters: 400, longitudinalMeters: 400)
        mapView.setRegion(region, animated: true)
    }

center에 위도와 경도를 저장해두고, latitudinalMeters: 400, longitudinalMeters: 400로 반경 어디까지 표기할지 써준다.

profile
Major interest in iOS 🍀 & 🍎

0개의 댓글