Skip to content

Files

Latest commit

650a51f · Dec 26, 2021

History

History
743 lines (540 loc) · 34.1 KB

File metadata and controls

743 lines (540 loc) · 34.1 KB

五、iOS 的 XamSnap

要开始编写 iOS 版本的 XamSnap,请打开我们在上一章中创建的解决方案。我们将主要在本章的XamSnap.iOS项目中工作。项目模板将自动创建一个名为ViewController的控制器;继续删除它。我们将边走边创建自己的控制器。

在本章中,我们将介绍以下内容:

  • iOS 应用的基础
  • 使用 UINavigationController
  • 实现登录屏幕
  • Segues 和 UITableView
  • 添加朋友列表
  • 添加邮件列表
  • 撰写邮件

了解 iOS 应用的基础知识

在我们开始开发我们的应用之前,让我们回顾一下应用的主要设置。苹果使用一个名为Info.plist的文件来存储任何 iOS 应用的重要信息。这些设置由操作系统本身以及苹果应用商店在设备上安装 iOS 应用时使用。填写此文件中的信息,开始开发任何新的 iOS 应用。

Xamarin Studio 提供了一个整洁的菜单来修改Info.plist文件中的值,如下图截图所示:

Understanding the basics of an iOS app

最重要的设置如下:

  • 应用名称:这是 iOS 中一个应用图标下面的标题。请注意,这与您的应用在 iOS 应用商店中的官方名称不同。
  • 捆绑标识符:这是你的应用的捆绑标识符或者捆绑 ID。这是识别您的应用的唯一名称。惯例是使用以您公司名称开头的反向域名命名方式,如com.jonathanpeppers.xamsnap
  • 版本:这是你的应用在应用商店用户可见的版本号,比如1.0.0
  • 构建:这是为开发人员保留的版本号(用于 CI 构建等)。),如1.0.0.1234
  • 设备:在这里可以选择 iPhone/iPodiPad 或者万能(所有设备)进行应用。
  • 部署目标:这是应用运行的最低 iOS 版本。
  • 主界面:这是你 app 的主故事板文件。
  • 设备方向:这些是您的应用能够旋转并支持的不同位置。
  • 状态栏样式:这些是隐藏应用中顶部状态栏并全屏运行的选项。

应用图标、闪屏等还有其他设置。您也可以在高级选项卡之间切换,以配置 Xamarin 不提供用户友好菜单的附加设置。

为我们的应用配置以下设置:

  • 申请名称 : XamSnap
  • 捆标识符:com.yourcompanyname.xamsnap;确保你给未来的应用命名,让它们以com.yourcompanyname开头。
  • 设备 : iPhone/iPod
  • 部署目标 : 8.0
  • 支持的设备方向:仅选择人像

Xamarin.iOS 构建选项

如果你右击你的项目,选择选项,你可以找到一些 Xamarin iOS 应用的附加设置,如下图截图所示。了解 Xamarin Studio 中 iOS 特定项目的可用资源是一个好主意。这里发生了很多事情,但在大多数情况下,默认设置会让你通过。

Xamarin.iOS Build Options

让我们讨论一些最重要的选项,如下所示:

iOS 构建

  • SDK 版本:这是用来编译你的应用的 iOS SDK 的版本。一般最好使用默认
  • 链接器行为 : Xamarin 实现了一个名为的链接功能。链接器将删除在程序集中永远不会被调用的任何代码。这使您的应用保持较小,并允许他们在您的应用中附带核心 Mono 运行时的精简版本。除了调试版本,最好使用仅链接软件开发工具包程序集选项。我们将在未来的章节中讨论链接。
  • 支持的架构:这些是处理器的类型。i386是模拟器,ARMv7 + ARM64是现代 iOS 设备编译的选项。除非升级旧的 Xamarin.iOS 应用,否则通常可以使用这里的默认值。
  • HttpClient 实现:xamarin . IOs 的更新版本允许你为System.Net.Http.HttpClient选择一个原生的 HTTP 栈。Mono 的实现是默认的,但不如本机堆栈的性能好。
  • SSL/TLS 实现 : Xamarin.iOS 也可以选择使用本机 API 进行 SSL。如果您选择 Mono,您的应用将只支持 TLS 1.0,因此最好在这里使用本机选项。
  • 使用 LLVM 优化编译器:勾选此项会编译更小运行更快但编译时间更长的代码。 LLVM 代表低级虚拟机
  • 去除本机调试符号:当此选项打开时,Xamarin 会从您的应用中移除额外的信息,这些信息将使 Xamarin Studio 能够进行调试。
  • 附加 mtouch 参数:该字段用于将额外的命令行参数传递给 iOS 的 Xamarin 编译器。你可以在https://developer.xamarin.com/api查看这些论点的完整列表。
  • 优化 iOS 的 PNG 文件:苹果使用自定义的 PNG 格式来加快你的应用内 PNG 的加载速度。您可以关闭此功能以加快构建速度,或者您计划自己优化图像。

