본문 바로가기
아이폰 개발/ios 개념&튜토리얼

iOS 배달앱 개발 예제(2) - (feat. 마커표시 MKAnnotation, CLLocationManagerDelegate, MKMapViewDelegate)

by 인생여희 2021. 2. 15.

iOS 배달앱 개발 예제(2) -

(feat. 마커표시 MKAnnotation, CLLocationManagerDelegate, MKMapViewDelegate)

 

 

 

✅ 이번 포스팅에서 구현할 기능

 

1.특정 위치에 어노테이션(마크) 표시

2.어노테이션(마크) 클릭시 위치정보 표시

3.어노테이션 왼쪽에 이미지 넣기

4.어노테이션 오른쪽에 상세정보 버튼 넣기

5.상세정보 버튼 클릭시 alert 창 띄워주기

 

 

✅ 어노테이션(마커) 클래스 생성

 

1.어노테이션(마커) 관련 변수 및 초기화 메소드 구현

 

Pin.h

 

#import <Foundation/Foundation.h>
@import MapKit;

//핀 객체
//마커(어노테이션)에 쓰일 객체.

NS_ASSUME_NONNULL_BEGIN

@interface Pin : NSObject <MKAnnotation>

//아래 변수 세개는 어노테이션(마커)을 커스터마이징 할때 필수로 구현해줘야 동작한다.
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
@property (nonatomic, copy) NSString *title;
@property (nonatomic , copy) NSString *subtitle;


//CLLocationCoordinate2D 로 핀 객체 초기화
-(id) initWithCoordinate :(CLLocationCoordinate2D)newCoordinate;

@end

NS_ASSUME_NONNULL_END

 

 

Pin.m

 

#import "Pin.h"

@implementation Pin

//지정 초기자
-(id)initWithCoordinate:(CLLocationCoordinate2D)newCoordinate{
    
    self = [super init];
    
    if (self) {
        _coordinate = newCoordinate;
        _title = @"좌표";
        _subtitle = @"좌표입니다.";
    }
    
    
    return self;
    
}

@end

 

 

✅ 위치정보 표시 + 맵뷰 델리게이트를 채택한 클래스 생성

 

0.권한체크 기능

1.싱글톤 패턴으로 생성.

2.위치정보가 변할때마다 호출되는 CLLocationManager 델리게이트 메소드 구현.

3.MapView에 어노테이션을 추가할때 호출되는 MapView 델리게이트 메소드 구현.

 

CoreLocation.h

 

#import <MapKit/MapKit.h>
#import <CoreLocation/CoreLocation.h>
#import <Foundation/Foundation.h>
#import "Pin.h"
#import "AppDelegate.h"

NS_ASSUME_NONNULL_BEGIN

//위치정보, 맵 델리게이트 메소드
@interface CoreLocation : NSObject <CLLocationManagerDelegate , MKMapViewDelegate>

//CoreLocation 이 객체를 참조하는 카운터
@property (readwrite) NSInteger refNum;

//로케이션 매니져 전역 변수
@property (nonatomic, retain) CLLocationManager *locationManager;


//싱글톤 패턴
+(CoreLocation *) sharedSingleton;


//지정 초기화 메소드
-(id)initWithLocationManager;


//맵뷰 셋팅
-(MKMapView *) settingMapViewWithRect : (CGRect) rect;

//mapView
@property (strong, nonatomic) MKMapView *mapView;

@end

NS_ASSUME_NONNULL_END

 

 

CoreLocation.m

 


#import "CoreLocation.h"

@implementation CoreLocation

@synthesize locationManager , refNum;

//위치 정보 제공 객체 싱글턴 패턴으로 생성
+(CoreLocation *)sharedSingleton{
    
    static CoreLocation* sharedSington;
    if (!sharedSington) {
        
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            sharedSington = [[CoreLocation alloc]initWithLocationManager];
        });
        
    }
    return sharedSington;
}


