Objective-C - 定位同時將地圖對應成手機前方

「就是有很多人搞不清楚東西南北…」,老闆一臉平淡的說著

「好吧,按下按鈕之後,讓使用者看著手機知道手機畫面的上方就是他面朝方向…收到」碼農準備出動!

我這邊的用法呢,因為 Map 有 Map 的 View ,LocationManager 也有自己獨立的 Class ,所以用起來會和網路上的稍為有一點不同,以下是作法和說明

流程上大致是當畫面上有需要更新這一個資訊時,再開啟更新GPS資料,一但有抓到了新的資料後就把更新關掉,以節省應用程式使用的資源

在ViewController裡的某個Button內容:

- (IBAction)onClickMyLocation:(id)sender {
    // 檢查是否有取得位置的權限
    [[PLPermissionManager sharedInstance] checkLoctionPermission:^(BOOL access){
        if(access){
            self.mapView.myLocationEnabled = YES;
            // 先更新 Location 取得 latitude 和 longitude.
            [[PLPermissionManager sharedInstance] updatingLocation:^(CLLocationCoordinate2D updateLocationCoordinate, NSError *error) {
                    // 取得經緯度後再取一次方向
                    [[PLPermissionManager sharedInstance] updatingHeading:^(CLLocationDirection updateHeading, NSError *error) {
                         // 開始重繪地圖
                        [self moveToLocation:updateLocationCoordinate
                                            zoom:[self.delegate zoomWithCurrentLocation]
                                         bearing:updateHeading
                                    selectMarker:nil];
                        }];
            }];
            
        }
        else{
            // 如果不允許的話,就顯示訊息告訴使用者blablablabla...
        }
    }];
}

重繪地圖的方式:

-(void)moveToLocation:(CLLocationCoordinate2D)locationCoordinate
                 zoom:(float)zoom
              bearing:(CLLocationDirection)bearing
         selectMarker:(GMSMarker*)marker {
    dispatch_async(dispatch_get_main_queue(), ^{
        if (CLLocationCoordinate2DIsValid(locationCoordinate))
        {
            // 確定經緯度可用後,使用 Google Maps 提供的方法(animateToCameraPosition)對 mapView 進行更新
            // 因為我不需要改viewAngle,所以給原本的預設值。
            GMSCameraPosition* position = [GMSCameraPosition cameraWithTarget:locationCoordinate zoom:zoom bearing:bearing viewingAngle:self.mapView.camera.viewingAngle];
            [self.mapView animateToCameraPosition:position];
            
        }
        else
        {
            // 如果檢查不通過,可以顯示錯誤訊息blablabla...
        }
    });
}

至於實際上的UpdatingLocation和UpdatingHeading的作法也沒有很複雜:

--  LocationPermission.h 檔
@property (nonatomic,strong) CLLocationManager *locationManager;

-(void)checkLoctionPermission:(void(^)(BOOL access))access;
-(void)updatingLocation:(void(^)(CLLocationCoordinate2D updateLocationCoordinate,NSError* error))updateLocationBlock;
-(void)updatingHeading:(void(^)(CLLocationDirection updateHeading, NSError* error))updateHeadingBlock;
---

--  LocationPermission.m 檔
typedef void(^UpdateLocationBlock)(CLLocationCoordinate2D updateLocationCoordinate,NSError* error);
typedef void(^UpdateHeadingBlock)(CLLocationDirection updateHeading,NSError* error);

@interface PLPermissionManager ()<CLLocationManagerDelegate>

@property (nonatomic,copy) UpdateLocationBlock updateLocationBlock;
@property (nonatomic,copy) UpdateHeadingBlock updateHeadingBlock;
@end

@implementation PLPermissionManager

-(void)updatingLocation:(void(^)(CLLocationCoordinate2D updateLocationCoordinate,NSError* error))updateLocationBlock{
    self.updateLocationBlock = updateLocationBlock; // 更新完之後的 CallBack
    [self.locationManager startUpdatingLocation];   // 更新位置
}
// 當 startUpdatingLocation 被呼叫後,這個 Function 會不斷被觸發。
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
    if(self.updateLocationBlock){
        [manager stopUpdatingLocation];
        CLLocationCoordinate2D updateCoordinate = kCLLocationCoordinate2DInvalid;
        if(0 != locations.count){
            updateCoordinate = [locations lastObject].coordinate;
            
        }
        self.updateLocationBlock(updateCoordinate,nil);
        self.updateLocationBlock = nil;
    }
}
-(void)updatingHeading:(void (^)(CLLocationDirection updateDirection, NSError * error))updateHeadingBlock {
    self.updateHeadingBlock = updateHeadingBlock; // 更新完之後的 CallBack
    [self.locationManager startUpdatingHeading];  // 更新方向
}
// 當 startUpdatingHeading 被呼叫後,這個 Function 會不斷被觸發。
-(void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading {
    if(self.updateHeadingBlock){
        [manager stopUpdatingHeading];
        self.updateHeadingBlock(newHeading.magneticHeading,nil);
        self.updateHeadingBlock = nil;
    }
}

大致上的作法就是這樣,Google到的內容講的更完整豐富

其實抓到的 newHeading 可能還會有誤差,我下面貼的文章有點出這個問題,只是沒有提供解答

如果你有興趣可以再去找找

 

 

參考:

官方文件

Google到的中文教學