[iPhone] - 시작하세요! 아이폰 3 프로그래밍 - Part 14. 여기가 어디지? 코어 로케이션을 이용한 길 찾기

iPhone/[위키북스]시작하세요! 아이폰3 프로그래밍 2010. 9. 14. 16:13

* 여기가 어디지? 코어 로케이션을 이용한 길 찾기
- 아이콘은 코어 로케이션 프레임워크를 사용하여 위치 정보를 제공한다. 코어 로케이션은 현재 위치를 결정하기 위해 3가지 기술을 사용한다. 그것은 바로 GPS, 휴대폰 기지국의 삼각 측량법, 와이파이 위치 확인 서비스(WPS)
- 세 기술 모두 많은 양의 전력을 필요로 하기 때문에, 코어 로케이션을 사용할 때는 배터리 소모에 주의해야 한다. 위치 정보가 정말 필요할 때 배터리가 부족하여 애플리케이션이 폴링하지 않을수 있기 때문이다.코어 로케이션을 사용하여 위치 정보를 수신할 때 정밀도를 설정할 수 있다. 정밀도를 조절하면 불 필요한 배터리 소모를 막을 수 있다.
- 코어 로케이션이 사용하는 기술들은 애플리케이션 레이어에게 가려져 있다. 그래서 애플리케이션이 코어 로케이션 라이브러리를 호출할 때 GPS, 삼각측량법, WPS중 어느 것을 사용하라고 지정할 수 없다.

* 로케이션 매니저
- 메인 클래스는 CLLocationManager이며, 로케이션 매니저를 생성하는데 사용한다. 다음과 같이 로케이션 매니저의 인스턴스를 만들어야 한다.
CLLocationManager *locationManager = [[CLLocationManager alloc] init];

- 이 코드는 로케이션 매니저의 인스턴스를 생성하지만, 폴링을 시작하고 위치 정보를 갱신하지 않는다. 폴링을 시작하려면 먼저 델리케이트 객체를 만들고, 델리케이트 객체를 로케이션 매니저에 할당해야 한다. 로케이션 매니저는 위치 정보를 수신하면 델리케이트 메서드를 호출한다.
- 델리케이트로 사용되는 객체는 반드시 CLLocationManagerDelegate 프로토콜을 따라야 한다.

* 정밀도 설정하기
- 델리케이트를 설정한 뒤에는 정밀도를 설정한다.
- 정밀도를 높게 설정하면 많은 전력이 소모된다는 것을 기억하자.
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;

- CLLocationAccuracy변수를 사용하여 정밀도를 설정하였다. CLLocationAccuacy는 double로 정의된 자료형이다.
- 정밀도는 미터 meter 단위를 사용하며, desiredAccuracy를 10으로 설정하면 10미터의 오차범위 안에서 현재 위치를 결정한다.
- kCLLocationAccuracyBest외에도 kCLLocationAccuracyNearestTenMeter, kCLLocationAccuracyHundredMeters, kCLLocationAccuracyKilometer, kCLLocationAccuracyThreeKilometers와 같은 값들을 사용할수 있다.

* 디스턴스 필터 설정하기
- 로케이션 매니저는 기본적으로 위치가 바뀌면 델리게이트에게 통보한다. 로케이션 매니저의 디스턴스 필터를 설정하면, 위치가 변경될 때 마다 델리게이트 메서드를 호출하는것이 아니라 필터에 명시된 거리 이상을 이동하였을 때만 호출한다. 또한 디스턴스 필터는 미터 단위를 사용한다.
0 디스턴스 필터 값이 1000으로 할당되어 있으면, 로케이션 매니저는 1000미터 이상 이동할 때까지 델리게이트에 통보하지 않는다.
locationManager.distanceFilter = 1000.0f;

-  로케이션 매니저의 필터를 해제하고 싶다면, 다음과 같이 kCLDistanceFilterNone상수를 사용할 수 있다.
locationManager.distanceFilter = kCLDistanceFilterNone;

