逐步解說 - Xamarin.iOS 中的背景位置

在此範例中,我們將建置 iOS 位置應用程式,以列印目前位置的相關信息:緯度、經度和其他參數到畫面。 此應用程式將示範如何在應用程式為作用中或背景時正確執行位置更新。

本逐步解說說明一些重要的背景概念,包括將應用程式註冊為背景必要應用程式、在應用程式背景設定背景時暫停UI更新,以及使用 WillEnterBackgroundWillEnterForegroundAppDelegate 方法。

應用程式設定

  1. 首先,建立新的 iOS > 應用程式>單一檢視應用程式 (C#)。 將其命名為 [位置],並確定已選取 [iPad] 和 [i 電話]。

  2. 位置應用程式在 iOS 中限定為背景必要應用程式。 編輯 專案的 Info.plist 檔案,以將應用程式註冊為位置應用程式。

    在 [方案總管] 底下,按兩下 Info.plist 檔案加以開啟,然後捲動至清單底部。 選取 [啟用背景模式] 和 [位置] 更新 複選框。

    在 Visual Studio for Mac 中,看起來會像這樣:

    將 [啟用背景模式] 和 [位置 更新] 複選框進行檢查

    在 Visual Studio 中, 需要手動更新 Info.plist ,方法是新增下列機碼/值組:

    <key>UIBackgroundModes</key>
    <array>
      <string>location</string>
    </array>
    
  3. 現在應用程式已註冊,它可以從裝置取得位置數據。 在 iOS 中,類別 CLLocationManager 可用來存取位置資訊,並可引發提供位置更新的事件。

  4. 在程式代碼中,建立名為 LocationManager 的新類別,為各種畫面和程序代碼提供單一位置來訂閱位置更新。 在類別中LocationManager,建立名為 LocMgrCLLocationManager 實例:

    public class LocationManager
    {
        protected CLLocationManager locMgr;
    
        public LocationManager () {
            this.locMgr = new CLLocationManager();
            this.locMgr.PausesLocationUpdatesAutomatically = false;
    
            // iOS 8 has additional permissions requirements
            if (UIDevice.CurrentDevice.CheckSystemVersion (8, 0)) {
                locMgr.RequestAlwaysAuthorization (); // works in background
                //locMgr.RequestWhenInUseAuthorization (); // only in foreground
            }
    
            if (UIDevice.CurrentDevice.CheckSystemVersion (9, 0)) {
                locMgr.AllowsBackgroundLocationUpdates = true;
            }
        }
    
        public CLLocationManager LocMgr {
            get { return this.locMgr; }
        }
    }
    

    上述程式代碼會設定 CLLocationManager 類別的一些屬性和權限:

    • PausesLocationUpdatesAutomatically – 這是一種布爾值,可根據系統是否允許暫停位置更新來設定。 在某些裝置上,它預設為 true,這可能會導致裝置在大約 15 分鐘後停止取得背景位置更新。
    • RequestAlwaysAuthorization - 您應該傳遞這個方法,為應用程式使用者提供選項,以允許在背景存取位置。 RequestWhenInUseAuthorization 如果您想要提供用戶選項,只有在應用程式位於前景時,才能存取位置,也可以傳遞。
    • AllowsBackgroundLocationUpdates – 這是 iOS 9 中引進的 Boolean 屬性,可設定為允許應用程式在暫停時接收位置更新。

    重要

    iOS 8 (及更新版)也需要 Info.plist 檔案中的專案,才能在授權要求中向用戶顯示。

  5. 針對應用程式需要的許可權類型新增 Info.plist 金鑰 – NSLocationAlwaysUsageDescriptionNSLocationWhenInUseUsageDescription和 / 或 NSLocationAlwaysAndWhenInUseUsageDescription – ,其字串將會在要求位置資料存取的警示中向使用者顯示。

  6. iOS 9 要求使用 AllowsBackgroundLocationUpdatesInfo.plist 時,包含具有 值 location的索引鍵UIBackgroundModes。 如果您已完成本逐步解說的步驟 2,這應該已在 Info.plist 檔案中。

  7. 在類別內 LocationManager ,使用下列程式代碼建立名為 StartLocationUpdates 的方法。 此程式代碼示範如何從 CLLocationManager開始接收位置更新:

    if (CLLocationManager.LocationServicesEnabled) {
        //set the desired accuracy, in meters
        LocMgr.DesiredAccuracy = 1;
        LocMgr.LocationsUpdated += (object sender, CLLocationsUpdatedEventArgs e) =>
        {
            // fire our custom Location Updated event
            LocationUpdated (this, new LocationUpdatedEventArgs (e.Locations [e.Locations.Length - 1]));
        };
        LocMgr.StartUpdatingLocation();
    }
    

    這個方法中有數個重要事情發生。 首先,我們會執行檢查,以查看應用程式是否可以存取裝置上的位置數據。 我們會藉由在上呼叫 LocationServicesEnabled 來確認這一 CLLocationManager點。 如果使用者拒絕應用程式存取位置資訊,這個方法會傳回 false

  8. 接下來,告訴位置管理員更新的頻率。 CLLocationManager 提供許多選項來篩選和設定位置數據,包括更新的頻率。 在此範例中,每當計量的位置變更時,將 設定 DesiredAccuracy 為更新。 如需設定位置更新頻率和其他喜好設定的詳細資訊,請參閱 Apple 檔中的 CLLocationManager 類別參考

  9. 最後,呼叫 StartUpdatingLocationCLLocationManager 實例。 這會告訴位置管理員取得目前位置的初始修正,並開始傳送更新

到目前為止,已建立位置管理員、使用我們想要接收的數據類型進行設定,並已決定初始位置。 現在程式代碼必須將位置數據轉譯至使用者介面。 我們可以使用採用 CLLocation 作為自變數的自定義事件來執行此動作:

// event for the location changing
public event EventHandler<LocationUpdatedEventArgs>LocationUpdated = delegate { };

下一個步驟是從 訂閱位置更新 CLLocationManager,並在新的位置數據可用時引發自定義 LocationUpdated 事件,以自變數的形式傳入位置。 若要這樣做,請建立新的類別 LocationUpdateEventArgs.cs。 此程式代碼可在主要應用程式中存取,並在引發事件時傳回裝置位置:

public class LocationUpdatedEventArgs : EventArgs
{
    CLLocation location;

    public LocationUpdatedEventArgs(CLLocation location)
    {
       this.location = location;
    }

    public CLLocation Location
    {
       get { return location; }
    }
}

使用者介面

  1. 使用 Xcode 介面產生器來建置顯示位置信息的畫面。 按兩下 Main.storyboard 檔案以開始。

    在分鏡腳本上,將數個捲標拖曳到畫面上,以作為位置資訊的佔位符。 在此範例中,有緯度、經度、高度、路線和速度的標籤。

    如需詳細資訊,請參閱 使用 Xcode 設計使用者介面。

  2. 在 Solution Pad 中,按兩下 ViewController.cs 檔案並加以編輯,以建立 LocationManager 的新實例並加以呼叫 StartLocationUpdates。 將程式代碼變更為如下所示:

    #region Computed Properties
    public static bool UserInterfaceIdiomIsPhone {
        get { return UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Phone; }
    }
    
    public static LocationManager Manager { get; set;}
    #endregion
    
    #region Constructors
    public ViewController (IntPtr handle) : base (handle)
    {
    // As soon as the app is done launching, begin generating location updates in the location manager
        Manager = new LocationManager();
        Manager.StartLocationUpdates();
    }
    
    #endregion
    

    這會啟動應用程式啟動時的位置更新,但不會顯示任何數據。

  3. 現在收到位置更新,請使用位置資訊來更新畫面。 下列方法會從我們的 LocationUpdated 事件取得位置,並在UI中顯示它:

    #region Public Methods
    public void HandleLocationChanged (object sender, LocationUpdatedEventArgs e)
    {
        // Handle foreground updates
        CLLocation location = e.Location;
    
        LblAltitude.Text = location.Altitude + " meters";
        LblLongitude.Text = location.Coordinate.Longitude.ToString ();
        LblLatitude.Text = location.Coordinate.Latitude.ToString ();
        LblCourse.Text = location.Course.ToString ();
        LblSpeed.Text = location.Speed.ToString ();
    
        Console.WriteLine ("foreground updated");
    }
    #endregion
    

我們仍然需要訂閱 LocationUpdated AppDelegate 中的事件,並呼叫新的 方法來更新 UI。 在呼叫之後StartLocationUpdates新增ViewDidLoad,下列程式代碼:

public override void ViewDidLoad ()
{
    base.ViewDidLoad ();

    // It is better to handle this with notifications, so that the UI updates
    // resume when the application re-enters the foreground!
    Manager.LocationUpdated += HandleLocationChanged;

}

現在,當應用程式執行時,它看起來應該像這樣:

範例應用程式執行

處理作用中和背景狀態

  1. 應用程式會在前景和使用中時輸出位置更新。 若要示範當應用程式進入背景時會發生什麼情況,請覆寫 AppDelegate 追蹤應用程式狀態變更的方法,讓應用程式在前景與背景之間轉換時寫入主控台:

    public override void DidEnterBackground (UIApplication application)
    {
        Console.WriteLine ("App entering background state.");
    }
    
    public override void WillEnterForeground (UIApplication application)
    {
        Console.WriteLine ("App will enter foreground");
    }
    

    在 中 LocationManager 新增下列程式代碼,以持續將更新的位置數據列印至應用程式輸出,以確認位置資訊仍可在背景中使用:

    public class LocationManager
    {
        public LocationManager ()
        {
        ...
        LocationUpdated += PrintLocation;
        }
        ...
    
        //This will keep going in the background and the foreground
        public void PrintLocation (object sender, LocationUpdatedEventArgs e) {
        CLLocation location = e.Location;
        Console.WriteLine ("Altitude: " + location.Altitude + " meters");
        Console.WriteLine ("Longitude: " + location.Coordinate.Longitude);
        Console.WriteLine ("Latitude: " + location.Coordinate.Latitude);
        Console.WriteLine ("Course: " + location.Course);
        Console.WriteLine ("Speed: " + location.Speed);
        }
    }
    
  2. 程式代碼有一個剩餘的問題:嘗試在背景應用程式背景時更新UI會導致iOS終止它。 當應用程式進入背景時,程式代碼必須取消訂閱位置更新,並停止更新UI。

    當應用程式即將轉換成不同的應用程式狀態時,iOS 會提供通知。 在此情況下,我們可以訂閱 ObserveDidEnterBackground 通知。

    下列代碼段示範如何使用通知,讓檢視知道何時停止 UI 更新。 這會進入 ViewDidLoad

    UIApplication.Notifications.ObserveDidEnterBackground ((sender, args) => {
        Manager.LocationUpdated -= HandleLocationChanged;
    });
    

    當應用程式執行時,輸出看起來會像這樣:

    控制台中位置輸出的範例

  3. 當在前景作業時,應用程式會列印位置更新到畫面,並在背景作業時繼續將數據列印至應用程式輸出視窗。

只有一個未解決的問題:畫面會在應用程式第一次載入時啟動UI更新,但無法得知應用程式何時重新進入前景。 如果背景應用程式回到前景,UI 更新將不會繼續。

若要修正此問題,請在另一個通知內巢狀啟動UI更新的呼叫,這會在應用程式進入作用中狀態時引發:

UIApplication.Notifications.ObserveDidBecomeActive ((sender, args) => {
  Manager.LocationUpdated += HandleLocationChanged;
});

現在 UI 會在應用程式第一次啟動時開始更新,並在應用程式回到前景時繼續更新。

在本逐步解說中,我們建置了行為良好、背景感知的 iOS 應用程式,以將位置數據列印到畫面和應用程式輸出視窗。