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
'아이폰 개발 > ios 개념&튜토리얼' 카테고리의 다른 글
iOS KeyChain 예제 - 키체인 (0) | 2021.02.16 |
---|---|
iOS Geofencing 예제 - 특정위치 안에 들어갔을때 알림(NSCoding, UserNotification) (1) | 2021.02.15 |
iOS 배달앱 개발 예제(1) - (feat. CoreLocation, NSTimer, NSURLSession) (0) | 2021.02.09 |
iOS CoreLocation 공통 모듈 - 싱글톤 (0) | 2021.02.09 |
iOS 최상위뷰 체크 예제 - Curent RootViewController (0) | 2021.02.08 |