iOS 捆绑签约

  • 签名身份:这是识别应用创建者的证书,用于将应用部署到设备。我们将在后面的章节中详细介绍这一点。
  • 配置文件:这是一个将应用部署到设备的特定配置文件。这与签署身份协同工作,但也声明了分发方法和可以安装该应用的设备。
  • 自定义权限:该文件包含要应用于供应配置文件的附加设置,并包含应用的其他特定声明,如 iCloud 或推送通知。iOS 应用的项目模板包括新项目的默认Entitlements.plist文件。

对于这个应用,您可以保留所有这些选项的默认值。当你自己制作一个真正的 iOS 应用时,你应该考虑根据你的应用的需要改变其中的许多。

使用 UINavigationController

在 iOS 应用中,管理不同控制器之间导航的关键类是UINavigationController类。它是一个父控制器,在一个堆栈中包含多个子控制器。用户可以通过在堆栈顶部放置新的控制器,或者使用内置的后退按钮将控制器弹出堆栈并导航到上一个屏幕来前进。

开发人员可以通过以下方法操作导航控制器的堆栈:

  • SetViewControllers:这设置了一个子控制器的数组。它有一个可选的动画过渡的值。
  • ViewControllers:这是获取或设置子控制器数组的属性,没有动画选项。
  • PushViewController:这将在堆栈顶部放置一个新的子控制器,并有一个显示动画的选项。
  • PopViewController:这将弹出堆栈顶部的子控制器,并有一个选项来激活过渡。
  • PopToViewController:弹出到指定的子控制器,移除上面的所有控制器。它提供了动画转换的选项。
  • PopToRootViewController:这将移除除最底部控制器之外的所有子控制器。它包括一个显示动画的选项。
  • TopViewController:这是一个返回当前在堆栈顶部的子控制器的属性。

类型

需要注意的是,如果在动画过程中试图修改堆栈,对动画使用该选项将导致崩溃。要解决这种情况,要么使用SetViewControllers方法并设置整个子控制器列表,要么在组合过渡期间避免使用动画。

让我们通过执行以下步骤在应用中设置导航控制器:

  1. 双击Main.storyboard文件,在 Xamarin Studio 中打开。
  2. 移除由项目模板创建的控制器。
  3. 将右侧工具箱中的导航控制器元素拖到故事板上。
  4. 请注意,已经创建了默认的视图控制器元素,以及导航控制器
  5. 您将看到一个连接两个控制器的片段。我们将在本章后面更详细地介绍这个概念。
  6. 保存故事板文件。

类型

只是给 Visual Studio 用户的一个提示,Xamarin 在使他们的 Visual Studio 扩展与 Xamarin Studio 完全相同方面做得很好。本章中的所有示例都应该像 OS X 的 Xamarin Studio 或 Windows 的 Visual Studio 中描述的那样工作。当然,一个例外是用于部署到模拟器或 iOS 设备的远程连接 mac。

如果此时运行应用,您将拥有一个顶部带有状态栏的基本 iOS 应用、一个包含带有默认标题的导航栏的导航控制器和一个完全为白色的子控制器,如下图所示:

Using UINavigationController

实现登录屏幕

由于我们应用的第一个屏幕将是登录屏幕,让我们从在故事板文件中设置适当的视图开始。我们将通过使用 Xamarin Studio 编写 C#代码来实现登录屏幕,并使用其 iOS 设计器在故事板文件中创建 iOS 布局。