//지정 초기화 메소드
-(id)initWithLocationManager{
    
    self = [super init];
    
    if (self) {
        
        //참조 횟수 0 으로 초기화 => userdefalt로 변경하기
        refNum = 0;
        

        //초기화
        if (locationManager == nil) {
        
            NSLog(@"locationManager 초기화");
            
            locationManager = [[CLLocationManager alloc]init];             //초기화
            locationManager.desiredAccuracy = kCLLocationAccuracyBest;        //지도 정확도 최상
            locationManager.delegate = self;                            //델리게이트 설정
            
            locationManager.distanceFilter = 10;                         //기준위치 10 미터 기준
            locationManager.pausesLocationUpdatesAutomatically  = NO;        //자동 멈춤 방지
                        
            
        }
        
        
        //현지 지도 접근 권한
        CLAuthorizationStatus auth =  [CLLocationManager authorizationStatus];
        
        //아직 권한 결정이 안되었으면
        if (auth == kCLAuthorizationStatusNotDetermined) {
            
            //권한 요청
            [locationManager requestAlwaysAuthorization];                  //백그라운드에서도 접근할때 - 사용자 권한 동의
            //[locationManager requestWhenInUseAuthorization];                   //포그라운드에서만 접근할때 - 사용자 권한 동의
            
        //접근 권한이 허용일 경우
        }else if(auth == kCLAuthorizationStatusAuthorizedAlways ||
                auth == kCLAuthorizationStatusAuthorizedWhenInUse){
            
            
            
            [locationManager setAllowsBackgroundLocationUpdates:YES];       //백그라운드 업데이트 허용
            [locationManager startUpdatingLocation];                     //업데이트 시작
            
        }
    
    }
    
    return self;
}


//위치 접근 권한 상태 체크
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
    if (status == kCLAuthorizationStatusAuthorizedAlways || status == kCLAuthorizationStatusAuthorizedWhenInUse) {
        NSLog(@"allowed"); // allowed
    }
    else if (status == kCLAuthorizationStatusDenied) {
        NSLog(@"denied"); // denied
        
        if ([CLLocationManager locationServicesEnabled]){

            NSLog(@"Location Services Enabled");

        }
        
    }
}


//위치 업데이트 델리게이트 메소드
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations{
    
    NSLog(@"위치 수집 업데이트 - didUpdateLocations");
    
    //NSLog(@"manager - latitude : %f" , manager.location.coordinate.latitude);
    //NSLog(@"manager - longitude : %f" , manager.location.coordinate.longitude);
    //NSLog(@"locationManager - latitude : %f" , locationManager.location.coordinate.latitude);
    //NSLog(@"locationManager - longitude : %f" , locationManager.location.coordinate.longitude);
    
}



- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{
    
    NSLog(@"위치 수집 오류 - didFailWithError");
}



#pragma helper Method
//권한 체크
- (void)checkLocationAccess {
    
    NSLog(@"checkLocationAccess");
    
    CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
    switch (status) {

    // custom methods after each case

        case kCLAuthorizationStatusDenied:
            
            NSLog(@"kCLAuthorizationStatusDenied:");
            
            //[self allowLocationAccess]; // 커스텀 메소드 구현 필요
            
            break;
        case kCLAuthorizationStatusRestricted:
            
            NSLog(@"kCLAuthorizationStatusRestricted:");
            
           // [self allowLocationAccess]; // 커스텀 메소드 구현 필요
            
            break;
        case kCLAuthorizationStatusNotDetermined:
            
            NSLog(@"kCLAuthorizationStatusNotDetermined:");
            
            break;
        case kCLAuthorizationStatusAuthorizedAlways:
            
            NSLog(@"kCLAuthorizationStatusAuthorizedAlways:");
            
            break;
        case kCLAuthorizationStatusAuthorizedWhenInUse:
            
            NSLog(@"kCLAuthorizationStatusAuthorizedWhenInUse:");
            
            break;
    }
}


#pragma MapView 초기화 메소드 추가

//맵뷰 셋팅하기
-(MKMapView *) settingMapViewWithRect : (CGRect) rect{
    
    _mapView = [[MKMapView alloc]initWithFrame:rect];
    
    _mapView.delegate = self;             //딜리게이트 설정 (anotation 의 메소드를 구현한다.)
    
    [_mapView setShowsUserLocation:YES];    //내 위치 표시.

    [_mapView setMapType:MKMapTypeStandard]; //지도 형태는 기본.
    [_mapView setZoomEnabled:YES];         //줌가능
    [_mapView setScrollEnabled:YES];       //스크롤가능
    
    
    MKCoordinateRegion region;
    MKCoordinateSpan span;               //보여줄 지도가 처리하는 넓이 정의.
    span.latitudeDelta = 0.03;           //숫자가 적으면 좁은영역 까지 보임.
    span.longitudeDelta = 0.03;
    
    
    CLLocationCoordinate2D location = _mapView.userLocation.coordinate;
   
    //위치정보를 못가져왔을때 기본으로 보여줄 위치 : 서울 시청
    //location.latitude = 37.566776259291515;
    //location.longitude = 126.97784661156555;
    
    location.latitude = locationManager.location.coordinate.latitude;
    location.longitude = locationManager.location.coordinate.longitude;
    
    region.span = span;       //크기 설정.
    region.center = location;  //위치 설정
    
    [_mapView setRegion:region animated:TRUE]; //지도 뷰에 지역 설정
    [_mapView regionThatFits:region];        //지도 화면에 맞게 크기 조정
    
    //핀 호출 예제 test
    Pin *pin = [[Pin alloc] initWithCoordinate:location];
    [pin setTitle:@"나"];
    [pin setSubtitle:@"현재 나의 위치 입니디."];
    
    //핀 꼽기
    [_mapView addAnnotation:pin];
    
    
    return _mapView;
}