* 로케이션 매니저 동작시키기
- 위치 정보를 폴링할 준비가 되었다면, 로케이션 매니저를 동작시킨다. 로케이션 매니저가 동작하면 위치 정보를 수신할 것이고, 위치 정보가 갱신될 때마다 델리게이트 메서드를 호출할 것이다.
[locationManager startUpdatingLocation];

* 효율적으로 로케이션 매니저 사용하기
- 위치 정보를 지속적으로 수신하지 않고, 단순히 현재 위치를 조회하고 싶다면 위치 정보를 얻은 후에 곧바로 로케이션 매니저를 중단시키면된다.
[locationManager stopUpdatingLocation];

* 로케이션 매니저 델리게이트
- 로케이션 매니저의 델리케이트는 반드시 CLLocationManagerDelegate 프로토콜을 따라야 한다.
- CLLocationManagerDelegate 프로토콜은 2개의 선택적 메서드를 정의하며, 이 메서드 중 하나는 현재의 위치가 결정되거나 위치가 변경되었을 때 로케이션 매니저에 의해 호출된다. 또 다른 메서드는 로케이션 매니저가 에러를 발생할 때 호출된다.

* 위치 정보 갱신하기
- 로케이션 매니저가 델리게이트에게 현재 위치를 제공할 때, 로케이션 매니저는 locationManager:didUpdateToLocation:fromLocation:메서드를 호출한다.
- 이 메서드의 인자는 세 개이며, 첫 번째 인자는 델리게이트 메서드를 호출한 로케이션 매니저이다. 두 번째와 세 번째 인자는 CLLoction 타입의 객체이며, 두번째 인자는 아이폰의 현재 위치를 가지고 있다. 세 번째 인자는 이전 위치를 가지고 있으며, 메서드가 처음 호출될때 nil 값을 가진다.

* CLLocation을 사용한 위도와 경도 구하기
- 위치 정보는 로케이션 매니저로부터 CLLocation인스턴스를 통해 전달된다. CLLocation 클래스에는 5개의 프로퍼티가 있다. 첫번째 프로퍼티는 coordinate이며, 위도와 경도를 가지고 있다.
CLLocationDegress latitude = theLocation.coordinate.latitude;
CLLovationDegress longitude = theLocation.coordinate.longitude;

- CLLocation 객체는 위도와 경도에 대한 정밀도를 제공한다. CLLocation의 horizontalAccuracy프로퍼티는 현재 위치를 묘사하는 원의 반지름으로 사용된다. horizontalAccuracy 값이 클수록 위치에 대한 정밀도는 떨어진다. 애플리케이션 안의 파란색 원은 위치 정보를 수신했을 때 나타나며, 반지름으로 horizontalAccuracy를 사용한다.
- horizontalAccuracy가 마이너스 값을 갖는다면, 특정한 이유로 인해 현재 위치를 파악할 수 없음을 의미한다.
- CLLocation 객체는 altitude라는 프로퍼티를 가지고 있으며, 이것은 해발고도상의 위치를 나타낸다.
CLLocationDistance altitude = theLocation.altitude;

- 각각의 CLLocation객체는 verticalAccuracy라는 이름의 프로퍼티를 가지고 있으며, 이 프로퍼티는 해발고도에 대한 정밀도를 나타낸다. 고도는 verticalAccuracy 만큼의 오차를 가지며 미터 단위를 사용한다. 만일 verticalAccuracy가 마이너스의 값이면, 아이폰이 고도 값을 구하지 못했음을 의미한다.
- CLLocation 객체는 timestamp라는 이름의 프로퍼티를 가지며, 이것은 로케이션 매니저가 현재 위치를 결정했을 때의 시간을 나타낸다.
- CLLocation은 getDistanceFrom:라는 유용한 인스턴스 메서드 하나를 가지고 있다. 이 인스턴스 메서드는 CLLocation 객체 사이의 거리를 반환하며 다음과 같이 사용한다.
CLLocationDistance distance = [fromLocation getDistanceFrom:toLocation];

