如何在 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# 不够锋利时