#pragma mark MKMapViewDelegate


//맵의 어노테이션 (마커) 표시
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
    
    NSLog(@"맵의 어노테이션 마커 표시");
    
    //현재 위치 일때
    if (annotation == self.mapView.userLocation) {
        
        //현재 위치 마커에 표시할 타이틀
        [mapView.userLocation setTitle:@"현재위치"];
        
        //현재 위치에는 커스텀 마커를 사용하지 않는다.
        return nil;
    }
    
    //현재 위치 마커가 아닐때
    Pin *pin = (Pin *)annotation;
    
    MKPinAnnotationView *dropPin = nil;      //마커준비
    static NSString *reusePinID = @"pin";   //마커 객체 재사용을 위한 id
    
    //마커 초기화
    dropPin = (MKPinAnnotationView *)[self.mapView dequeueReusableAnnotationViewWithIdentifier:reusePinID];
    
    if (dropPin == nil) {
        
        dropPin = [[MKPinAnnotationView alloc]
                   initWithAnnotation:annotation
                   reuseIdentifier:reusePinID];
    }
    
    //핀이 떨어지는 애니메이션
    dropPin.animatesDrop = YES;
    
    
    //마커 오른쪽에 (i) 모양 버튼 초기화
    UIButton *infoBtn = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
    dropPin.userInteractionEnabled = YES;
    dropPin.canShowCallout = YES;
    dropPin.rightCalloutAccessoryView = infoBtn;
    
    
    //마커 왼쪽에 표시할 이미지
    NSString *markImg = @"food1.jpg";
    
    [dropPin setPinTintColor:[UIColor greenColor]];
    
    //왼쪽 이미지 아이콘 위치 및 모양 지정
    UIImageView *leftIconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:markImg]];
    leftIconView.contentMode = UIViewContentModeScaleAspectFit;
    leftIconView.frame = CGRectMake(5, 5, dropPin.frame.size.height- 10, dropPin.frame.size.height - 10);
    dropPin.leftCalloutAccessoryView = leftIconView;
    leftIconView.layer.cornerRadius = 5;
    leftIconView.clipsToBounds = YES;
    
    //마커 리턴
    return dropPin;
}


//어노테이션의 더보기 클릭시 호출되는 메소드
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control{
    
        //핀 어노테이션 정보
        Pin * mk = (Pin*)view.annotation;
        
        //얼럿메세지 초기화
        NSString *alertMessage = [mk.title stringByAppendingString:@"\n"];
    
        //1.팝업창
        UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"정보" message:alertMessage preferredStyle:UIAlertControllerStyleAlert];
    
        //2.버튼
        UIAlertAction *ok = [UIAlertAction actionWithTitle:@"대화히기" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {

        }];
        
        //3.취소버튼
        UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"닫기" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            NSLog(@"닫기");
        }];
        
        //4.버튼 추가
        [alert addAction:ok];
        [alert addAction:cancel];
        
        
        //최초의 뷰 컨트롤러 (기본뷰컨트롤러)
        UIViewController *uvc = [[[UIApplication sharedApplication] delegate].window rootViewController];
        
        AppDelegate *app =  (AppDelegate *) [[UIApplication sharedApplication] delegate];
        
        NSLog(@"1. 최상위 rootViewController : %@" , [app visibleViewController:uvc]);
        
        //5.띄우기
        [[app visibleViewController:uvc] presentViewController:alert animated:YES completion:nil];
}


@end

 

✅위에서 생성한 클래스 호출해서 사용하기

 

workVC.h

 

#import <MapKit/MapKit.h>
#import "CoreLocation.h"
#import <UIKit/UIKit.h>
#import "Pin.h"
#import "SessionObj.h"

NS_ASSUME_NONNULL_BEGIN

@interface workVC : UIViewController <MySessionDelegate>
{
    NSTimer *timer;
}

//사용할 세션 객체
@property (strong,nonatomic) SessionObj *session;

//출근 / 퇴근 상태
@property (strong, nonatomic) IBOutlet UIBarButtonItem *workState;

