1. 程式人生 > >用Xamarin和Visual Studio編寫iOS App

用Xamarin和Visual Studio編寫iOS App

                     

一說開發 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 主機:

  1. 在 Mac 電腦上,開啟系統偏好設定,選擇共享。
  2. 開啟遠端登入。
  3. 將“允許訪問”改為“僅這些使用者”,然後新增將用於訪問 Mac 上的 Xamarin 和 Xcode 的使用者。

  4. 關閉視窗,返回 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;
  • 1

這將匯入 iOS UIKit 框架。

然後是類定義:

public class PhotoCollectionDataSource : UICollectionViewDataSource{}
  • 1
  • 2
  • 3

還記得你為 Collection View Cell 定義的 reuse identifier 嗎?在這裡我們將會用到它。在類定義中加入:

private static readonly string photoCellIdentifier = "ImageCellIdentifier";
  • 1

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;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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;
  • 1

在 ViewDidLoad()最後,新增程式碼,初始化資料來源並將它繫結到 Collection View:

photoDataSource = new PhotoCollectionDataSource();collectionView.DataSource = photoDataSource;
  • 1
  • 2

這樣,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;}
  • 1
  • 2
  • 3
  • 4

現在你可以讓 PhotoCollectionDataSource 去抓取照片了。

在 PhotoCollectionDataSource.cs 的頂部:

using Photos;
  • 1

在 PhotoCollectionDataSource 增加變數:

private PHFetchResult imageFetchResult;private PHImageManager imageManager;
  • 1
  • 2

imageFetchResult 變數用於儲存照片對應 Asset 的陣列,然後通過 imageManager 物件來獲取照片資料。
在 GetCell() 方法前,新增構造方法:

public PhotoCollectionDataSource(){    imageFetchResult = PHAsset.FetchAssets(PHAssetMediaType.Image, null);    imageManager = new PHImageManager();}
  • 1
  • 2
  • 3
  • 4
  • 5

這個構造方法從 Photos app 中抓取所有圖片資源,並將結果放到 imageFetchResult 變數中。然後初始化 imageManager,app 用它來查詢每一張照片的具體資料。

在構造方法下面,新增析構方法,將 imageManager 物件釋放:

~PhotoCollectionDataSource(){    imageManager.Dispose();}
  • 1
  • 2
  • 3
  • 4

在 GetItemsCount 和 GetCell 方法中用新資料來源中的圖片替換原來的空 cell。修改 GetItemsCount() 方法為:

public override nint GetItemsCount(UICollectionView collectionView, nint section){    return imageFetchResult.Count;}
  • 1
  • 2
  • 3
  • 4

修改 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;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

以上程式碼分別進行說明如下:

  1. indexPath 表明當前將返回哪一個 cell 。其 Item 屬性表示了 cell 的索引。 我們根據這個索引獲得圖片資源並將之轉換為 PHAsset 物件。
  2. 用 imageManager 物件去請求獲取 PHAsset 所對應的圖片,同時指定了所需圖片的大小和縮放模式。
  3. 許多 iOS 框架中的方法都會在執行耗時任務時使用延遲執行,當任務完成時再呼叫委託方法。以 RequestImageForAsset 方法為例,當請求完成時,委託方法將被呼叫,所請求的圖片和相關資訊將通過引數傳遞到委託方法。
  4. 最後,設定 cell 中的圖片。

編譯執行。你會被詢問需要訪問許可權。

如果你選擇 OK,app 什麼也不會顯示。搞毛啊!

iOS 認為照片屬於使用者的敏感資訊,需要經過使用者授權。但是當用戶同意授權之後, app 也必須註冊接收相應的通知,以便重新重新整理檢視。也就是你接下來的工作。

註冊照片訪問授權通知

首先,你需要在 PhotoCollectionDataSource 類中增加一個方法以便當照片庫內容發生改變後重新抓取資料。在類中加入以下方法:

public void ReloadPhotos(){    imageFetchResult = PHAsset.FetchAssets(PHAssetMediaType.Image, null);}
  • 1
  • 2
  • 3
  • 4

然後,開啟 ViewController.cs 匯入 photos 框架:

using Photos;
  • 1

在  ViewDidLoad() 方法中:

// 1PHPhotoLibrary.SharedPhotoLibrary.RegisterChangeObserver((changeObserver) =>{    //2    InvokeOnMainThread(() =>    {        // 3        photoDataSource.ReloadPhotos();        collectionView.ReloadData();    });});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

上述程式碼負責:

  1. 將 app 註冊為接收照片庫改變通知,當照片庫內容改變時呼叫指定程式碼。
  2. InvokeOnMainThread() 方法在主執行緒中重新整理 UI,否則會導致 app 崩潰。
  3. 呼叫 photoDataSource.ReloadPhotos() 重新獲取照片,呼叫 collectionView.ReloadData() 讓 Collection View 重繪。

最後,我們來解決前面的問題,在 app 還沒有得到相簿訪問許可權時,請求使用者授權。

在 ViewDidLoad() 方法中,在初始化 photoDataSource 之前加入:

if (PHPhotoLibrary.AuthorizationStatus == PHAuthorizationStatus.NotDetermined){    PHPhotoLibrary.RequestAuthorization((PHAuthorizationStatus newStatus) =>    { });}
  • 1
  • 2
  • 3
  • 4
  • 5

這裡需要檢查當前授權狀態,如果使用者未授權,提示使用者進行授權。
為了再次提示使用者授權,你需要通過  Simulator \ Reset Content and Settings 重置模擬器。

編譯執行。你會看到照片訪問授權的提示,如果你選擇 OK,這個 app 會在 Collection View 中顯示照片的縮圖!

結束語

你可以從這裡下載完整的 Visual Studio 專案。

在本教程中,你學習瞭如何配置 Xamarin 以及如何用它來建立 iOS app。

Xamarin 指南網站 有幾個優秀的學習資源。要了解更多關於建立跨平臺 app 的內容,請檢視 Xamarin 教程關於建立同一應用的 iOSAndroid app。

微軟收購 Xamarin 後做出了一些令人讚歎的改變。 在微軟的 Build 打回和 Xamarin Evolve 中你會看到這種傾向。Xamarin 釋出了最近 Evolve 大會的會議視訊,這些視訊詳細介紹了關於如何使用 Xamarin 的資訊和未來的產品方向。

你會用 Xamarin 建立 app 嗎?如果你對本文有任何問題建議,請在下面留言。