나침반 소스

컴퓨터/아이폰 : 2011. 6. 24. 13:19


앱개발 하다가 나침반을 넣을 일이 있어서 여기저기 검색했는데 너무 쉬워서인지
자세하세 설명이 된걸 찾을수가 없었습니다.
그래서 이번에는 제가만든 소스를 올려 보려고 합니다.

compass로 애플에서 검색하면 telsameter인가 하는것만 검색되더군요.
그걸로 값을 계산해서 알수도 있는것 같던데 복잡하고 귀찮아서 포기하고
CLHeading을 이용하는 방법을 사용했습니다.

아주 간단한건데요 CLLocationManager를 등록해주고 update되는 Heading데이터만
화면에 출력해주면 끝입니다.

일단 이것을 구현하기위해서는 
CoreLocation.framework과 CoreGraphics.framework 두가지가 꼭 필요합니다.
자신의 프로젝트에 Frameworks에다 등록해주시고
저는 나침반을 여기저기 자주 사용할것 같아서 compassview를 하나 만들어서 재활용 하기로 했습니다.
화려한 UI가 추가되면 좋겠지만 그건 각자 알아서 하시고
이번에는 간단하게 화면에다 동그라미와 막대기로 방향만 표시합니다.

이제 코드를 보겠습니다. 
LocationManager를 생성해 줍니다. 뷰의 initWithFrame함수또는 뷰컨트롤러에 적당한곳에다
넣어도 되겠습니다. 저는 나침반을 갖다붙여쓰기쉽게 뷰에다 넣어놓기로 했습니다.

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        
        curColor = [UIColor redColor];
        
        // 현재 뷰에다 locationManager를 붙여줍니다.
        self.locationManager = [[[CLLocationManager alloc] init] autorelease];
        
        // setup delegate callbacks( 콜백 즉 이벤트를 받을 대리자를 설정합니다.)
        locationManager.delegate = self;

        // start the compass( 나침반을 만들 heading데이터를 받기 시작합니다.)
        [locationManager startUpdatingHeading];
        
    }
    return self;
}

그다음은 화면이 갱신될때마다 나침반 배경과 바늘을 각도에 맞게 출력합니다.
만약 이미지로 처리한다면 아마 더블버퍼링을 해야할듯한데 저는 그냥 선으로 그렸기때문에
단순히 처리했습니다. 물론 예쁘게 꾸미는건 각자가....
그리고 나침반 바늘은 원래 길게 그려야되지만 편의상 시계바늘처럼 반만 그렸습니다.

- (void)drawRect:(CGRect)rect
{
 
    CGPoint ptCenter;
    CGPoint compassPoint;
    CGFloat diameter;
    CGFloat degreeNorth = 0.0f;
    
   // 화면 context를 받아옵니다.
   // 윈도우에서 dc같은거 같던데 파일핸들 처럼 화면 핸들이라고 보면 될것 같군요.
   // 이걸로 화면에다 이것저것 각종 삽질을 해줄수 있는...
    CGContextRef context = UIGraphicsGetCurrentContext();

    // 이건 단순한 테스트 코드입니다. (필요없는 코드입니다.)
    CGContextSetRGBFillColor (context, 1, 0, 0, 1);// 3
    CGContextFillRect (context, CGRectMake (0, 0, 200, 100 ));// 4
    CGContextSetRGBFillColor (context, 0, 0, 1, .5);// 5
    CGContextFillRect (context, CGRectMake (0, 0, 100, 200));
   
   // 화면가운데에다 나침반을 표시하기위해 center포인트를 구합니다.
    ptCenter = CGPointMake(320/2, 450/2);
    
   // 반지름(Radious)로 지름을 계산합니다. 
   // RADIOUS는 위에서 define된 값입니다.
    diameter = RADIOUS * 2;
    
   // 나침반 바깥테두리를 그립니다.
    CGContextStrokeEllipseInRect(context, CGRectMake(ptCenter.x-RADIOUS,ptCenter.y-RADIOUS, diameter, diameter));
   // 나침반 가운데 작은원을 그립니다.
    CGContextStrokeEllipseInRect(context, CGRectMake(ptCenter.x-5,ptCenter.y-5, 10,10));
    
    NSLog(@"%f, %f", ptCenter.x, ptCenter.y);
    
    // Heading데이터는 내가 바라보는 방향이므로 북쪽은 360도 에서 각도를 빼줍니다.
    degreeNorth = 360 - headingDegree;
    
     // 나침반 바늘을 그리는 함수인데 아래쪽에 코드를 추가했습니다.
    [self drawLineCenterContext:context withCenter:ptCenter toDegree:degreeNorth withRadious:RADIOUS];
}


아래함수가 나침반의 바늘을 그리는 함수입니다.
위에서도 말씀드렸지만 편의상 시계바늘처럼 짧은 바늘만 그렸습니다.

