Xamarin.iOS 中的 iOS 擴充功能
在 iOS 影片中建立擴充功能
延伸模組如 iOS 8 中介紹,是 UIViewControllers
iOS 在標準內容中呈現的特殊功能,例如通知中心內,使用者要求執行特殊輸入或其他內容,例如編輯延伸模組可以提供特殊效果篩選的相片等自定義鍵盤類型。
所有擴充功能都會與容器應用程式一起安裝(使用64位整合API撰寫的兩個元素),並從主機應用程式中的特定擴充點啟動。 而且,由於它們將作為現有系統功能的補充,因此它們必須是高效能、精簡且健全的。
擴充點
類型 | 描述 | 擴充點 | 主應用程式 |
---|---|---|---|
動作 | 特定媒體類型的特製化編輯器或查看器 | com.apple.ui-services |
任意 |
檔提供者 | 允許應用程式使用遠端檔案存放區 | com.apple.fileprovider-ui |
使用 UIDocumentPickerViewController 的應用程式 |
鍵盤 | 替代鍵盤 | com.apple.keyboard-service |
任意 |
相片編輯 | 相片操作和編輯 | com.apple.photo-editing |
Photos.app 編輯器 |
共用 | 與社交網路、傳訊服務等共享數據。 | com.apple.share-services |
任意 |
Today | 出現在 [今日] 畫面或通知中心的 [小工具] | com.apple.widget-extensions |
今日和通知中心 |
iOS 10 和 iOS 12 已新增其他擴充點。 您可以在 iOS 應用程式延伸模組程式設計指南中找到所有支援類型的完整資料表。
限制
延伸模組有一些限制,其中有些是適用於所有類型的(例如,沒有一種類型的延伸模組可以存取相機或麥克風),而其他類型的延伸模組可能會對其使用有特定限制(例如,自定義鍵盤不能用於安全的數據輸入字段,例如密碼)。
通用限制如下:
- 健康情況套件和事件套件 UI 架構無法使用
- 延伸模組無法使用 擴充背景模式
- 擴充功能無法存取裝置的相機或麥克風(雖然它們可能存取現有的媒體檔案)
- 延伸模組無法接收 Air Drop 資料(雖然可以透過 Air Drop 傳輸資料)
- UIActionSheet 和 UIAlertView 無法使用;延伸模組必須使用 UIAlertController
- UIApplication 的數個成員無法使用:UIApplication.SharedApplication、UIApplication.OpenUrl、UIApplication.BeginIgnoringInteractionEvents 和 UIApplication.EndIgnoringInteractionEvents
- iOS 會對今天的擴充功能強制執行 16 MB 的記憶體使用量限制。
- 根據預設,鍵盤延伸模組無法存取網路。 這會影響裝置上的偵錯(模擬器中不會強制執行限制),因為 Xamarin.iOS 需要網路存取才能運作。 將專案 Info.plist 中的值設定
Requests Open Access
為Yes
,以要求網路存取。 如需鍵盤擴充功能限制的詳細資訊,請參閱Apple的 自定義鍵盤指南 。
如需個別限制,請參閱Apple的應用程式 延伸模組程序設計指南。
散發、安裝和執行擴充功能
擴充功能會從容器應用程式內散發,接著會透過App Store提交和散發。 隨應用程式一起散發的擴充功能會安裝於該時間點,但使用者必須明確啟用每個擴充功能。 不同類型的擴充功能會以不同的方式啟用;有數個要求用戶流覽至 [設定 ] 應用程式,並從該處啟用它們。 雖然其他專案會在使用時啟用,例如在傳送相片時啟用共用延伸模組。
使用擴充功能的應用程式(其中使用者遇到擴充點)稱為 「主機」應用程式,因為它是在執行擴充功能時裝載擴充功能的應用程式。 安裝擴充功能的應用程式是 容器應用程式,因為它是安裝擴充功能時包含擴充功能的應用程式。
一般而言,容器應用程式會描述擴充功能,並逐步引導使用者完成啟用它的程式。
擴充功能偵錯和發行版本
執行中應用程式延伸模組的記憶體限制明顯低於套用至前景應用程式的記憶體限制。 執行 iOS 的模擬器對延伸模組的限制較少,而且您可以執行擴充功能,而沒有任何問題。 不過,在裝置上執行相同的擴充功能可能會導致非預期的結果,包括擴充功能當機或系統主動終止。 因此,請務必先在裝置上建置及測試擴充功能,再寄送。
您應該確定下列設定會套用至容器專案和所有參考的延伸模組:
- 在發行組態中建置應用程式套件。
- 在 iOS 建置項目設定中,將 [鏈接器行為] 選項設定為 [僅鏈接架構 SDK] 或 [全部連結]。
- 在 iOS 偵錯項目設定中,取消核取 [啟用偵錯] 和 [啟用程式代碼剖析] 選項。
延伸模組生命週期
擴充功能可以像呈現多個UI畫面的單 一UIViewController 或更複雜的延伸模組一樣簡單。 當使用者遇到 擴充點 時(例如共用影像時),他們將有機會從為該延伸點註冊的延伸模塊中選擇。
如果他們選擇其中一個應用程式的延伸模組,則會 UIViewController
將其具現化,並開始一般的檢視控制器生命週期。 不過,與一般應用程式不同,一般應用程式在使用者完成與其互動時會暫停,但通常不會終止,擴充功能會載入、執行,然後重複終止。
延伸模組可以透過 NSExtensionContext 物件與其主機應用程式通訊。 某些擴充功能具有可接收結果異步回呼的作業。 這些回呼將會在背景線程上執行,而延伸模組必須將此納入考慮;例如,如果您想要更新使用者介面,請使用 NSObject.InvokeOnMainThread 。 如需詳細資訊,請參閱下方的 <與主機應用程式 通訊>一節。
根據預設,延伸模組及其容器應用程式無法通訊,儘管一起安裝。 在某些情況下,容器應用程式基本上是空的「出貨」容器,其用途會在安裝擴充功能之後提供。 不過,如果情況規定,容器應用程式和延伸模組可能會從通用區域共享資源。 此外, Today Extension 可能會要求其容器應用程式開啟 URL。 此行為會顯示在事件倒數小工具中。
建立延伸模組
擴充功能(及其容器應用程式)必須是64位二進位檔,並使用 Xamarin.iOS 整合 API 來建置。 開發擴充功能時,您的解決方案至少會包含兩個專案:容器應用程式,以及容器所提供的每個延伸模組一個專案。
容器應用程式專案需求
用來安裝擴充功能的容器應用程式具有下列需求:
- 它必須維護延伸模組項目的參考。
- 它必須是完整的應用程式(必須能夠成功啟動並執行),即使它只做不到提供安裝延伸模組的方式。
- 它必須有套件組合標識碼,這是延伸模組專案套件組合標識符的基礎(如需詳細資訊,請參閱下一節)。
延伸模組專案需求
此外,延伸模組的專案也有下列需求:
它必須具有以容器應用程式套件組合標識符開頭的套件組合標識碼。 例如,如果容器應用程式的套件組合識別碼
com.myCompany.ContainerApp
為 ,則延伸模組的識別碼可能是com.myCompany.ContainerApp.MyExtension
:它必須定義索引鍵
NSExtensionPointIdentifier
,其檔案中Info.plist
具有適當的值(例如com.apple.widget-extension
今日通知中心小工具)。它也必須使用適當的值,定義
NSExtensionMainStoryboard
其Info.plist
檔案中的索引鍵或NSExtensionPrincipalClass
索引鍵:NSExtensionMainStoryboard
使用 索引鍵來指定分鏡腳本的名稱,此腳本會顯示延伸模組的主要UI(減號.storyboard
)。 例如,Main
針對Main.storyboard
檔案。NSExtensionPrincipalClass
使用 索引鍵來指定在啟動擴充功能時將初始化的類別。 此值必須符合您UIViewController
的 Register 值:
特定類型的延伸模組可能會有其他需求。 例如,Today 或 Notification Center Extension 的主體類別必須實作 INCWidgetProviding。
重要
如果您使用 Visual Studio for Mac 提供的延伸模組範本來啟動專案,範本會自動提供這些需求,並為您自動符合這些需求。
逐步解說
在下列逐步解說中,您將建立一個 範例 Today 小工具,以計算年度剩餘的天數和天數:
建立解決方案
若要建立必要的解決方案,請執行下列動作:
首先,建立新的 iOS 單 一檢視應用程式 專案,然後按兩下 一步 : 按鈕:
呼叫項目
TodayContainer
,然後按兩下一 步: 按鈕:確認 [項目名稱] 和 [SolutionName],然後按兩下 [建立] 按鈕以建立方案:
接下來,在 方案總管 中,以滑鼠右鍵按兩下 [解決方案],然後從 [今日擴充功能] 範本新增 iOS 擴充功能專案:
呼叫項目
DaysRemaining
,然後按兩下一 步: 按鈕:檢閱專案,然後按兩下 [ 建立] 按鈕來建立它:
產生的解決方案現在應該有兩個專案,如下所示:
建立擴充功能用戶介面
接下來,您必須設計 Today 小工具的介面。 這可以使用分鏡腳本或在程序代碼中建立UI來完成。 以下將詳細說明這兩種方法。
使用分鏡腳本
若要使用分鏡腳本建置 UI,請執行下列動作:
在 方案總管 中,按兩下 [延伸模組] 項目的
Main.storyboard
檔案以開啟它以進行編輯:選取依範本自動新增至 UI 的標籤,並在 [屬性總管] 的 [小工具] 索引標籤中提供 [名稱
TodayMessage
]:將變更儲存至分鏡腳本。
使用程序代碼
若要在程式代碼中建置 UI,請執行下列動作:
在 方案總管 中,選取 DaysRemaining 專案、新增類別並呼叫它
CodeBasedViewController
:同樣地,在 方案總管 中,按兩下 [延伸模組] 檔案
Info.plist
以開啟它以進行編輯:選取 [ 來源檢視 ] [從畫面底部] 並開啟
NSExtension
節點:移除索引鍵,
NSExtensionMainStoryboard
並使用 值CodeBasedViewController
新增NSExtensionPrincipalClass
:儲存您的變更。
接下來,編輯 CodeBasedViewController.cs
檔案,使其看起來如下:
using System;
using Foundation;
using UIKit;
using NotificationCenter;
using CoreGraphics;
namespace DaysRemaining
{
[Register("CodeBasedViewController")]
public class CodeBasedViewController : UIViewController, INCWidgetProviding
{
public CodeBasedViewController ()
{
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// Add label to view
var TodayMessage = new UILabel (new CGRect (0, 0, View.Frame.Width, View.Frame.Height)) {
TextAlignment = UITextAlignment.Center
};
View.AddSubview (TodayMessage);
// Insert code to power extension here...
}
}
}
請注意,符合 [Register("CodeBasedViewController")]
您為 NSExtensionPrincipalClass
上述指定的值。
撰寫延伸模組的程序代碼
建立使用者介面之後,開啟 TodayViewController.cs
或 CodeBasedViewController.cs
檔案(根據上述用來建立使用者介面的方法),變更 ViewDidLoad 方法,使其看起來如下:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// Calculate the values
var dayOfYear = DateTime.Now.DayOfYear;
var leapYearExtra = DateTime.IsLeapYear (DateTime.Now.Year) ? 1 : 0;
var daysRemaining = 365 + leapYearExtra - dayOfYear;
// Display the message
if (daysRemaining == 1) {
TodayMessage.Text = String.Format ("Today is day {0}. There is one day remaining in the year.", dayOfYear);
} else {
TodayMessage.Text = String.Format ("Today is day {0}. There are {1} days remaining in the year.", dayOfYear, daysRemaining);
}
}
如果使用以程式代碼為基礎的使用者介面方法,請將 // Insert code to power extension here...
批註取代為上述新程序代碼。 呼叫基底實作(並插入以程式代碼為基礎的版本標籤)之後,此程式代碼會執行簡單的計算,以取得年份的日期和剩餘天數。 然後,它會在UI設計中建立的標籤 (TodayMessage
) 中顯示訊息。
請注意,此程式與撰寫應用程式的一般程式相似。 延伸模組的生命週期與應用程式中的 UIViewController
檢視控制器相同,但延伸模組沒有背景模式,且在使用者完成使用時不會暫停。 相反地,擴充功能會視需要重複初始化和取消配置。
建立容器應用程式用戶介面
在本逐步解說中,容器應用程式只是用來作為寄送並安裝延伸模組的方法,而且不提供自己的功能。 編輯 TodayContainer 的 Main.storyboard
檔案,並新增一些定義 Extension 函式的文字,以及如何安裝它:
將變更儲存至分鏡腳本。
測試擴充功能
若要在 iOS 模擬器中測試延伸模組,請執行 TodayContainer 應用程式。 容器的主要檢視將會顯示:
接下來,按下模擬器中的 [首頁] 按鈕,從畫面頂端向下撥動以開啟通知中心,選取 [今日] 索引卷標,然後按兩下 [編輯] 按鈕:
將 DaysRemaining 擴充功能新增至 [今日] 檢視,然後按兩下 [完成] 按鈕:
新的小工具將會新增至 [ 今日 ] 檢視,並顯示結果:
與主機應用程式通訊
您上述建立的今日延伸模組範例不會與其主機應用程式通訊(今日畫面)。 如果這樣做,它會使用 或 類別的 TodayViewController
ExtensionContext CodeBasedViewController
屬性。
針對從主應用程式接收數據的延伸模組,數據的格式為儲存在 ExtensionContext 之 ExtensionContext UIViewController
的 InputItems 屬性中的 NSExtensionItem 物件數位。
其他延伸模組,例如相片編輯延伸模組,可能會區分使用者完成或取消使用量。 這會透過 ExtensionContext 屬性的 CompleteRequest 和 CancelRequest 方法傳回主機應用程式。
如需詳細資訊,請參閱Apple的應用程式 延伸模組程序設計指南。
與父應用程式通訊
「應用程式群組」可讓不同的應用程式 (或應用程式及其擴充功能) 存取共用檔案儲存體位置。 「應用程式群組」可用於資料下列資料:
如需詳細資訊,請參閱使用功能檔的應用程式群組一節。
MobileCoreServices
使用延伸模組時,請使用統一類型標識碼 (UTI) 來建立及操作應用程式、其他應用程式和/或服務之間交換的數據。
MobileCoreServices.UTType
靜態類別會定義下列與 Apple kUTType...
定義相關的協助程式屬性:
kUTTypeAlembic
-Alembic
kUTTypeAliasFile
-AliasFile
kUTTypeAliasRecord
-AliasRecord
kUTTypeAppleICNS
-AppleICNS
kUTTypeAppleProtectedMPEG4Audio
-AppleProtectedMPEG4Audio
kUTTypeAppleProtectedMPEG4Video
-AppleProtectedMPEG4Video
kUTTypeAppleScript
-AppleScript
kUTTypeApplication
-Application
kUTTypeApplicationBundle
-ApplicationBundle
kUTTypeApplicationFile
-ApplicationFile
kUTTypeArchive
-Archive
kUTTypeAssemblyLanguageSource
-AssemblyLanguageSource
kUTTypeAudio
-Audio
kUTTypeAudioInterchangeFileFormat
-AudioInterchangeFileFormat
kUTTypeAudiovisualContent
-AudiovisualContent
kUTTypeAVIMovie
-AVIMovie
kUTTypeBinaryPropertyList
-BinaryPropertyList
kUTTypeBMP
-BMP
kUTTypeBookmark
-Bookmark
kUTTypeBundle
-Bundle
kUTTypeBzip2Archive
-Bzip2Archive
kUTTypeCalendarEvent
-CalendarEvent
kUTTypeCHeader
-CHeader
kUTTypeCommaSeparatedText
-CommaSeparatedText
kUTTypeCompositeContent
-CompositeContent
kUTTypeConformsToKey
-ConformsToKey
kUTTypeContact
-Contact
kUTTypeContent
-Content
kUTTypeCPlusPlusHeader
-CPlusPlusHeader
kUTTypeCPlusPlusSource
-CPlusPlusSource
kUTTypeCSource
-CSource
kUTTypeData
-Database
kUTTypeDelimitedText
-DelimitedText
kUTTypeDescriptionKey
-DescriptionKey
kUTTypeDirectory
-Directory
kUTTypeDiskImage
-DiskImage
kUTTypeElectronicPublication
-ElectronicPublication
kUTTypeEmailMessage
-EmailMessage
kUTTypeExecutable
-Executable
kUTExportedTypeDeclarationsKey
-ExportedTypeDeclarationsKey
kUTTypeFileURL
-FileURL
kUTTypeFlatRTFD
-FlatRTFD
kUTTypeFolder
-Folder
kUTTypeFont
-Font
kUTTypeFramework
-Framework
kUTTypeGIF
-GIF
kUTTypeGNUZipArchive
-GNUZipArchive
kUTTypeHTML
-HTML
kUTTypeICO
-ICO
kUTTypeIconFileKey
-IconFileKey
kUTTypeIdentifierKey
-IdentifierKey
kUTTypeImage
-Image
kUTImportedTypeDeclarationsKey
-ImportedTypeDeclarationsKey
kUTTypeInkText
-InkText
kUTTypeInternetLocation
-InternetLocation
kUTTypeItem
-Item
kUTTypeJavaArchive
-JavaArchive
kUTTypeJavaClass
-JavaClass
kUTTypeJavaScript
-JavaScript
kUTTypeJavaSource
-JavaSource
kUTTypeJPEG
-JPEG
kUTTypeJPEG2000
-JPEG2000
kUTTypeJSON
-JSON
kUTType3dObject
-k3dObject
kUTTypeLivePhoto
-LivePhoto
kUTTypeLog
-Log
kUTTypeM3UPlaylist
-M3UPlaylist
kUTTypeMessage
-Message
kUTTypeMIDIAudio
-MIDIAudio
kUTTypeMountPoint
-MountPoint
kUTTypeMovie
-Movie
kUTTypeMP3
-MP3
kUTTypeMPEG
-MPEG
kUTTypeMPEG2TransportStream
-MPEG2TransportStream
kUTTypeMPEG2Video
-MPEG2Video
kUTTypeMPEG4
-MPEG4
kUTTypeMPEG4Audio
-MPEG4Audio
kUTTypeObjectiveCPlusPlusSource
-ObjectiveCPlusPlusSource
kUTTypeObjectiveCSource
-ObjectiveCSource
kUTTypeOSAScript
-OSAScript
kUTTypeOSAScriptBundle
-OSAScriptBundle
kUTTypePackage
-Package
kUTTypePDF
-PDF
kUTTypePerlScript
-PerlScript
kUTTypePHPScript
-PHPScript
kUTTypePICT
-PICT
kUTTypePKCS12
-PKCS12
kUTTypePlainText
-PlainText
kUTTypePlaylist
-Playlist
kUTTypePluginBundle
-PluginBundle
kUTTypePNG
-PNG
kUTTypePolygon
-Polygon
kUTTypePresentation
-Presentation
kUTTypePropertyList
-PropertyList
kUTTypePythonScript
-PythonScript
kUTTypeQuickLookGenerator
-QuickLookGenerator
kUTTypeQuickTimeImage
-QuickTimeImage
kUTTypeQuickTimeMovie
-QuickTimeMovie
kUTTypeRawImage
-RawImage
kUTTypeReferenceURLKey
-ReferenceURLKey
kUTTypeResolvable
-Resolvable
kUTTypeRTF
-RTF
kUTTypeRTFD
-RTFD
kUTTypeRubyScript
-RubyScript
kUTTypeScalableVectorGraphics
-ScalableVectorGraphics
kUTTypeScript
-Script
kUTTypeShellScript
-ShellScript
kUTTypeSourceCode
-SourceCode
kUTTypeSpotlightImporter
-SpotlightImporter
kUTTypeSpreadsheet
-Spreadsheet
kUTTypeStereolithography
-Stereolithography
kUTTypeSwiftSource
-SwiftSource
kUTTypeSymLink
-SymLink
kUTTypeSystemPreferencesPane
-SystemPreferencesPane
kUTTypeTabSeparatedText
-TabSeparatedText
kUTTagClassFilenameExtension
-TagClassFilenameExtension
kUTTagClassMIMEType
-TagClassMIMEType
kUTTypeTagSpecificationKey
-TagSpecificationKey
kUTTypeText
-Text
kUTType3DContent
-ThreeDContent
kUTTypeTIFF
-TIFF
kUTTypeToDoItem
-ToDoItem
kUTTypeTXNTextAndMultimediaData
-TXNTextAndMultimediaData
kUTTypeUniversalSceneDescription
-UniversalSceneDescription
kUTTypeUnixExecutable
-UnixExecutable
kUTTypeURL
-URL
kUTTypeURLBookmarkData
-URLBookmarkData
kUTTypeUTF16ExternalPlainText
-UTF16ExternalPlainText
kUTTypeUTF16PlainText
-UTF16PlainText
kUTTypeUTF8PlainText
-UTF8PlainText
kUTTypeUTF8TabSeparatedText
-UTF8TabSeparatedText
kUTTypeVCard
-VCard
kUTTypeVersionKey
-VersionKey
kUTTypeVideo
-Video
kUTTypeVolume
-Volume
kUTTypeWaveformAudio
-WaveformAudio
kUTTypeWebArchive
-WebArchive
kUTTypeWindowsExecutable
-WindowsExecutable
kUTTypeX509Certificate
-X509Certificate
kUTTypeXML
-XML
kUTTypeXMLPropertyList
-XMLPropertyList
kUTTypeXPCService
-XPCService
kUTTypeZipArchive
-ZipArchive
請參閱下列範例:
using MobileCoreServices;
...
NSItemProvider itemProvider = new NSItemProvider ();
itemProvider.LoadItem(UTType.PropertyList ,null, (item, err) => {
if (err == null) {
NSDictionary results = (NSDictionary )item;
NSString baseURI =
results.ObjectForKey("NSExtensionJavaScriptPreprocessingResultsKey");
}
});
如需詳細資訊,請參閱使用功能檔的應用程式群組一節。
預防措施和考慮
延伸模組的可用記憶體比應用程式少得多。 它們預期會快速執行,且對用戶和他們裝載的應用程式進行最少入侵。 不過,延伸模組也應該為取用應用程式提供獨特的實用函式,其具有品牌UI,可讓用戶識別其所屬的擴充功能開發人員或容器應用程式。
鑒於這些嚴格的需求,您只應該部署經過徹底測試和優化以達到效能和記憶體耗用量的擴充功能。
摘要
本文件涵蓋擴充功能、擴充點類型,以及iOS對延伸模組所加之已知限制的類型。 它討論如何建立、散發、安裝及執行擴充功能和延伸模組生命週期。 它提供建立簡單 Today 小工具的逐步解說,其中顯示使用分鏡腳本或程式代碼建立小工具 UI 的兩種方式。 它示範如何在 iOS 模擬器中測試延伸模組。 最後,它會簡短討論與主機應用程式通訊,以及開發延伸模組時應採取的一些預防措施和考慮。