返回 Xamarin Studio 中的项目,并执行以下步骤:

  1. 双击Main.storyboard文件,在 iOS 设计器中打开。
  2. 选择您的视图控制器,点击属性面板并选择部件标签。
  3. 进入字段。
  4. 注意LoginController类是为你生成的。如果您愿意,您可以创建一个Controllers文件夹并将文件移入其中。

下面的屏幕截图显示了控制器的设置在进行更改后在 Xamarin Studio 中的样子:

Implementing the login screen

现在,让我们通过执行以下步骤来修改控制器的布局:

  1. 再次双击Main.storyboard文件返回 iOS 设计器。
  2. 点击导航栏,编辑标题字段,阅读Login
  3. 将两个文本字段拖到控制器上。根据用户名和密码条目对其进行适当定位和调整。您可能还想删除默认文本以使字段为空。
  4. 对于第二个字段,勾选安全文本输入复选框。这将设置控件隐藏密码字段的字符。
  5. 您可能还想为UsernamePassword填写占位符字段。
  6. 将按钮拖到控制器上。将按钮的标题设置为Login
  7. 将活动指示器拖到控制器上。选中激活隐藏复选框。
  8. 接下来,通过填写名称字段为每个控件创建出口。分别命名网点usernamepasswordloginindicator
  9. 保存故事板文件,看一下LoginController.designer.cs

您会注意到 Xamarin Studio 已经为每个插座生成了属性:

Implementing the login screen

继续编译应用,确保一切正常。此时,我们还需要添加对上一章创建的XamSnap.Core项目的引用。

接下来,让我们设置我们的 iOS 应用来注册它的所有视图模型以及将在整个应用中使用的其他服务。我们将使用我们在第 4 章XamSnap -一个跨平台应用中创建的ServiceContainer类来设置我们整个应用中的依赖关系。打开AppDelegate.cs并添加以下方法:

public override bool FinishedLaunching(
   UIApplication application,
   NSDictionary launchOptions) 
{ 
  //View Models 
  ServiceContainer.Register<LoginViewModel>(() =>
     new LoginViewModel()); 
  ServiceContainer.Register<FriendViewModel>(() =>
     new FriendViewModel()); 
  ServiceContainer.Register<RegisterViewModel>(() =>
     new RegisterViewModel()); 
  ServiceContainer.Register<MessageViewModel>(() =>
     new MessageViewModel()); 

  //Models 
  ServiceContainer.Register<ISettings>(() =>
     new FakeSettings()); 
  ServiceContainer.Register<IWebService>(() =>
     new FakeWebService()); 

  return true; 
} 

未来,我们将用真正的服务取代虚假的服务。现在让我们将登录功能添加到LoginController.cs中。首先将LoginViewModel添加到类顶部的成员变量中,如下所示:

readonly LoginViewModel loginViewModel =
   ServiceContainer.Resolve<LoginViewModel>(); 

这将把LoginViewModel的共享实例拉入控制器的局部变量中。这是我们将在整本书中使用的模式,以便将共享视图模型从一个类传递到另一个类。

接下来,覆盖ViewDidLoad以将视图模型的功能与插座中设置的视图挂钩,如下所示:

public override void ViewDidLoad() 
{ 
  base.ViewDidLoad(); 

  login.TouchUpInside += async(sender, e) => 
  { 
    loginViewModel.UserName = username.Text; 
    loginViewModel.Password = password.Text; 

    try 
    { 
      await loginViewModel.Login(); 

      //TODO: navigate to a new screen 
    } 
    catch (Exception exc) 
    { 
      new UIAlertView("Oops!", exc.Message, null, "Ok").Show(); 
    } 
  }; 
} 

我们将在本章后面添加代码以导航到新屏幕。

接下来,让我们连接IsBusyChanged事件来实际执行一个动作,如下所示:

public override void ViewWillAppear(bool animated) 
{ 
  base.ViewWillAppear(animated); 

  loginViewModel.IsBusyChanged += OnIsBusyChanged; 
} 

public override void ViewWillDisappear(bool animated) 
{ 
  base.ViewWillDisappear(animated); 

  loginViewModel.IsBusyChanged -= OnIsBusyChanged; 
} 

void OnIsBusyChanged(object sender, EventArgs e) 
{ 
  username.Enabled = 
    password.Enabled = 
    login.Enabled =  
    indicator.Hidden = !loginViewModel.IsBusy; 
} 

