如何在 Mac 上用 C# 製作 Android 和 iOS 應用程序

已發表: 2022-03-11

曾幾何時,有一家公司擁有所有最好的工具,為他們的平台編寫軟件非常棒。 但慢慢地,他們對自己的問題變得漠不關心。 當他們的系統崩潰時,他們並沒有驚慌失措,而是接受了宇宙的這種狀態作為生命的事實。 他們相信他們的節目本身就是完美的,寧靜而優雅,他們的目的不言而喻。

哦,男孩,如果他們只知道他們錯了……

當他們意識到自己的錯誤並且他們的 CEO 哭著要帶回所有離開他們平台並揚帆遠航的開發人員時,已經遲到了。 這家公司就是微軟,我相信他們的命運已經註定,他們會慢慢地但肯定會從技術領域的最前沿消失。

我很高興我錯了!

在過去的幾年裡,微軟已經從袖子裡拿出了幾張王牌。 是的,他們搞砸了 Skype(我仍然討厭他們),在智能手機上失敗了,在平板電腦上幾乎成功了。 但是,他們也做了一些非常了不起的事情。 他們放棄了封閉的帝國方法,開源了 .NET,加入了 Linux 基金會,發布了適用於 Linux 的 SQL Server,並創建了這個名為 Visual Studio for Mac 的偉大新工具。

沒錯,一個真正的Microsoft IDE,不是用於 Windows,而是用於 Mac。 想像一下!

適用於 Mac 的 Visual Studio

在 Mac 上使用 C# 編寫您的第一個跨平台 Android 和 iOS 應用程序

您可以使用 Visual Studio for Mac 創建幾乎任何類型的應用程序。 它可以是 iOS、tvOS、Android、Mac、.NET Core,甚至是 ASP.NET。 由於所有酷孩子現在都在編寫移動應用程序,讓我們看看在 Visual Studio for Mac 中創建將在 Android 和 iOS 上運行的 C# 應用程序需要什麼。

您需要做的第一件事是選擇應用程序模板。 讓我們從一個簡單的“單一視圖應用程序”開始。

單視圖應用程序。

填寫包名稱並引導您的應用程序後,Visual Studio 將創建一個包含三個項目的解決方案。 第一個項目將是一個共享庫,您應該在其中保留與平台無關的代碼,另外兩個將是 Android 和 iOS 應用程序。

入門。

您可以使用“運行”菜單或應用程序欄中的命令來啟動您的應用程序。

你好世界,點擊我!

記錄兩次點擊。

恭喜! 您現在是一名 iOS 和 Android 開發人員,儘管您從未編寫過一行 Objective-C、Swift 或 Java 代碼。

不過,我們的應用程序還沒有真正取得太多成就。 讓我們讓事情變得更有趣,並結合地圖和位置服務。

使用地圖和定位服務

請記住,用於 Mac 的 VS 仍處於“預覽”狀態,您在使用它時找不到太多幫助和文檔。 關於如何做事的最佳參考仍然是官方 Xamarin 文檔。

Visual Studio For Mac 不使用與你可能在 PC 上看到的 Xamarin 工具相同的解決方案和應用程序結構。 在大多數情況下,您需要嘗試並繞過一些障礙才能使他們的示例正常工作。 讓我們希望,一旦 VS for Mac 的最終版本發布,Microsoft 將保持領先地位,並提供令人敬畏的 MSDN 資源集合。

在 iOS 上顯示當前位置

訪問移動設備資源(例如當前位置)需要用戶“手動”授予您的應用程序使用這些資源的權限。 iOS 使用文件info.plist來存儲這些設置。 VS for Mac 提供了一個可視化的界面來編輯這個文件。 我們需要做的第一件事是為名為NSLocationWhenInUseUsageDescription的設置添加一個值。

在使用說明中添加位置值。

注意:設置屬性名稱時,VS 會顯示“NSLocationWhenInUseUsageDescription”的長名稱。 這是意料之中的,不用擔心。

我們的引導應用程序是使用一個簡單的按鈕創建的,該按鈕用於計算點擊次數。 您要做的第一件事是將其刪除並用地圖替換屏幕內容。 為此,請在解決方案瀏覽器中查找Main.storyboard文件並雙擊它以在編輯器中打開它。

故事板是應用程序用戶界面的可視化表示,顯示內容屏幕以及這些屏幕之間的連接。 故事板由一系列場景組成,每個場景代表一個視圖控制器及其視圖; 場景由 segue 對象連接,這些對象表示兩個視圖控制器之間的轉換。

情節提要由 Apple 引入並被 Xamarin 採用。 有關詳細信息,請參閱 Apple 文檔或 Xamarin 文檔。