- 위 코드는 CLLocation 객체인 fromLocation과 toLocation 사이의 거리를 반환한다. distance객체는 대권거리 값을 가지며, 이 거리는 altitude프로퍼티와 두 점 사이의 해발고도 값을 무시한 채 양쪽의 점이 해발 고도상에 같은 위치에 있다는 전제하에 계산된 것이다.

* 에러통보
- 로케이션 매니저가 현재 위치를 탐색하는 데 실패하면, 두번째 델리게이트 메서드인 locationManager:didFailWithError:를 호출한다.
- 에러가 발생하는 가장 흔한 원인 중 하나는 사용자 접근 불가이다. 로케이션 매니저는 사용자 인증을 필요로 하기 때문에, 애플리케이션이 위치 정보에 처음으로 접근할 때 사용자가 위치 정보에 접근해도 되는지를 확인하는 알림 창을 화면에 띄운다.
- 사용자가 Don't Allow 버튼을 클릭하면, 로케이션 매니저는 locationManager:didFailWithError:메서드를 호출하고 kCLErrorDenied라는 에러 코드와 함께 델리게이트에게 통보한다.
- locationManager:didFailWithError:는 kCLErrorLocationUnknown 라는 또 다른 에러 코드를 사용하며, 이것은 현재 위치를 결정할 수 없음을 나타낸다. 하지만 kCLErrorLocationUnknown 에러가 발생해도 로케이션 매니저가 지속적으로 현재 위치를 탐색한다. kCLErrorDenied 메러가 발생하면 애플리케이션은 현재 세션을 유지하는 동안에는 위치 정보에 접근할 수 없다.

* 코어 로케이션 사용하기
- Xcode에서 뷰 기반의 애플리케이션 템플릿을 사용하여 새 프로젝트를 생성한다. 파일명은 WhereAmI라고 입력한다.

#### WhereAmIViewController.h ####
#import 
#import 
// 코어 로케이션 프레임워크의 헤더 파일을 포함

// 로케이션 매니저로부터 위치 정보를 받기 위해서 CLLocationManagerDelegate프로토콜을 따르게
@interface WhereAmIViewController:UIViewController
{
	// 코어 로케이션 매니저의 인스턴스 선언
	CLLocationManager *locationManager;
	// 출발 지점을 저장할 포인터를 선언, 출발 지점의 좌표는 로케이션 매니저에 의해 업데이트 될것
	CLLocation *startingPoint;
	
	UILabel *latitudeLabel;
	UILabel *longitudeLabel;
	UILabel *horizontalAccuracyLabel;
	UILabel *altitudeLabel;
	UILabel *verticalAccuracyLabel;
	UILabel *distanceTraveledLabel;
}

@property (nonatomic, retain) CLLocationManager *locationManager;
@property (nonatomic, retain) CLLocation *startingPoint;
@property (nonatomic, retain) IBOutlet UILabel *latitudeLabel;
@property (nonatomic, retain) IBOutlet UILabel *longitudeLabel;
@property (nonatomic, retain) IBOutlet UILabel *horizontalAccuracyLabel;
@property (nonatomic, retain) IBOutlet UILabel *altitudeLabel;
@property (nonatomic, retain) IBOutlet UILabel *verticalAccuracyLabel;
@property (nonatomic, retain) IBOutlet UILabel *distanceTraveledLabel;

@end

- 인터페이스 빌더를 실행하기 위해 WhereAmIViewController.xib를 더블 클릭한다. 레이블 6개를 왼쪽에 배치하고 레이블 텍스트를 각각 Latitude:, Longitude:, Horizontal Accuracy:, Altitude:, Vertical Accuracy:, Distance Traveled:라고 입력한다.
- 오른쪽에 레이블 6개를 더 배치하고 각 아웃렛과 연결한다.

#### WhereAmIViewController.m #####
#import "WhereAmIViewController.h"