现在,你可能会问为什么我们以这种方式订阅活动。问题是LoginViewModel类将持续你的应用的整个生命周期,而LoginController类不会。如果我们在ViewDidLoad订阅了活动,但是后来没有退订,那么我们的应用就会出现内存泄漏。我们还避免对事件使用 lambda 表达式,因为否则就不可能取消对事件的订阅。

请注意,按钮上的TouchUpInside事件没有同样的问题,因为只要控制器有问题,它就会存在于内存中。这是 C#中事件的常见问题,这就是为什么在 iOS 上使用前面的模式是一个好主意。

如果您现在运行应用,您应该能够输入用户名和密码,如下图所示。按下登录,你会看到指示灯出现,所有控制被禁用。您的应用将正确地调用共享代码,并且在我们添加一个真正的 web 服务时应该能够正常工作。

Implementing the login screen

使用分段导航

分段是从一个控制器到另一个控制器的转换。同样,故事板文件是控制器和它们的视图的集合,由 segues 连接在一起。这又允许您同时看到每个控制器的布局和应用的一般流程。

segue 只有几个类别,如下所示:

  • 按压:用于导航控制器内。它将新控制器推到导航控制器堆栈的顶部。Push 使用导航控制器的标准动画技术,通常是最常用的片段。
  • 关系:用于为另一个控制器设置一个子控制器。例如,导航控制器的根控制器、容器视图或 iPad 应用中的拆分视图控制器。
  • 模态:使用该功能时,模态呈现的控制器将出现在父控制器的顶部。它将覆盖整个屏幕,直到解散。有几种不同类型的过渡动画可用。
  • 自定义:这是一个自定义段,包含一个自定义类的选项,它子类化UIStoryboardSegue。这为您提供了对动画以及下一个控制器如何呈现的精细控制。

Segues 在执行时也遵循以下模式:

  • 将创建目标控制器及其视图。
  • 创建了UIStoryboardSegue的子类 segue 对象。这通常只对自定义片段很重要。
  • 在源控制器上调用PrepareForSegue方法。这是一个在程序开始前运行定制代码的好地方。
  • 调用 segue 的Perform方法,开始过渡动画。这是定制段的大部分代码所在的地方。

在 Xamarin.iOS 设计器中,您可以选择从按钮或表格视图行中自动触发 segue,或者只给 segue 一个标识符。在第二种情况下,您可以通过使用源控制器的标识符在源控制器上调用PerformSegue方法来自己启动 segue。

现在让我们通过执行以下步骤来设置我们的Main.storyboard文件的一些方面,从而设置一个新的片段:

  1. 双击Main.storyboard文件,在 iOS 设计器中打开。
  2. 向故事板添加新的表格视图控制器
  3. 选择您的视图控制器,导航至属性窗格和小部件选项卡。
  4. 进入字段。
  5. 向下滚动到视图控制器部分,输入Conversations标题
  6. 按住 Ctrl 并从一个控制器拖动蓝线到另一个控制器,通过单击创建从LoginControllerConversationsController的分段。
  7. 从出现的弹出窗口中选择显示部分。
  8. 点击选择该段,并给它一个OnLogin标识符
  9. 保存故事板文件。

您的故事板看起来类似于下面截图中显示的内容:

Using segues for navigation

打开LoginController.cs并修改我们在本章前面标记为TODO的代码行,如下所示:

PerformSegue("OnLogin", this); 

现在,如果您构建并运行应用,您将在成功登录后导航到新的控制器。将执行 segue,您将看到导航控制器提供的内置动画。

设置可用视图

接下来,让我们在第二个控制器上设置表视图。我们在 iOS 上使用了一个强大的类UITableView。它用于许多情况,与其他平台上的列表视图非常相似。UITableView类由另一个名为UITableViewSource的类控制。它有一些方法,您需要覆盖这些方法来设置应该存在多少行以及这些行应该如何显示在屏幕上。

类型

注意UITableViewSourceUITableViewDelegateUITableViewDataSource的组合。为了简单起见,我更喜欢使用UITableViewSource,因为经常需要同时使用另外两个类。