- (void)drawLineCenterContext:(CGContextRef)context withCenter:(CGPoint)center toDegree:(CGFloat)degree withRadious:(NSInteger)radious
{
    CGPoint ptDest;
    
    CGFloat chkRadian;

    // 각도를 라디안으로 변화합니다. 왜냐하면 sin,cos같은 삼각함수는 라디안으로 처리해야되기때문에
    // 라디안변환은 각도 * (PI/180)입니다.
    // DEG2RAD는 위에서 Define한 수식입니다.
    // 미리계산된값 
0.0174532925 를 사용해도 됩니다.
    CGFloat radAngle = DEG2RAD(degree); 
    
    // 단순히 바늘을 그리는 작업이므로 0, 90, 180, 270도는 계산할 필요없이 바로 좌표를 입력합니다.
    // 그외에는 삼각함수로 계산하는데 삼각함수는 각도에따라 값이 +, -를 왔다갔다 하므로 일일이
    // 신경쓰기보다는 항상 90도 이하의 각도로 만들어서 계산하면 편리합니다.
    // 1,2,3,4 분면에따라 x,y좌표 계산시에 sin, cos을 바꿔줘야합니다. 약간 머리를 굴려야되는데
    // 더쉬운 방법도 있을것 같은데 단순한 제머리로는 이게 한계라..-_-;;
    // 다른방법 있으시면 댓글로 공유좀 부탁드립니다.
    if (degree == 0.0f) {
        ptDest.x = center.x;
        ptDest.y = center.y - radious;
    }
    else if( degree == 90.0f )
    {
        ptDest.x = center.x + radious;
        ptDest.y = center.y;
    }
    else if( degree == 180.0f )
    {
        ptDest.x = center.x;
        ptDest.y = center.y + radious;
    }
    else if( degree == 270.0 )
    {
        ptDest.x = center.x - radious;
        ptDest.y = center.y;
    }
    else if( degree > 0.0f && degree < 90.0f )
    {
        
        ptDest.x = center.x + (radious * sin(radAngle));
        ptDest.y = center.y - (radious * cos(radAngle));        
    }
    else if( degree > 90.0f && degree < 180.0f )
    {
            chkRadian = (degree - 90.0f) * 0.0174532925;  // convert to radian
        
        ptDest.x = center.x + (radious * cos(chkRadian));
        ptDest.y = center.y + (radious * sin(chkRadian));        
    }
    else if( degree > 180.0f && degree < 270.0f )
    {
        chkRadian = (degree - 180.0f) * 0.0174532925;  // convert to radian
        
        ptDest.x = center.x - (radious * sin(chkRadian));
        ptDest.y = center.y + (radious * cos(chkRadian));        
    }
    else if( degree > 270.0f && degree < 360.0f )
    {
        chkRadian = (degree - 270.0f) * 0.0174532925;  // convert to radian
        
        ptDest.x = center.x - (radious * cos(chkRadian));
        ptDest.y = center.y - (radious * sin(chkRadian));        
    }
    
    NSLog(@"%f (%d) : X:%f, Y:%f", degree, radious, ptDest.x, ptDest.y);
    
   // 최종적으로 계산된 좌표를 이용해서 중심점에서 좌표까지 선을 그립니다.
    CGContextMoveToPoint(context, center.x , center.y );
    CGContextAddLineToPoint(context, ptDest.x, ptDest.y);
    CGContextStrokePath(context);
 
}


여기까지 하면 다 된것이 아니라 
이제 제일중요한 heading데이터를 받아야 합니다.
그러기 위해서는 최초에 뷰를 만들고나서 <CLLocationManagerDelegate> 프로토콜을 사용해야합니다.
LocationManager의 대리인으로 LocationManager로부터 넘어오는 각종 이벤트들을 뷰에서 처리하겠다는
의미이죠.
코드에서는 이렇게 들어가 있으면 된 것입니다.
@interface CompassView : UIView <CLLocationManagerDelegate> {

그리고 .m파일에서 실제 처리 메소드를 구현해줘야 합니다.
아래 두개 메소드인데요 heading데이터가 업데이트 될때와 에러가 발생했을때 처리할 두개의 메소드입니다.

#pragma mark -
#pragma CLLocationManager delegate Methods

// This delegate method is invoked when the location manager has heading data.
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)heading 
{
    // 데이터로는 마그네틱해딩 즉 자북 을 사용합니다.
    // 실제 북쪽 즉 진북을 쓰려면 trueHeading을 사용하면 됩니다.
    CGFloat magHeading = heading.magneticHeading;
    
    // 화면이 갱신될때마다 사용할 수 있도록 headingDegree에다 넣어줍니다.
    self.headingDegree = magHeading;
    NSLog(@"Heading : %f", magHeading);
    
    // 업데이트된 바늘을 그리기위해 지금즉시 화면을 갱신하도록 뷰를 갱신합니다.
    [self setNeedsDisplay];
}

// This delegate method is invoked when the location managed encounters an error condition.
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error 
{
    
    if ([error code] == kCLErrorDenied) {
        // This error indicates that the user has denied the application's request to use location services.
        NSLog(@"Stop updating Heading");
        [manager stopUpdatingHeading];
    } else if ([error code] == kCLErrorHeadingFailure) {
        // This error indicates that the heading could not be determined, most likely because of strong magnetic interference.
        NSLog(@"Heading failure");
    }
    
}

이상 간단한 나침반 만들기였습니다.
소스는 뷰소스를 파일로 첨부했습니다.

'컴퓨터 > 아이폰' 카테고리의 다른 글

아이폰 다음버전 출시설..ㅋ  (0) 2011.06.24
UIWebView와 Application(App) 간의 통신  (0) 2011.06.24
push notification  (0) 2011.06.24
DES암호화  (0) 2011.06.24
local notification  (0) 2011.06.14
      
Posted by k_ben