移除按鈕並將地圖視圖組件添加到頁面。

移除地圖視圖組件。

確保正確命名您的“mapView”組件。

命名地圖視圖組件

現在剩下的就是清理ViewController.cs文件,並修改ViewDidLoad()方法以匹配以下內容:

 using CoreLocation; public override void ViewDidLoad() { base.ViewDidLoad(); // Perform any additional setup after loading the view, typically from a nib. CLLocationManager locationManager = new CLLocationManager(); locationManager.RequestWhenInUseAuthorization(); mapView.ShowsUserLocation = true; }

您可以使用“快速修復”功能讓 VS 自動添加對 CoreLocation 庫的引用,也可以手動添加它。

運行 iOS 應用程序後,您應該會看到訪問您的位置的請求。 一旦獲得許可,您的地圖將加載一個標準的藍點,顯示您所在的位置(或您使用 iOS 模擬器偽裝的位置 :))。

允許在應用程序中使用位置。

在 Android 上顯示當前位置

不幸的是,谷歌和微軟決定讓這個簡單的任務比 iOS 更複雜一些。 為了在 Android 應用程序中使用地圖,您需要創建 Google Maps API 密鑰,並將其添加到您的AndroidManifest.xml文件中。

Xamarin 開發人員為獲取 Google Maps API 密鑰創建了一個非常簡單的指南。 在繼續之前,請按照他們指南中的步驟操作。 完成後,您的AndroidManifest.xml應包含如下設置:

 <meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="YOUR KEY" />

您現在已準備好將地圖添加到您的應用程序。

VS for Mac 的偉大之處在於它由 NuGet 提供支持,就像它的老大哥一樣。 由於默認情況下不包含地圖處理庫,因此您需要安裝Xamarin.Forms.Maps包。

安裝 Xamarin.Forms.Maps

但是,沒有“地圖視圖”組件可以拖到“活動”中。 相反,將地圖添加到屏幕需要手動更改 Resources->layout->Main.axml 文件。 您可以使用設計器視圖刪除之前創建的按鈕,然後切換到“代碼視圖”並在您的LinearLayout中添加以下片段代碼:

 <fragment xmlns:andro android: android:layout_width="match_parent" android:layout_height="match_parent" class="com.google.android.gms.maps.MapFragment" />

與 iOS 一樣,您需要配置您的應用程序以請求適當的權限。 為此,請打開AndroidManifest.xml進行編輯,然後單擊編輯器左下方的“應用程序”按鈕。 VS 將向您顯示用於設置這些值的可視界面。 您需要啟用其中一些,如下所示。

啟用權限。

現在是時候編寫一些真正的代碼了。 找到MainActivity.cs文件,打開它進行編輯,並進行以下更改:

添加命名空間引用:

 using Android.Gms.Maps.Model; using Android.Gms.Maps; using Android.Locations; Make your MainActivity also a ILocationListener. public class MainActivity : Activity, ILocationListener Implement the ILocationListener methods within your MainActivity: public void OnProviderEnabled(string provider) {} public void OnProviderDisabled(string provider) {} public void OnStatusChanged(string provider, Availability status, Bundle extras) {} public void OnLocationChanged(Android.Locations.Location location) { LatLng latLng = new LatLng(location.Latitude, location.Longitude); CameraPosition.Builder builder = CameraPosition.InvokeBuilder(); builder.Target(latLng); builder.Zoom(15); builder.Bearing(155); builder.Tilt(10); CameraPosition cameraPosition = builder.Build(); CameraUpdate cameraUpdate = CameraUpdateFactory.NewCameraPosition(cameraPosition); MapFragment mapFrag = (MapFragment)FragmentManager.FindFragmentById(Resource.Id.map); GoogleMap map = mapFrag.Map; if (map != null) { map.MoveCamera(cameraUpdate); } }

添加以下兩個變量作為類級變量:

 LocationManager locMgr; string locationProvider;

並清理OnCreate()方法,如下所示:

 protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); // Set our view from the "main" layout resource SetContentView(Resource.Layout.Main); locMgr = GetSystemService(LocationService) as LocationManager; Criteria locationCriteria = new Criteria(); locationCriteria.Accuracy = Accuracy.Coarse; locationCriteria.PowerRequirement = Power.Medium; locationProvider = locMgr.GetBestProvider(locationCriteria, true); locMgr.RequestLocationUpdates(locationProvider, 2000, 1, this); }

通過從OnCreate()方法中調用 GetSystemService,您的MainActivity將作為ILocationListener激活,從而能夠處理上面列出的所有事件。

運行您的 Android 應用程序,您應該將地圖定位到您的位置,類似於下圖。