在我们切入并开始编码之前,让我们回顾一下UITableViewSource上最常用的方法,如下所示:

  • RowsInSection:这个方法可以定义一个节的行数。所有表视图都有许多节和行。默认情况下,只有一个部分;但是,要求返回一个节中的行数。
  • NumberOfSections:这是表格视图中的节数。
  • GetCell:此方法必须为每行返回一个单元格。由开发人员来设置单元格的外观;您可以设置表格视图来回收单元格。滚动时回收单元格将产生更好的性能。
  • TitleForHeader:如果被覆盖,这个方法是返回标题字符串最简单的方法。默认情况下,表视图中的每个部分都可以有一个标准的标题视图。
  • RowSelected:当用户选择一行时会调用这个方法。

您可以覆盖其他方法,但这些方法会让您在大多数情况下都可以继续。如果需要开发自定义样式的表格视图,也可以设置自定义页眉和页脚。

现在我们打开ConversationsController.cs文件,在ConversationsController里面创建一个嵌套类,如下所示:

class TableSource : UITableViewSource 
{ 
  const string CellName = "ConversationCell"; 
  readonly MessageViewModel messageViewModel =
     ServiceContainer.Resolve<MessageViewModel>(); 

  public override nint RowsInSection(
     UITableView tableview, nint section) 
  { 
    return messageViewModel.Conversations == null ?
       0 : messageViewModel.Conversations.Length; 
  } 

  public override UITableViewCell GetCell(
     UITableView tableView, NSIndexPath indexPath) 
  { 
    var conversation =
       messageViewModel.Conversations[indexPath.Row]; 
    var cell = tableView.DequeueReusableCell(CellName); 
    if (cell == null) 
    { 
      cell = new UITableViewCell(
         UITableViewCellStyle.Default, CellName); 
      cell.Accessory =
         UITableViewCellAccessory.DisclosureIndicator; 
    } 
    cell.TextLabel.Text = conversation.UserName; 
    return cell; 
  } 
} 

我们实现了设置表视图所需的两种方法:RowsInSectionGetCell。我们返回了在视图模型中找到的对话数量,并为每行设置了单元格。我们还使用UITableViewCellAccessory.DisclosureIndicator为用户添加了一个指示器,让他们可以点击该行。

请注意我们对回收单元的实现。用单元格标识符调用DequeueReusableCell将第一次返回一个null单元格。如果null,您应该使用相同的单元格标识符创建一个新的单元格。对DequeueReusableCell的后续调用将返回一个现有的单元格,使您能够重用它。您也可以在故事板文件中定义TableView单元格,这对自定义单元格很有用。我们这里的单元格非常简单,所以从代码中定义它更容易。在移动平台上,回收电池对于保存内存和为用户提供非常流畅的滚动表格非常重要。

接下来,我们需要在TableView上设置TableView源。给我们的ConversationsController类添加一些更改,如下所示:

readonly MessageViewModel messageViewModel = 
  ServiceContainer.Resolve<MessageViewModel>(); 

public override void ViewDidLoad() 
{ 
  base.ViewDidLoad(); 

  TableView.Source = new TableSource(); 
} 

public async override void ViewWillAppear(bool animated) 
{ 
  base.ViewWillAppear(animated); 

  try 
  { 
    await messageViewModel.GetConversations(); 

    TableView.ReloadData(); 
  } 
  catch(Exception exc) 
  { 
    new UIAlertView("Oops!", exc.Message, null, "Ok").Show(); 
  } 
} 

因此,当视图出现时,我们将加载对话列表。完成该任务后,我们将重新加载表视图,以便它显示我们的对话列表。如果您运行该应用,登录后您会看到一些对话出现在表格视图中,如下图所示。接下来,当我们从一个真实的 web 服务加载对话时,一切都将以相同的方式运行。

Setting up UITableView

添加好友列表屏幕

我们的 XamSnap 应用需要的下一个屏幕是我们的朋友列表。创建新对话时,该应用将加载一个朋友列表以开始对话。我们将遵循非常相似的模式来加载我们的对话列表。

首先,我们将通过执行以下步骤来创建UIBarButtonItem,它将导航到名为FriendsController的新控制器:

  1. 双击Main.storyboard文件,在 iOS 设计器中打开。
  2. 向故事板添加新的表格视图控制器
  3. 选择您的视图控制器,点击属性面板,并确保您已经选择了部件标签。
  4. 进入字段。
  5. 向下滚动至查看控制器部分,并在标题字段中输入Friends
  6. 导航项目工具箱拖到ConversationsController上。
  7. 创建一个新的栏按钮项目元素,并将其放置在新导航栏的右上角。
  8. 在工具栏按钮的属性面板中,将其标识符设置为添加。这将使用内置的加号按钮,这在整个 iOS 应用中普遍使用。
  9. 按住 Ctrl 并将蓝色线条从条形按钮拖动到下一个控制器,创建从条形按钮项目FriendsController的分段。
  10. 从出现的弹出窗口中选择显示部分。
  11. 保存故事板文件。

您对故事板的更改看起来应该类似于下面截图中显示的内容:

Adding a friends list screen

您应该会看到一个新的FriendsController类,这是 Xamarin Studio 为您生成的。如果您编译并运行该应用,您将看到我们创建的新工具栏按钮项。点击它将引导您找到新的控制器。

现在让我们实现UITableViewSource来显示我们的好友列表。从FriendsController内部的新嵌套类开始,如下所示:

class TableSource : UITableViewSource 
{ 
  const string CellName = "FriendCell"; 
  readonly FriendViewModel friendViewModel =
     ServiceContainer.Resolve<FriendViewModel>(); 

  public override nint RowsInSection(
     UITableView tableview, nint section) 
  { 
    return friendViewModel.Friends == null ?
       0 : friendViewModel.Friends.Length; 
  } 

  public override UITableViewCell GetCell(
     UITableView tableView, NSIndexPath indexPath) 
  { 
    var friend =
       friendViewModel.Friends[indexPath.Row]; 
    var cell = tableView.DequeueReusableCell(CellName); 
    if (cell == null) 
    { 
      cell = new UITableViewCell(
         UITableViewCellStyle.Default, CellName); 
      cell.AccessoryView =
         UIButton.FromType(UIButtonType.ContactAdd); 
      cell.AccessoryView.UserInteractionEnabled = false; 
    } 
    cell.TextLabel.Text = friend.Name; 
    return cell; 
  } 
} 

就像以前一样,我们实现了表格单元回收,并且仅仅为每个朋友在标签上设置了文本。我们使用cell.AccessoryView向用户指示每个单元格都是可点击的,并开始新的对话。我们禁用了按钮上的用户交互,只是为了在用户单击按钮时允许选择行。否则,我们必须为按钮实现一个点击事件。

接下来,我们需要以与对话相同的方式修改FriendsController,如下所示:

readonly FriendViewModel friendViewModel =
   ServiceContainer.Resolve<FriendViewModel>(); 

public override void ViewDidLoad() 
{ 
  base.ViewDidLoad(); 

  TableView.Source = new TableSource(); 
} 

public async override void ViewWillAppear(bool animated) 
{ 
  base.ViewWillAppear(animated); 

  try 
  { 
    await friendViewModel.GetFriends(); 

    TableView.ReloadData(); 
  } 
  catch(Exception exc) 
  { 
    new UIAlertView("Oops!", exc.Message, null, "Ok").Show(); 
  } 
} 

这将与对话列表完全相同:控制器将异步加载好友列表并刷新表视图。如果您编译并运行该应用,您将能够导航到屏幕并查看我们在第 4 章xamsap-A 跨平台应用中创建的示例好友列表,如下图所示:

Adding a friends list screen

添加消息列表

现在让我们实现屏幕来查看对话或消息列表。我们将尝试在 iOS 的内置文本消息应用上模拟屏幕。为此,我们还将介绍如何创建自定义表格视图单元格的基础知识。

首先,我们需要一个新的MessagesController类;执行以下步骤:

  1. 双击Main.storyboard文件,在 iOS 设计器中打开。
  2. 向故事板添加新的表格视图控制器
  3. 选择您的视图控制器,点击属性面板,并确保您已经选择了部件标签。
  4. 进入字段。
  5. 向下滚动至查看控制器部分,并在标题字段中输入Messages
  6. 按住并拖动蓝线从一个控制器到另一个控制器,创建一个从ConversationsControllerMessagesController的分段。 *** 从出现的弹出窗口中选择显示部分。在属性窗格中输入标识符 OnConversation。* 现在在MessagesController的表格视图中创建两个表格视图单元格。您可以重用默认创建的现有空白。* 将每个单元格的样式字段更改为基本。* 为每个单元格分别设置标识符MyCellTheirCell。* 保存故事板文件。**

**Xamarin 工作室将生成MessagesController.cs。就像以前一样,你可以将控制器移动到Controllers文件夹。现在打开MessagesController.cs并在嵌套类中实现UITableViewSource,如下所示:

class TableSource : UITableViewSource
{
  const string MyCellName = "MyCell";
  const string TheirCellName = "TheirCell";
  readonly MessageViewModel messageViewModel =
    ServiceContainer.Resolve();
  readonly ISettings settings = ServiceContainer.Resolve();

  public override nint RowsInSection(
    UITableView tableview, nint section)
  {
    return messageViewModel.Messages == null ? 0 :
      messageViewModel.Messages.Length;
  }

  public override UITableViewCell GetCell(
    UITableView tableView, NSIndexPath indexPath)
  {
    var message = messageViewModel.Messages [indexPath.Row];
    bool isMyMessage = message.UserName == settings.User.Name;
    var cell = (BaseMessageCell)tableView.DequeueReusableCell(
      isMyMessage ? MyCellName : TheirCellName);
    cell.TextLabel.Text = message.Text;
    return cell;
  }
}

我们添加了一些逻辑来检查消息是否来自当前用户,以决定适当的表单元标识符。由于我们对两个单元格都使用了基本样式,因此我们可以使用单元格上的TextLabel属性来设置UILabel的文本。

现在让我们对MessagesController进行所需的更改,如下所示:

readonly MessageViewModel messageViewModel = 
  ServiceContainer.Resolve<MessageViewModel>(); 

public override void ViewDidLoad() 
{ 
  base.ViewDidLoad(); 

  TableView.Source = new TableSource(); 
} 

public async override void ViewWillAppear(bool animated) 
{ 
  base.ViewWillAppear(animated); 

  Title = messageViewModel.Conversation.UserName; 
  try 
  { 
    await messageViewModel.GetMessages(); 
    TableView.ReloadData(); 
  } 
  catch (Exception exc) 
  { 
    new UIAlertView("Oops!", exc.Message, null, "Ok").Show(); 
  } 
} 

这里唯一的新内容是我们将Title属性设置为对话的用户名。

为了完成我们的自定义单元格,我们需要通过执行以下步骤在 Xcode 中进行更多更改:

  1. 双击Main.storyboard文件,在 iOS 设计器中打开。
  2. 单击任一单元格上的默认文本标题,选择一个标签
  3. 使用一些创意来设计这两个标签。我选择将文字做成MyCell蓝色和TheirCell绿色。我将标签上的对齐设置为在TheirCell中右对齐。
  4. 保存故事板文件并返回。

接下来,我们需要更新ConversationsController来导航到这个新屏幕。让我们修改ConversationsController.cs里面的TableSource类,如下所示:

readonly ConversationsController controller; 

public TableSource(ConversationsController controller) 
{ 
  this.controller = controller;
}

public override void RowSelected(
  UITableView tableView, NSIndexPath indexPath)
{ 
  var conversation = messageViewModel.Conversations[indexPath.Row]; 
  messageViewModel.Conversation = conversation; 
  controller.PerformSegue("OnConversation", this); 
}

当然,你必须修改控制器 ViewDidLoad中的一小段:

TableView.Source = new TableSource(this); 

如果您现在运行应用,您将能够查看消息列表,如下图所示:

Adding a list of messages

撰写消息

对于我们应用的最后一部分,我们需要实现一些苹果没有提供的定制功能。我们需要添加一个文本字段,该字段带有一个似乎附加在表格视图底部的按钮。其中大部分需要编写一些简单的 C#代码和连接事件。

让我们从给我们的MessagesController类添加一些新的成员变量开始,如下所示:

UIToolbar toolbar; 
UITextField message; 
UIBarButtonItem send; 

我们将在工具栏内放置文本字段和条形按钮,如ViewDidLoad中的以下代码所示:

public override void ViewDidLoad() 
{ 
  base.ViewDidLoad(); 

  //Text Field 
  message = new UITextField( 
    new CGRect(0, 0, TableView.Frame.Width - 88, 32)) 
  { 
    BorderStyle = UITextBorderStyle.RoundedRect, 
    ReturnKeyType = UIReturnKeyType.Send, 
    ShouldReturn = _ => 
    { 
        Send(); 
        return false; 
    }, 
  }; 

  //Bar button item 
  send = new UIBarButtonItem("Send", UIBarButtonItemStyle.Plain, 
    (sender, e) => Send()); 

  //Toolbar 
  toolbar = new UIToolbar( 
    new CGRect(0, TableView.Frame.Height - 44,  
      TableView.Frame.Width, 44)); 
  toolbar.Items = new[] 
  { 
    new UIBarButtonItem(message), 
    send 
  }; 

  TableView.Source = new TableSource(); 
  TableView.TableFooterView = toolbar; 
} 

大部分工作是基本的用户界面设置。这不是我们可以在 Xcode 内部做的事情,因为它是一个非常具体的用例。我们从 C#中创建一个文本字段、栏按钮项和工具栏,并将它们作为页脚添加到我们的UITableView中。这将在表格视图的底部显示工具栏,位于我们之前定义的任何行的下方。

现在我们需要修改ViewWillAppear,如下:

public async override void ViewWillAppear(bool animated) 
{ 
  base.ViewWillAppear(animated); 

  Title = messageViewModel.Conversation.Username; 

  messageViewModel.IsBusyChanged += OnIsBusyChanged; 

  try 
  { 
    await messageViewModel.GetMessages(); 
    TableView.ReloadData(); 
    message.BecomeFirstResponder(); 
  } 
  catch (Exception exc) 
  { 
    new UIAlertView("Oops!", exc.Message, null, "Ok").Show(); 
  } 
} 

为了显示和隐藏旋转器,我们需要订阅IsBusyChanged。我们也叫BecomeFirstResponder,这样键盘就会出现,把焦点给我们的文本字段。

因此,让我们为ViewWillDisapper添加一个覆盖来清理事件,如下所示:

public override void ViewWillDisappear(bool animated) 
{ 
  base.ViewWillDisappear(animated); 

  messageViewModel.IsBusyChanged -= OnIsBusyChanged; 
} 

接下来,我们为IsBusyChanged设置方法,如下:

void OnIsBusyChanged (object sender, EventArgs e) 
{ 
  message.Enabled = send.Enabled = !messageViewModel.IsBusy; 
} 

OnIsBusyChanged用于在加载时禁用我们的一些视图。

最后但同样重要的是,我们需要实现一个发送新消息的函数,如下所示:

async void Send() 
{ 
  //Just hide the keyboard if they didn't type anything 
  if (string.IsNullOrEmpty(message.Text)) 
  { 
    message.ResignFirstResponder(); 
    return; 
  } 

  //Set the text, send the message 
  messageViewModel.Text = message.Text; 
  await messageViewModel.SendMessage(); 

  //Clear the text field & view model 
  message.Text = messageViewModel.Text = string.Empty; 

  //Reload the table 
  TableView.InsertRows(new[]  
  {  
    NSIndexPath.FromRowSection( 
      messageViewModel.Messages.Length - 1, 0)  
  }, UITableViewRowAnimation.Automatic); 
} 

这段代码也相当简单。发送完消息后,我们只需要清除文本字段,告诉表视图重新加载新添加的行,如下图所示。使用async关键字使这变得容易。

Composing messages

总结

在本章中,我们介绍了苹果和 Xamarin 为开发 iOS 应用提供的基本设置。这包括 Xamarin Studio 中的Info.plist文件和项目选项。我们介绍了UINavigationController,iOS 应用中导航的基本构建模块,并实现了一个包含用户名和密码字段的登录屏幕。接下来,我们学习了 iOS segues 和UITableView课程。我们使用UITableView实现了好友列表屏幕,使用UITableView实现了消息列表屏幕。最后,我们添加了一个自定义用户界面功能:一个浮动在消息列表底部的自定义工具栏。

完成本章后,您将拥有部分功能的 iOS 版本的 XamSnap。您将对 iOS 平台和工具有更深入的了解,并有相当好的知识应用于构建自己的 iOS 应用。请自行实现本章中未涉及的其余屏幕。如果您迷路了,请随意查看本书附带的完整示例应用。

在下一章中,我们将在安卓上实现这些用户界面。**