原文:Building iOS Apps with Xamarin and Visual Studio
作者:(Bill Morefield](https://www.raywenderlich.com/u/bmorefield)
译者:kmyhy
一说开发 iOS app,你立马就会想到苹果的开发语言 Objective C/Swift 和 Xcode。但是,这并不是唯一的选择,我们完全可以使用别的语言和框架。
一种主流的替换方案是 Xamarin,这是一个跨平台框架,允许你开发 iOS、Android 和 OSX、Windows app,它使用的是 C# 和 Visual Studio。最大的好处在于,Xamarin 允许你在 iOS 和 Android app 间共享代码。
Xamarin 与其他跨平台框架相比有一个最大的好处:使用 Xamarin,你的项目能够编译出本地代码,并使用本地 APIs。也就是说,用 Xamarin 编写的 app 和用 Xcode 编出来的 app 毫无区别。更多细节,请阅读这篇文章Xamarin vs. Native App Development。
但是 Xamarin 还有一个巨大的缺点,就是它的价格。每个平台 1000 美元/年的价格,可能要让你戒掉每天都喝的拿铁或法布奇诺才能负担得起……程序员如果不喝咖啡是件很危险的事情。因为高昂的价格,Xamarin 至今只在预算丰沛的企业项目中才会采用。
但是,自从微软收购 Xamarin 之后这种情况发生了改变,微软将它集成到新版的 Visual Studio 中,甚至是免费的社区版。而社区版对个人开发者和小团队是免费的。
免费?真是太好了!
除了价格以外(或者根本不考虑价格),Xamarin 还拥有其它好处,包括允许程序员:
- 利用原有的 c# 库和工具编写手机 app。
- 在不同平台的 app 之间共用代码。
- 在 ASP.net 后台和前端 app 之间共用代码。
Xamarin 还允许你根据你的需求改变工具。如果想最大化地跨平台共用代码,请使用 Xamarin Forms,它非常适合于不针对特定平台的特性或者需要单独定制 UI 的 app。
如果你的 app 需要调用针对特定平台的功能或界面,请使用 Xamarin.iOS、Xamarin.Android 或其他平台的模块,这样就可以直接调用本地 API 和框架。这些模块能够创建高度定制的 UI,同时在通用代码上支持跨平台。
在本教程中,你将使用 Xamarin.iOS 创建 iPhone app,这个 app 用于列出用户的照片库。
本教程不需要任何 iOS 或 Xamarin 开发经验,但最好具备 C# 基础。
开始
要用 Xamarin 和 Visual Studio 开发 iOS app,理想的情况下你需要两台电脑:
- 一台 Windows 电脑,用于运行 Visual Studio,并编写代码。
- 一台装有 Xcode 的 Mac 电脑,用于充当 buid 主机。并不是专门用它来进行编译,而是在开发和调试过程中,用它来接受自 Windows 电脑发出网络请求。
两台电脑的物理距离越近越好,因为当你在 windows 电脑上编译和运行时,iOS 模拟器会在 Mac 电脑上运行。
你可能会问“如果我没有两台电脑怎么办?”
对于只使用 Mac 平台的用户,Xamarin 也提供了 OSX 下的 IDE,但本教程主要目的是演示全新的 Visual Studio。如果你不喜欢这样,你可以在 Mac 上跑一个 Windows 虚拟机。VMWare Fushion 或者免费开源的 VirtualBox 都可以。
如果使用 Windows 虚拟机,你需要保证 Windows 能够通过网络访问 Mac。也就是说,你需要在 Windows 下 Ping 到 Mac 的 IP 地址。
对于纯 Windows 用户,那么现在、马上去买一台 Mac。我在这里等你!如果不行,就使用 MacinCloud 或 Macminicolo 之类的云服务吧。
本教程假设你有单独的 Mac 和 Windows 电脑,当然,对于在使用 Mac 下使用 Windows 主机的人来说,本教程也是适用的。
安装 Xcode 和 Xamarin
下载、安装 Xcode 到你的 Mac 电脑上,如果你还没有这样做的话。这个和从应用商店安装其他 app 并无不同,只不过有好几 G 大,需要的时间长一点。
装完 Xcode 之后,下载 Xamarin Studio 到 Mac 电脑上。不用填写 email 地址,下载是免费的。插一句:是不是觉得很爽——这不需要牺牲你的任何一种咖啡为代价!
下载完成后后,打开安装包,双击 Install Xamarin.app。接收协议条款,点击 continue。
安装器会自动找到已经安装的工具并检查当前操作系统版本。它会显示一个开发环境列表。勾上 Xamarin.iOS,点击 continue。
然后你会看到一个确认清单,列出了将要安装的内容。点击 continue。然后你会看到一个概要,以及一个启动 Xamarin Studio 的选项。直接点 Quit 完成安装。
安装 Visual Studio 和 Xamarin
在本教程中,你可以使用任意版本的 Visual Studio,甚至是免费的社区版。社区版的功能并不完全,但完全不影响你开发复杂的 app。
你的 Windows 当你必须满足 Visual Studio 的最小系统需求。要获得比较顺畅的开发体验,至少需要 3 GB 的内存。
如果你没有安装过 Visual Studio,你可以从社区版网页上点击绿色的社区版2015按钮,下载社区版的安装器。
运行安装器,开始安装,选择定制安装选项。在特性列表中,展开跨平台手机开发,然后选择 C#/.NET(Xamarin v4.0.3)(本教程编写时的版本,很可能会有不同)。
点击 Next,等待安装完成。时间有点长,你可以站起来走一下,消化掉安装 Xcode 时吃的饼干 :]
如果你已经装过 Visual Studio 但没有 Xamarin tools,进入 Windows 的 Programs and Features,找到 Visual Studio 2015,选择它,点击 Change,然后选择 Modify。
在 Cross Platform Mobile Development 下面找到 Xamarin,即 C#/.NET (Xamarin v4.0.3),勾选它,点击 Upate 以进行安装。
呼——装的东西真多,但总算是搞定了!
编写 app
打开 Visual Studio,选择 File\New\Project。在 Visual C# 下面,展开 iOS,选择 iPhone -> Single View App 模板。这个模板创建一个只有一个 view controller 的 app,view controller 是 iOS app 中用于管理视图的类。
无论 Name 还是 Solution Name,都请输入 ImageLocation。选择项目保存路径,然后点击 OK,新项目就创建好了。
Visual Studio 会提示你需要指定一台 Mac 电脑作为 Xamarin 的 buid 主机:
- 在 Mac 电脑上,打开系统偏好设置,选择共享。
- 打开远程登录。
将“允许访问”改为“仅这些用户”,然后添加将用于访问 Mac 上的 Xamarin 和 Xcode 的用户。
关闭窗口,返回 Windows 电脑。
回到 Visual Studio,在要求你指定一台 Mac 作为 build 主机时,选择你的 Mac 电脑,然后点击 Connect。输入用户名密码,点击 login。
你可以查看工具栏,检查是否已经连接成功。
从 Solution Platform 下拉框中选择 iPhone Simulator,这将自动选择 build 主机的一个模拟器。如果要改变为其他模拟器,可以点击当前模拟器右边的小箭头。
按下绿色的 Debug 箭头或者 F5 快捷键,编译运行程序。
编译完成后,你却不能在 Windows 上看到任何效果。因为它运行在你的 Mac (build 主机) 上。这就是为什么最好将两台机器尽量靠近的理由!
在前几天的 Evolve 大会上,Xamarin 宣布将推出 iOS Simulator Remoting ,它能够让你和运行在苹果 iOS 模拟器上的 app 进行交互,就像在 Windows PC 上运行了一个模拟器一样。但在目前,你仍然需要和运行在 Mac 上的模拟器打交道。
在模拟器上,你会看到一个启动画面闪现,然后显示一个空的窗口。恭喜你!你的 Xamarin 能够正常工作了。
要停止 app,可以点击红色的 Stop 按钮(Shift+F5 快捷键)。
创建 Collection View
这个 app 会显示给用户一个 Collection View,以展示用户相册中的缩略图片。Collection View 是一个 iOS 控件,以网格形式显示多个条目。
要编辑 app 故事板中的“场景”,请从解决方案管理器中打开 Main.storyboard。
在工具箱中的搜索栏中,输入 collection 字样进行过滤。将 Data View 下面的 Collection View 对拖到空白的视图中央。
选择 Collection View,你会看到它四周出现一些空心的小圆圈。如果你看到的是 T 字而不是小圆圈,请再点它一次,即可切换到小圆圈。
点击并拖动每个小圆圈直到看见蓝色线条后放开鼠标按键,控件的边缘就自动对齐到线条所在的地方。
然后设置 Collection View 的自动布局约束,自动布局约束用于告诉 app 当设备旋屏时视图应当如何重新改变大小。在故事板上边的工具栏中,点击 CONSTRAINTS 字样右边的绿色的加号按钮。这将自动为 Collection View 创建约束。
自动创建的约束大部分正确,但也需要对其中一些进行调整。在属性窗口中,切换到 Layout 标签,拉到 Constraints 一栏。
边距中的两个约束是正确的,但宽高约束不正确。删除 Width 和 Height 约束(点击它们右边的 X 按钮)。
注意,Collection View 此时变成橙色。这表明约束不正确。
点击 Collection View 以选中它。如果你看到之前一样的圆圈,再点击它一次切换到绿色的 T 字图标。点击并拖放 Collection View 上端的 T 字直到绿色的名为 Top Layout Guide 的外框处。放开鼠标左键,这将创建一条相对于视图顶部的约束。
然后,点击并向左拖放 Collection View 左边的 T 字直到看到一条蓝色的虚线。放开鼠标左键,这将创建一条相对于视图左边缘的约束。
这时,你的约束应该是这样的:
配置 Collection View Cell
看见 Collection View 中的小方块了吗?在这个方块中有一个红色的惊叹号。这就是一个 Collection View Cell,表示 Collection View 中的一个单元格。
要配置这个 cell 的大小,需要在 Collection View 中进行。选中 Collection View,上拉到 Layout 标签的顶部。在 Cell Size 小节,将其 Width 和 Height 设置为 100。
然后,点击 Collection View Cell 中的红色惊叹号,这将弹出一个提示窗口,说你还没有为 cell 分配一个 reuse identifier。因此,选中 cell,打开 Widget 标签,下拉到 Collection Reusable View 小节,将 Identifier 设置为 ImageCellIdentifier。这将让这个错误消失。
继续下拉,来到 Interaction 小节,将 Background Color 设置为 Predefined 中的蓝色。
现在,场景效果变成:
上拉到 Widget 节顶部,将 Class 设置为 PhotoCollectionImageCell。
Visual Studio 会自动创建同一类名的类,继承 UICollectionViewCell,并自动创建一个 PhotoCollectionImageCell.cs 文件。唉,什么时候 Xcode 才能和 Visual Studio 一样?!
创建 Colleciton View 数据源
你还需要创建一个类,充当 UICollectionViewDataSource,为 Collection View 提供数据。
在解决方案管理器的 ImageLocation 上右击,选择 Add \ Class, 类名为 PhotoCollectionDataSource.cs 然后点击 Add。
打开新创建的 PhotoCollectionDataSource.cs,然后在文件顶部写入:
using UIKit;
这将导入 iOS UIKit 框架。
然后是类定义:
public class PhotoCollectionDataSource : UICollectionViewDataSource
{
}
还记得你为 Collection View Cell 定义的 reuse identifier 吗?在这里我们将会用到它。在类定义中加入:
private static readonly string photoCellIdentifier = "ImageCellIdentifier";
UICollectionViewDataSource 类中有两个抽象方法必须实现。在类中加入:
public override UICollectionViewCell GetCell(UICollectionView collectionView,
NSIndexPath indexPath)
{
var imageCell = collectionView.DequeueReusableCell(photoCellIdentifier, indexPath)
as PhotoCollectionImageCell;
return imageCell;
}
public override nint GetItemsCount(UICollectionView collectionView, nint section)
{
return 7;
}
GetCell() 方法负责提供一个用于在 Collection View 中显示的 cell。
DequeueReusableCell 方法会重用那些不再使用的 cell,例如那些已经不需要在屏幕上显示的 cell,然后返回该 cell。如果没有可重用的 cell,则会创建一个新的 cell。
GetItemsCount 方法负责告诉 Collection View 需要显示多少(7 个) cell。
然后需要在 ViewController 类中添加一个 Collection View 的引用,ViewController 就是管理着 Collection View 的那个 Scene。回到 Main.storyboard,选择 Collection View,来到 Widget 标签,将 Name 设为 collectionView。
Visual Studio 会自动在 ViewController 类中创建一个名为 collectionView 的实例变量。
注意,在 ViewController.cs 中你无法看到这个实例变量。要看到这个变量,你需要点击 ViewController.cs 左边的右箭头,以打开 ViewController.designer.cs。这里才看得见 Visual Studio 为你创建的实例变量。
从解决方案管理器中打开 ViewController.cs,在类中添加如下字段:
private PhotoCollectionDataSource photoDataSource;
在 ViewDidLoad()最后,添加代码,初始化数据源并将它绑定到 Collection View:
photoDataSource = new PhotoCollectionDataSource();
collectionView.DataSource = photoDataSource;
这样,photoDataSource 就能够为 Collection View 提供数据了。
编译运行程序。你会看到 Collection View 显示了 7 个蓝色方块。
好极了—— 一切顺利!
显示照片
蓝色方块搞定了,接下来是将数据源变成从设备中获取的图片,然后在 Collection View 中显示它们。你将用 Photos 框架访问来自 Photos app 的照片、视频。
接下来,你需要在 cell 中加入一个 Image View。打开 Main.stroyboard,选择 Collection View Cell。在 Widget 标签,下拉并设置 Background Color 为默认。
在工具箱中,搜索 image view,然后拖一个 Image View 到 cell 中。
Image View 的默认大小比 cell 大,要修改其大小,选择这个 Image View 然后在 Properties \ Layout 标签的 View 小节下面,将 X 和 Y 设为 0 ,Width 和 Height 设为 100。
切换到 Widget 标签,将 Name 设置为 cellImageView。Visual Studio 会自动创建一个名为 cellImageView 的变量。
拉到 View 小节,将 Mode 设为 Aspect Fill。这将防止图片被缩放。
注意:在 PhotoCollectionImageCell.cs 中无法看到 cellImageView 变量。这个类是分部类,这个变量在另外一个文件中。
在解决方案管理器中,点击 PhotoCollectionImageCell.cs 左边的箭头,展开它。打开 PhotoCollectionImageCell.designer.cs,你将看到 cellImageView 变量声明。
这个文件是自动创建的,不要去改变它。否则,它们会在你不知道的情况下被覆盖,或者导致类和故事板之间的绑定被打断,从而导致运行时错误。
这个变量不是公有的,因此别的类无法访问它。因此,你需要提供一个访问它的方法,以便我们能够改变 Image View 上显示的图片。
打开 PhotoCollectionImageCell.cs 添加如下方法:
public void SetImage(UIImage image)
{
cellImageView.Image = image;
}
现在你可以让 PhotoCollectionDataSource 去抓取照片了。
在 PhotoCollectionDataSource.cs 的顶部:
using Photos;
在 PhotoCollectionDataSource 增加变量:
private PHFetchResult imageFetchResult;
private PHImageManager imageManager;
imageFetchResult 变量用于存储照片对应 Asset 的数组,然后通过 imageManager 对象来获取照片数据。
在 GetCell() 方法前,添加构造方法:
public PhotoCollectionDataSource()
{
imageFetchResult = PHAsset.FetchAssets(PHAssetMediaType.Image, null);
imageManager = new PHImageManager();
}
这个构造方法从 Photos app 中抓取所有图片资源,并将结果放到 imageFetchResult 变量中。然后初始化 imageManager,app 用它来查询每一张照片的具体数据。
在构造方法下面,添加析构方法,将 imageManager 对象释放:
~PhotoCollectionDataSource()
{
imageManager.Dispose();
}
在 GetItemsCount 和 GetCell 方法中用新数据源中的图片替换原来的空 cell。修改 GetItemsCount() 方法为:
public override nint GetItemsCount(UICollectionView collectionView, nint section)
{
return imageFetchResult.Count;
}
修改 GetCell 方法为:
public override UICollectionViewCell GetCell(UICollectionView collectionView,
NSIndexPath indexPath)
{
var imageCell = collectionView.DequeueReusableCell(photoCellIdentifier, indexPath)
as PhotoCollectionImageCell;
// 1
var imageAsset = imageFetchResult[indexPath.Item] as PHAsset;
// 2
imageManager.RequestImageForAsset(imageAsset,
new CoreGraphics.CGSize(100.0, 100.0), PHImageContentMode.AspectFill,
new PHImageRequestOptions(),
// 3
(UIImage image, NSDictionary info) =>
{
// 4
imageCell.SetImage(image);
});
return imageCell;
}
以上代码分别进行说明如下:
- indexPath 表明当前将返回哪一个 cell 。其 Item 属性表示了 cell 的索引。 我们根据这个索引获得图片资源并将之转换为 PHAsset 对象。
- 用 imageManager 对象去请求获取 PHAsset 所对应的图片,同时指定了所需图片的大小和缩放模式。
- 许多 iOS 框架中的方法都会在执行耗时任务时使用延迟执行,当任务完成时再调用委托方法。以 RequestImageForAsset 方法为例,当请求完成时,委托方法将被调用,所请求的图片和相关信息将通过参数传递到委托方法。
- 最后,设置 cell 中的图片。
编译运行。你会被询问需要访问权限。
如果你选择 OK,app 什么也不会显示。搞毛啊!
iOS 认为照片属于用户的敏感信息,需要经过用户授权。但是当用户同意授权之后, app 也必须注册接收相应的通知,以便重新刷新视图。也就是你接下来的工作。
注册照片访问授权通知
首先,你需要在 PhotoCollectionDataSource 类中增加一个方法以便当照片库内容发生改变后重新抓取数据。在类中加入以下方法:
public void ReloadPhotos()
{
imageFetchResult = PHAsset.FetchAssets(PHAssetMediaType.Image, null);
}
然后,打开 ViewController.cs 导入 photos 框架:
using Photos;
在 ViewDidLoad() 方法中:
// 1
PHPhotoLibrary.SharedPhotoLibrary.RegisterChangeObserver((changeObserver) =>
{
//2
InvokeOnMainThread(() =>
{
// 3
photoDataSource.ReloadPhotos();
collectionView.ReloadData();
});
});
上述代码负责:
- 将 app 注册为接收照片库改变通知,当照片库内容改变时调用指定代码。
- InvokeOnMainThread() 方法在主线程中刷新 UI,否则会导致 app 崩溃。
- 调用 photoDataSource.ReloadPhotos() 重新获取照片,调用 collectionView.ReloadData() 让 Collection View 重绘。
最后,我们来解决前面的问题,在 app 还没有得到相册访问权限时,请求用户授权。
在 ViewDidLoad() 方法中,在初始化 photoDataSource 之前加入:
if (PHPhotoLibrary.AuthorizationStatus == PHAuthorizationStatus.NotDetermined)
{
PHPhotoLibrary.RequestAuthorization((PHAuthorizationStatus newStatus) =>
{ });
}
这里需要检查当前授权状态,如果用户未授权,提示用户进行授权。
为了再次提示用户授权,你需要通过 Simulator \ Reset Content and Settings 重置模拟器。
编译运行。你会看到照片访问授权的提示,如果你选择 OK,这个 app 会在 Collection View 中显示照片的缩略图!
结束语
你可以从这里下载完整的 Visual Studio 项目。
在本教程中,你学习了如何配置 Xamarin 以及如何用它来创建 iOS app。
在 Xamarin 指南网站 有几个优秀的学习资源。要了解更多关于创建跨平台 app 的内容,请查看 Xamarin 教程关于创建同一应用的 iOS 和 Android app。
微软收购 Xamarin 后做出了一些令人赞叹的改变。 在微软的 Build 打回和 Xamarin Evolve 中你会看到这种倾向。Xamarin 发布了最近 Evolve 大会的会议视频,这些视频详细介绍了关于如何使用 Xamarin 的信息和未来的产品方向。
你会用 Xamarin 创建 app 吗?如果你对本文有任何问题建议,请在下面留言。