位於薩拉熱窩的地圖。

使用適用於 iOS 和 Android 的共享庫

VS for Mac 的最大特點之一是可以在 iOS 和 Android 應用程序之間共享代碼。 理想情況下,我們可以將應用程序的所有業務邏輯放在一個共享庫中,將任何 iOS 和 Android 特定代碼限制為 UI 的一部分。

讓我們創建一個共享類,它將異步執行 HTTP 請求並在調試控制台中顯示內容。

使用以下代碼在名為RestClient.cs的共享庫中創建一個新類文件:

(確保使用項目中的正確命名空間)

 using System; using System.Net; namespace testshared { public delegate void callback(string responseText); class ReqState { public ReqState(HttpWebRequest req, callback cb) { request = req; callback = cb; } public HttpWebRequest request { get; set; } public callback callback; } public class RestClient { public RestClient() {} public void FetchPage(string url, callback cb) { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.BeginGetResponse(new AsyncCallback(FinishWebRequest), new ReqState(request, cb)); } private void FinishWebRequest(IAsyncResult result) { ReqState reqState = (result.AsyncState as ReqState); HttpWebResponse response = reqState.request.EndGetResponse(result) as HttpWebResponse; using (var reader = new System.IO.StreamReader(response.GetResponseStream())) { string responseText = reader.ReadToEnd(); reqState.callback(responseText); } } } }

在 iOS 上使用庫

修改 iOS 項目中的ViewController.cs文件以匹配以下代碼:

(確保使用項目中的正確命名空間)

 using System; using UIKit; using System.Diagnostics; namespace testshared.iOS { public partial class ViewController : UIViewController { RestClient rest = new RestClient(); public ViewController(IntPtr handle) : base(handle) {} public override void ViewDidLoad() { base.ViewDidLoad(); // Perform any additional setup after loading the view, typically from a nib. Button.AccessibilityIdentifier = "myButton"; Button.TouchUpInside += delegate { Button.SetTitle("Loading...", UIControlState.Normal); rest.FetchPage("http://www.google.com", doneCallback); }; } public void doneCallback(string content) { InvokeOnMainThread(() => { Debug.Write(content); Button.SetTitle("All Done", UIControlState.Normal); }); } public override void DidReceiveMemoryWarning() { base.DidReceiveMemoryWarning(); // Release any cached data, images, etc that aren't in use. } } }

運行您的 iOS 應用程序,單擊按鈕並檢查 Visual Studio 中的“應用程序輸出”選項卡。 它應該顯示如下內容:

應用程序輸出選項卡。

在 Android 上使用庫

Android 應用程序所需的更改與 iOS 應用程序所需的更改非常相似。 修改MainActivity.cs文件以匹配以下內容:

(確保使用項目中的正確命名空間)

 using Android.App; using Android.Widget; using Android.OS; namespace testshared.Droid { [Activity(Label = "testshared", MainLauncher = true, Icon = "@mipmap/icon")] public class MainActivity : Activity { RestClient rest = new RestClient(); protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); // Set our view from the "main" layout resource SetContentView(Resource.Layout.Main); // Get our button from the layout resource, // and attach an event to it Button button = FindViewById<Button>(Resource.Id.myButton); button.Click += delegate { button.Text = $"Loading..."; rest.FetchPage("http://www.google.com", doneCallback); }; } public void doneCallback(string content) { RunOnUiThread(() => { Button button = FindViewById<Button>(Resource.Id.myButton); button.Text = "All done"; System.Diagnostics.Debug.WriteLine(content); }); } } }

注意: Android 和 iOS 兩個平台的系統架構都要求所有 UI 交互都發生在主應用程序線程上。 這意味著對 UI 元素的任何更改也應該在主線程中發生。 這就是RunOnUiThreadInvokeOnMainThread的用武之地。由於 HTTP 請求在單獨的線程中執行,並且在主線程之外調用了doneCallback() ,因此我們必須使用這些方法才能訪問按鈕並更改標籤。

C# 開發人員正在接管 Android 和 iOS

Visual Studio for Mac 仍有一些問題需要解決,但從第一眼看到它,我對它的未來感到非常興奮。 對移動應用程序的需求每天都在增長,借助 Visual Studio for Mac,Microsoft 進一步使一支優秀的 C# 開發人員大軍能夠滿足這一需求。

Swift 和 Java/JVM 現在在我們的移動設備開發環境之戰中有了一個新的、非常強大的競爭對手。
相關: .NET Core - 走向狂野和開源。 微軟,你怎麼花了這麼長時間?!
相關: Dart 語言:當 Java 和 C# 不夠鋒利時