//텍스트 뷰
@property (strong, nonatomic) IBOutlet UITextView *textViewForLocation;

//mapView 컨테이너
@property (strong, nonatomic) IBOutlet UIView *mapViewContainer;

//mapView
@property (strong, nonatomic) MKMapView *mapView;

@end

NS_ASSUME_NONNULL_END

 

workVC.m

 

1.CoreLocation 클래스에 있는 메소드를 이용해서 MapView를 초기화 한다.

2. 위치값(CLLocationCoordinate2D.longtude , latitude) 을 인자값으로 Pin 어노테이션(마크) 객체를 생성한다.

3. 2번에서 생성한 Pin을 MapView에 인자값으로 넣어준다.

4.MapView에 어노테이션(마커)이 표시되어 나온다.

 


#import "workVC.h"

@interface workVC ()
@end

@implementation workVC

@synthesize workState, textViewForLocation;


- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"workVC viewDidLoad 진입");
    
    //위치 정보 기록 텍스트 뷰 초기화
    textViewForLocation.text = @"";
    
    
    //NSURL Session 초기화
    self.session = [[SessionObj alloc]init];
    self.session.mySessionDelegate = self;
    
    
    //MapView 초기화 및 셋팅 [!]
    _mapView = [[MKMapView alloc]initWithFrame:self.mapViewContainer.bounds];
    _mapView = [[CoreLocation sharedSingleton] settingMapViewWithRect:self.mapViewContainer.bounds];
    [self.view addSubview:_mapView];
    
    
     CLLocationCoordinate2D location = _mapView.userLocation.coordinate;
    
     //신촌역 위, 경도
     location.latitude = 37.559865651863895;
     location.longitude = 126.94259995439766;
     
     //핀 호출 예제 test
     Pin *pin = [[Pin alloc] initWithCoordinate:location];
     [pin setTitle:@"신촌역"];
     [pin setSubtitle:@"신촌역 입니다."];
     
     //핀 꼽기
     [_mapView addAnnotation:pin];

}


- (IBAction)goToWorkAction:(id)sender {
    
    NSLog(@"(근태) goToWorkAction 진입");
    
    NSString *title =  workState.title;
    
    if ([title isEqualToString:@"출근"]) {
        
        
        //참조 카운터 +
        [CoreLocation sharedSingleton].refNum += 1;
        
        //1,타이머 객체 생성
        NSString *str = @"Timer";
        timer = [NSTimer scheduledTimerWithTimeInterval:3.0
                        target:self
                        selector:@selector(updateMethod:)
                        userInfo:str
                        repeats:YES];
        
        [workState setTitle:@"퇴근"];
    
        
    }else{
        
         [workState setTitle:@"출근"];
    
         //타이머 종료
         [timer invalidate];
        
         //참조 카운터 -
         [CoreLocation sharedSingleton].refNum -= 1;
         NSInteger refNum = [[CoreLocation sharedSingleton] refNum];
         NSLog(@"CoreLocation : 참조 카운터 : %d" , (int)refNum);
    }
    
}

//타이머 업데이트 메소드
- (void) updateMethod:(NSTimer *)incomingTimer {
        
    NSLog(@"(근태) Inside update method");
    
    //위도
    CLLocationDegrees latitude =
        [[[[CoreLocation sharedSingleton] locationManager] location] coordinate].latitude;
    
    //경도
    CLLocationDegrees longitude =
        [[[[CoreLocation sharedSingleton] locationManager] location] coordinate].longitude;
    
    
    NSString *result = [NSString stringWithFormat:@"위도(lat) : %f , 경도(long) : %f \n" ,latitude ,longitude];
    
    //텍스트뷰 위치 정보 갱신
    textViewForLocation.text = [textViewForLocation.text stringByAppendingString:result];
    
    NSURL *testurl = [NSURL URLWithString:@"https://reqres.in/api/users/7"];
    [self.session startWithURL:testurl];
    
}


//통신 세션이 완료되면 호출되는 델리게이트 메소드 - - 결과에 따라 처리해줘야함
-(void)getResultDataFromMySessionData{
    
    NSLog(@" (근태) getResultDataFromMySessionData viewDidLoad 메소드 진입");
    NSLog(@"session.resultCode : %d" , self.session.resultCode);
    NSLog(@"session.finishCode : %d" , self.session.finishCode);
    NSString *resultString =  [NSString stringWithFormat:@"%@", self.session.resultData];
    NSLog(@"resultData : %@" , resultString);
    
}


@end

 

 

 

 

 

2020_02_10_CorelocationNetwork.zip
2.72MB