在 Xamarin 中使用 tvOS 文字和搜尋欄位
如有需要,您的 Xamarin.tvOS 應用程式可以使用文字欄位和螢幕上的鍵盤,向使用者要求少量文字(例如使用者識別碼和密碼):
您可以選擇性地使用搜尋欄位來提供應用程式內容的關鍵字搜尋能力:
本檔將涵蓋在 Xamarin.tvOS 應用程式中使用文字和搜尋欄位的詳細數據。
關於文字和搜尋欄位
如上所述,如果需要,您的 Xamarin.tvOS 可以呈現一或多個文字欄位,以使用螢幕上收集使用者少量的文字(或視使用者已安裝的 tvOS 版本而定的選擇性藍牙鍵盤)。
此外,如果您的 app 向使用者呈現大量內容(例如音樂、電影或圖片集合),您可能會想要包含搜尋欄位,讓使用者輸入少量文字來篩選可用專案清單。
文字欄位
在tvOS中,文字欄位會顯示為固定高度的圓角輸入方塊,當使用者按兩下螢幕鍵盤時,就會顯示螢幕小鍵盤:
當使用者將焦點移至指定的文字欄位時,它會變大並顯示深陰影。 設計使用者介面時,您必須記住這一點,因為文字欄位可以在焦點內重疊其他UI元素。
Apple 有下列使用文字欄位的建議:
- 謹慎使用文字輸入 - 由於螢幕鍵盤的性質,輸入長段的文字或填寫多個文字欄位對使用者很乏味。 更好的解決方案是使用選取範圍清單或 按鈕來限制文字項目的數量。
- 使用提示來傳達目的 - 文字字段可以在空白時顯示佔位元「提示」。 如果適用,請使用提示來描述文字欄位的目的,而不是個別的標籤。
- 選取 [適當的默認鍵盤類型 - tvOS] 提供數種不同的用途建置鍵盤類型,可供您為 [文字字段] 指定。 例如,[電子郵件地址鍵盤] 可讓使用者從最近輸入的位址清單中選取,以簡化輸入專案。
- 當適當時,請使用安全文字欄位 - 安全文字欄位會顯示輸入為點的字元(而不是實際字母)。 收集密碼等敏感性資訊時,請一律使用安全文字欄位。
鍵盤
每當使用者按兩下使用者介面中的文字欄位時,就會顯示螢幕上的線性鍵盤。 使用者使用 Touch Surface The Siri Remote 從鍵盤選取個別字母,並輸入要求的資訊:
如果目前檢視上有多個文字欄位,系統會自動顯示 [下一步] 按鈕,讓使用者前往下一個文字字段。 最後一個 文字欄位會顯示 [完成 ] 按鈕,該欄位將結束文字專案,並將用戶傳回上一個畫面。
用戶隨時都可以按下 Siri 遠端上的 [功能表 ] 按鈕來結束文字輸入,然後再返回上一個畫面。
Apple 有下列使用螢幕鍵盤的建議:
- 選取 [適當的默認鍵盤類型 - tvOS] 提供數種不同的用途建置鍵盤類型,可供您為 [文字字段] 指定。 例如,[電子郵件地址鍵盤] 可讓使用者從最近輸入的位址清單中選取,以簡化輸入專案。
- 適當時,請使用鍵盤配件檢視 - 除了一律顯示的標準資訊之外,選擇性的輔助檢視(例如影像或標籤)也可以新增至螢幕上的鍵盤,以釐清文字輸入的目的,或協助使用者輸入必要的資訊。
如需使用螢幕鍵盤的詳細資訊,請參閱 Apple 的 UIKeyboardType、管理鍵盤、數據輸入自定義檢視和 iOS 文字程式設計指南檔。
Search
搜尋欄位提供文字欄位和螢幕小鍵盤的特殊畫面,可讓使用者篩選鍵盤下方顯示的專案集合:
當使用者在搜尋欄位中輸入字母時,下列結果會自動反映搜尋的結果。 用戶隨時可以將焦點移至結果,然後選取其中一個專案。
Apple 有下列使用搜尋欄位的建議:
- 提供最近的搜尋 - 因為使用 Siri 遠端輸入文字可能會很乏味,且使用者傾向於重複搜尋要求,請考慮在鍵盤區域下目前的結果之前新增 [最近搜尋結果] 區段。
- 可能的話,請限制結果 數目 - 因為使用者難以剖析和巡覽的大型項目清單,請考慮限制傳回的結果數目。
- 如果適當,請提供搜尋結果篩選 - 如果應用程式提供的內容適合自己,請考慮新增範圍列,讓使用者進一步篩選傳回的搜尋結果。
如需詳細資訊,請參閱 Apple 的 UISearchController 類別參考。
使用文字欄位
在 Xamarin.tvOS 應用程式中使用文字欄位的最簡單方式,就是使用 iOS 設計工具將它們新增至使用者介面設計。
執行下列操作:
在程式代碼中,您可以使用其 Text
屬性來取得或設定文字欄位的值:
Console.WriteLine ("User ID {0} and Password {1}", UserId.Text, Password.Text);
您可以選擇性地使用 Started
和 Ended
文字欄位事件來回應開始和結束的文字專案。
使用搜尋欄位
在 Xamarin.tvOS 應用程式中使用搜尋欄位的最簡單方式,就是使用介面設計工具將它們新增至使用者介面設計。
執行下列操作:
在 Solution Pad 中,按兩下
Main.storyboard
檔案以開啟檔案以供編輯。將新的集合檢視控制器拖曳至分鏡腳本,以呈現使用者搜尋的結果:
在 Properties Pad 的 [小工具] 索引標籤中,針對 [類別] 和
SearchResults
[分鏡腳本標識符] 使用SearchResultsViewController
:選取 設計介面上的儲存格原型 。
在 [屬性總管] 的 [小工具] 索引標籤中,針對 [類別] 和
ImageCell
[標識符] 使用SearchResultCell
:設定儲存格原型的設計,並在 [屬性總管] 的 [小工具] 索引標籤中,以唯一的 [名稱] 公開每個元素:
將變更儲存至分鏡腳本。
提供數據模型
接下來,您必須提供類別,以作為使用者所搜尋結果的數據模型。 在 方案總管 中,以滑鼠右鍵按兩下 [項目名稱],然後選取 [新增>檔案...>一般>空白類別並提供名稱:
例如,允許使用者依標題和關鍵詞搜尋圖片集合的應用程式可能如下所示:
using System;
using Foundation;
namespace tvText
{
public class PictureInformation : NSObject
{
#region Computed Properties
public string Title { get; set;}
public string ImageName { get; set;}
public string Keywords { get; set;}
#endregion
#region Constructors
public PictureInformation (string title, string imageName, string keywords)
{
// Initialize
this.Title = title;
this.ImageName = imageName;
this.Keywords = keywords;
}
#endregion
}
}
集合檢視儲存格
在數據模型就緒后,請編輯 原型數據格 (SearchResultViewCell.cs
),讓它看起來如下:
using Foundation;
using System;
using UIKit;
namespace tvText
{
public partial class SearchResultViewCell : UICollectionViewCell
{
#region Private Variables
private PictureInformation _pictureInfo = null;
#endregion
#region Computed Properties
public PictureInformation PictureInfo {
get { return _pictureInfo; }
set {
_pictureInfo = value;
UpdateUI ();
}
}
#endregion
#region Constructors
public SearchResultViewCell (IntPtr handle) : base (handle)
{
// Initialize
UpdateUI ();
}
#endregion
#region Private Methods
private void UpdateUI ()
{
// Anything to process?
if (PictureInfo == null) return;
try {
Picture.Image = UIImage.FromBundle (PictureInfo.ImageName);
Picture.AdjustsImageWhenAncestorFocused = true;
Title.Text = PictureInfo.Title;
TextColor = UIColor.LightGray;
} catch {
// Ignore errors if view isn't fully loaded
}
}
#endregion
}
}
方法UpdateUI
將用來在每次更新屬性時,在具名 UI 元素中顯示 PictureInformation 專案 (PictureInfo
屬性) 的個別字段。 例如,與圖片相關聯的 Image 和 Title。
集合檢視控制器
接下來,編輯搜尋結果集合檢視控制器 (SearchResultsViewController.cs
),並使其看起來如下:
using Foundation;
using System;
using UIKit;
using System.Collections.Generic;
namespace tvText
{
public partial class SearchResultsViewController : UICollectionViewController , IUISearchResultsUpdating
{
#region Constants
public const string CellID = "ImageCell";
#endregion
#region Private Variables
private string _searchFilter = "";
#endregion
#region Computed Properties
public List<PictureInformation> AllPictures { get; set;}
public List<PictureInformation> FoundPictures { get; set; }
public string SearchFilter {
get { return _searchFilter; }
set {
_searchFilter = value.ToLower();
FindPictures ();
CollectionView?.ReloadData ();
}
}
#endregion
#region Constructors
public SearchResultsViewController (IntPtr handle) : base (handle)
{
// Initialize
this.AllPictures = new List<PictureInformation> ();
this.FoundPictures = new List<PictureInformation> ();
PopulatePictures ();
FindPictures ();
}
#endregion
#region Private Methods
private void PopulatePictures ()
{
// Clear list
AllPictures.Clear ();
// Add images
AllPictures.Add (new PictureInformation ("Antipasta Platter","Antipasta","cheese,grapes,tomato,coffee,meat,plate"));
AllPictures.Add (new PictureInformation ("Cheese Plate", "CheesePlate", "cheese,plate,bread"));
AllPictures.Add (new PictureInformation ("Coffee House", "CoffeeHouse", "coffee,people,menu,restaurant,cafe"));
AllPictures.Add (new PictureInformation ("Computer and Expresso", "ComputerExpresso", "computer,coffee,expresso,phone,notebook"));
AllPictures.Add (new PictureInformation ("Hamburger", "Hamburger", "meat,bread,cheese,tomato,pickle,lettus"));
AllPictures.Add (new PictureInformation ("Lasagna Dinner", "Lasagna", "salad,bread,plate,lasagna,pasta"));
AllPictures.Add (new PictureInformation ("Expresso Meeting", "PeopleExpresso", "people,bag,phone,expresso,coffee,table,tablet,notebook"));
AllPictures.Add (new PictureInformation ("Soup and Sandwich", "SoupAndSandwich", "soup,sandwich,bread,meat,plate,tomato,lettus,egg"));
AllPictures.Add (new PictureInformation ("Morning Coffee", "TabletCoffee", "tablet,person,man,coffee,magazine,table"));
AllPictures.Add (new PictureInformation ("Evening Coffee", "TabletMagCoffee", "tablet,magazine,coffee,table"));
}
private void FindPictures ()
{
// Clear list
FoundPictures.Clear ();
// Scan each picture for a match
foreach (PictureInformation picture in AllPictures) {
if (SearchFilter == "") {
// If no search term, everything matches
FoundPictures.Add (picture);
} else if (picture.Title.Contains (SearchFilter) || picture.Keywords.Contains (SearchFilter)) {
// If the search term is in the title or keywords, we've found a match
FoundPictures.Add (picture);
}
}
}
#endregion
#region Override Methods
public override nint NumberOfSections (UICollectionView collectionView)
{
// Only one section in this collection
return 1;
}
public override nint GetItemsCount (UICollectionView collectionView, nint section)
{
// Return the number of matching pictures
return FoundPictures.Count;
}
public override UICollectionViewCell GetCell (UICollectionView collectionView, NSIndexPath indexPath)
{
// Get a new cell and return it
var cell = collectionView.DequeueReusableCell (CellID, indexPath);
return (UICollectionViewCell)cell;
}
public override void WillDisplayCell (UICollectionView collectionView, UICollectionViewCell cell, NSIndexPath indexPath)
{
// Grab the cell
var currentCell = cell as SearchResultViewCell;
if (currentCell == null)
throw new Exception ("Expected to display a `SearchResultViewCell`.");
// Display the current picture info in the cell
var item = FoundPictures [indexPath.Row];
currentCell.PictureInfo = item;
}
public override void ItemSelected (UICollectionView collectionView, NSIndexPath indexPath)
{
// If this Search Controller was presented as a modal view, close
// it before continuing
// DismissViewController (true, null);
// Grab the picture being selected and report it
var picture = FoundPictures [indexPath.Row];
Console.WriteLine ("Selected: {0}", picture.Title);
}
public void UpdateSearchResultsForSearchController (UISearchController searchController)
{
// Save the search filter and update the Collection View
SearchFilter = searchController.SearchBar.Text ?? string.Empty;
}
public override void DidUpdateFocus (UIFocusUpdateContext context, UIFocusAnimationCoordinator coordinator)
{
var previousItem = context.PreviouslyFocusedView as SearchResultViewCell;
if (previousItem != null) {
UIView.Animate (0.2, () => {
previousItem.TextColor = UIColor.LightGray;
});
}
var nextItem = context.NextFocusedView as SearchResultViewCell;
if (nextItem != null) {
UIView.Animate (0.2, () => {
nextItem.TextColor = UIColor.Black;
});
}
}
#endregion
}
}
首先, IUISearchResultsUpdating
介面會新增至 類別,以處理使用者正在更新的搜尋控制器篩選:
public partial class SearchResultsViewController : UICollectionViewController , IUISearchResultsUpdating
也會定義常數來指定原型單元格的標識碼(符合上述介面設計工具中定義的標識碼),稍後集合控制器要求新的單元格時會用到:
public const string CellID = "ImageCell";
系統會針對所搜尋專案的完整清單、搜尋篩選字詞和符合該字詞的專案清單建立 儲存體:
private string _searchFilter = "";
...
public List<PictureInformation> AllPictures { get; set;}
public List<PictureInformation> FoundPictures { get; set; }
public string SearchFilter {
get { return _searchFilter; }
set {
_searchFilter = value.ToLower();
FindPictures ();
CollectionView?.ReloadData ();
}
}
SearchFilter
變更時,會更新相符專案清單,並重載集合檢視的內容。 例 FindPictures
程負責尋找符合新搜尋字詞的專案:
private void FindPictures ()
{
// Clear list
FoundPictures.Clear ();
// Scan each picture for a match
foreach (PictureInformation picture in AllPictures) {
if (SearchFilter == "") {
// If no search term, everything matches
FoundPictures.Add (picture);
} else if (picture.Title.Contains (SearchFilter) || picture.Keywords.Contains (SearchFilter)) {
// If the search term is in the title or keywords, we've found a match
FoundPictures.Add (picture);
}
}
}
當使用者變更搜尋控制器中的篩選時,將會更新 的值 SearchFilter
(將會更新結果集合檢視:
public void UpdateSearchResultsForSearchController (UISearchController searchController)
{
// Save the search filter and update the Collection View
SearchFilter = searchController.SearchBar.Text ?? string.Empty;
}
方法 PopulatePictures
一開始會填入可用項目的集合:
private void PopulatePictures ()
{
// Clear list
AllPictures.Clear ();
// Add images
AllPictures.Add (new PictureInformation ("Antipasta Platter","Antipasta","cheese,grapes,tomato,coffee,meat,plate"));
...
}
為了這個範例,當集合檢視控制器載入時,會在記憶體中建立所有範例數據。 在實際應用程式中,此數據可能會從資料庫或 Web 服務讀取,而且只需要保留才能避免超過 Apple TV 有限的記憶體。
NumberOfSections
和 GetItemsCount
方法會提供相符項目的數目:
public override nint NumberOfSections (UICollectionView collectionView)
{
// Only one section in this collection
return 1;
}
public override nint GetItemsCount (UICollectionView collectionView, nint section)
{
// Return the number of matching pictures
return FoundPictures.Count;
}
方法 GetCell
會針對集合檢視中的每個項目傳回新的 原型單元格 (根據 CellID
上述在分鏡腳本中定義的專案:
public override UICollectionViewCell GetCell (UICollectionView collectionView, NSIndexPath indexPath)
{
// Get a new cell and return it
var cell = collectionView.DequeueReusableCell (CellID, indexPath);
return (UICollectionViewCell)cell;
}
在 WillDisplayCell
顯示 Cell 之前呼叫 方法,以便設定它:
public override void WillDisplayCell (UICollectionView collectionView, UICollectionViewCell cell, NSIndexPath indexPath)
{
// Grab the cell
var currentCell = cell as SearchResultViewCell;
if (currentCell == null)
throw new Exception ("Expected to display a `SearchResultViewCell`.");
// Display the current picture info in the cell
var item = FoundPictures [indexPath.Row];
currentCell.PictureInfo = item;
}
方法 DidUpdateFocus
會在結果集合檢視中醒目提示專案時,向使用者提供視覺回饋:
public override void DidUpdateFocus (UIFocusUpdateContext context, UIFocusAnimationCoordinator coordinator)
{
var previousItem = context.PreviouslyFocusedView as SearchResultViewCell;
if (previousItem != null) {
UIView.Animate (0.2, () => {
previousItem.TextColor = UIColor.LightGray;
});
}
var nextItem = context.NextFocusedView as SearchResultViewCell;
if (nextItem != null) {
UIView.Animate (0.2, () => {
nextItem.TextColor = UIColor.Black;
});
}
}
最後,方法會在 ItemSelected
結果集合檢視中處理用戶選取專案(按兩下具有Siri遠端的 Touch Surface):
public override void ItemSelected (UICollectionView collectionView, NSIndexPath indexPath)
{
// If this Search Controller was presented as a modal view, close
// it before continuing
// DismissViewController (true, null);
// Grab the picture being selected and report it
var picture = FoundPictures [indexPath.Row];
Console.WriteLine ("Selected: {0}", picture.Title);
}
如果 [搜尋欄位] 顯示為強制回應對話框檢視(在呼叫它的檢視頂端),請使用 DismissViewController
方法,在使用者選取專案時關閉搜尋檢視。 在此範例中,[搜尋字段] 會顯示為 [索引卷標檢視] 索引卷標的內容,因此不會在此關閉。
如需集合檢視的詳細資訊,請參閱使用 集合檢視 檔。
呈現搜尋欄位
搜尋欄位(及其相關聯的螢幕鍵盤和搜尋結果)有兩種主要方式可以呈現給tvOS中的使用者:
- 強制回應對話框檢視 - 搜尋字段可以透過目前的檢視和檢視控制器呈現為全螢幕模式對話框檢視。 這通常是為了回應用戶按兩下按鈕或其他UI元素。 當使用者從搜尋結果中選取專案時,對話框就會關閉。
- 檢視內容 - 搜尋欄位是指定檢視的直接部分。 例如,當做索引標籤檢視控制器中 [搜尋] 索引標籤的內容。
如需上述圖片的可搜尋清單範例,搜尋欄位會顯示為 [搜尋] 索引卷標中的 [檢視內容],而 [搜尋索引卷標檢視控制器] 看起來如下:
using System;
using UIKit;
namespace tvText
{
public partial class SecondViewController : UIViewController
{
#region Constants
public const string SearchResultsID = "SearchResults";
#endregion
#region Computed Properties
public SearchResultsViewController ResultsController { get; set;}
#endregion
#region Constructors
public SecondViewController (IntPtr handle) : base (handle)
{
}
#endregion
#region Private Methods
public void ShowSearchController ()
{
// Build an instance of the Search Results View Controller from the Storyboard
ResultsController = Storyboard.InstantiateViewController (SearchResultsID) as SearchResultsViewController;
if (ResultsController == null)
throw new Exception ("Unable to instantiate a SearchResultsViewController.");
// Create an initialize a new search controller
var searchController = new UISearchController (ResultsController) {
SearchResultsUpdater = ResultsController,
HidesNavigationBarDuringPresentation = false
};
// Set any required search parameters
searchController.SearchBar.Placeholder = "Enter keyword (e.g. coffee)";
// The Search Results View Controller can be presented as a modal view
// PresentViewController (searchController, true, null);
// Or in the case of this sample, the Search View Controller is being
// presented as the contents of the Search Tab directly. Use either one
// or the other method to display the Search Controller (not both).
var container = new UISearchContainerViewController (searchController);
var navController = new UINavigationController (container);
AddChildViewController (navController);
View.Add (navController.View);
}
#endregion
#region Override Methods
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// If the Search Controller is being displayed as the content
// of the search tab, include it here.
ShowSearchController ();
}
public override void ViewDidAppear (bool animated)
{
base.ViewDidAppear (animated);
// If the Search Controller is being presented as a modal view,
// call it here to display it over the contents of the Search
// tab.
// ShowSearchController ();
}
#endregion
}
}
首先,定義常數,其符合 介面設計工具中指派給搜尋結果集合檢視控制器的分鏡腳本標識碼 :
public const string SearchResultsID = "SearchResults";
接下來, ShowSearchController
方法會建立新的搜尋檢視集合控制器,並顯示它是必要的:
public void ShowSearchController ()
{
// Build an instance of the Search Results View Controller from the Storyboard
ResultsController = Storyboard.InstantiateViewController (SearchResultsID) as SearchResultsViewController;
if (ResultsController == null)
throw new Exception ("Unable to instantiate a SearchResultsViewController.");
// Create an initialize a new search controller
var searchController = new UISearchController (ResultsController) {
SearchResultsUpdater = ResultsController,
HidesNavigationBarDuringPresentation = false
};
// Set any required search parameters
searchController.SearchBar.Placeholder = "Enter keyword (e.g. coffee)";
// The Search Results View Controller can be presented as a modal view
// PresentViewController (searchController, true, null);
// Or in the case of this sample, the Search View Controller is being
// presented as the contents of the Search Tab directly. Use either one
// or the other method to display the Search Controller (not both).
var container = new UISearchContainerViewController (searchController);
var navController = new UINavigationController (container);
AddChildViewController (navController);
View.Add (navController.View);
}
在上述方法中,一旦 SearchResultsViewController
從 Storyboard 具現化 ,就會建立新的 UISearchController
,向使用者呈現搜尋字段和螢幕小鍵盤。 搜尋結果集合(如 所 SearchResultsViewController
定義)將會在此鍵盤下顯示。
接下來,SearchBar
會使用佔位元提示等資訊來設定 。 這會向使用者提供資訊,說明要預先格式化的搜尋類型。
然後,搜尋欄位會以下列兩種方式之一向用戶呈現:
- 強制回應對話框檢視 - 呼叫
PresentViewController
方法,以在現有檢視上顯示搜尋,全螢幕。 - 檢視內容 -
UISearchContainerViewController
建立包含搜尋控制器的 。UINavigationController
會建立 ,以包含搜尋容器,然後將瀏覽控制器新增至檢視控制器AddChildViewController (navController)
,並顯示View.Add (navController.View)
檢視 。
最後,再次根據簡報類型, ViewDidLoad
或 ViewDidAppear
方法會呼叫 ShowSearchController
方法,向用戶呈現搜尋:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// If the Search Controller is being displayed as the content
// of the search tab, include it here.
ShowSearchController ();
}
public override void ViewDidAppear (bool animated)
{
base.ViewDidAppear (animated);
// If the Search Controller is being presented as a modal view,
// call it here to display it over the contents of the Search
// tab.
// ShowSearchController ();
}
當應用程式執行且使用者選取的 [搜尋] 索引標籤時,系統會向使用者顯示完整的未篩選專案清單:
當使用者開始輸入搜尋字詞時,結果清單會依該字詞進行篩選,並自動更新:
用戶隨時都可以將焦點切換至搜尋結果中的專案,然後按兩下 Siri 遠端的 Touch Surface 加以選取。
摘要
本文涵蓋在 Xamarin.tvOS 應用程式中設計和使用文字和搜尋欄位。 它示範如何在介面設計工具中建立文字和搜尋集合內容,並且以兩種不同的方式向tvOS中的用戶顯示搜尋欄位。