@implementation WhereAmIViewController
@synthesize locationManager;
@synthesize startingPoint;
@synthesize latitudeLabel;
@synthesize longitudeLabel;
@synthesize horizontalAccuracyLabel;
@synthesize altitudeLabel;
@synthesize verticalAccuracyLabel;
@synthesize distanceTraveledLabel;

#pragma mark -
-(void)viewDidLoad
{
	// CLLocationManager를 메모리에 할당하고 초기화
	self.locationManager = [[CLLocationManager alloc] init];
	// 컨트롤러 클래스를 델리케이트로 지정
	locationManager.delegate = self;
	// 정밀도를 최고로 설정
	locationManager.desiredAccuracy = kCLLocationAccuracyBest;
	// 로케이션 매니저가 위치 정보에 대한 업데이트를 시작할수 있게 호출
	[locationManager startUpdatingLocation];	
}

#pragma mark -
#pragma mark CLLocationManagerDelegate Methods
// 자기 자신을 로케이션 매니저의 델리게이트로 지정하였기 때문에 델리게이트 메서드인
// locationManager:didUpdateToLocation:fromLocation:을 여기에 구현
-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
	// startingPoint가 nil값을 가지는지를 확인하고 nil이라면, 위치정보를 업데이트하고 현재 위치를 startingPoint에 할당
	if(startingPoint == nil)
		self.startingPoint = newLocation;
	
	NSString *latitudeString = [[NSString alloc] initWithFormat:@"%g•", newLocation.coordinate.latitude];
	latitudeLabel.text = latitudeString;
	[latitudeString release];
	
	NSString *longitudeString = [[NSString alloc] initWithFormat:@"%g•", newLocation.coordinate.longitude];
	longitudeLabel.text = longitudeString;
	[longitudeString release];
	
	NSString *horizontalAccuracyString = [[NSString alloc] initWithFormat:@"%gm", newLocation.horizontalAccuracy];
	horizontalAccuracyLabel.text = horizontalAccuracyString;
	[horizontalAccuracyString release];
	
	NSString *altitudeString = [[NSString alloc] initWithFormat:@"%gm", newLocation.altitude];
	altitudeLabel.text = altitudeString;
	[altitudeString release];
	
	NSString *verticalAccuracyString = [[NSString alloc] initWithFormat:@"%gm", newLocation.verticalAccuracy];
	verticalAccuracyLabel.text = verticalAccuracyString;
	[verticalAccuracyString release];
	
	CLLocationDistance newDistance = [newLocation distanceFromLocation:startingPoint];
	NSString *distanceString = [[NSString alloc] initWithFormat:@"%gm", newDistance];
	distanceTraveledLabel.text = distanceString;
	[distanceString release];
	
}

-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
	NSString *errorType = (error.code == kCLErrorDenied) ? @"Access Denied" : @"Unknown Error";
	UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error gettting Location"
													message:errorType
												   delegate:nil
										  cancelButtonTitle:@"Okay"
										  otherButtonTitles:nil];
	[alert show];
	[alert release];
	
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

- (void)viewDidUnload {
	self.locationManager = nil;
	self.latitudeLabel = nil;
	self.longitudeLabel = nil;
	self.horizontalAccuracyLabel = nil;
	self.altitudeLabel = nil;
	self.verticalAccuracyLabel = nil;
	self.distanceTraveledLabel = nil;

	[super viewDidUnload];
}


- (void)dealloc {
	[locationManager release];
	[latitudeLabel release];
	[longitudeLabel release];
	[horizontalAccuracyLabel release];
	[altitudeLabel release];
	[verticalAccuracyLabel release];
	[distanceTraveledLabel release];
    [super dealloc];
}

@end

- 프로그램을 컴파일하기 전에 CoreLocation.framework 를 추가한다.

- 나는 VMWare 상태라서 그런지 전부 Error만 리턴된다.. 쩝
- 또한 getDistanceFrom: 메서드 사용시 계속적인 경고메시지가 출력되어 메서드를 변경했다. 어디선가 찾은거 같은데
- [newLocation distanceFromLocation:startingPoint] 이렇게 하니까 경고 메시지가 나오지 않는다.
: