在 Xamarin.iOS 中使用代码创建 iOS 用户界面

iOS 应用的用户界面类似于店面 - 应用程序通常只获取一个窗口,但是它可以根据需要使用任意数量的对象填满窗口,并且可以根据应用要显示的内容更改对象和排列方式。 此情形中的对象(用户看到的内容)称为视图。 为了在应用程序中构建单个屏幕,视图会在内容视图层次结构中互相堆叠,该层次结构由单个视图控制器进行管理。 具有多个屏幕的应用程序具有多个内容视图层次结构(各自具有自己的视图控制器),应用程序会将视图置于窗口中以基于用户所处的屏幕创建不同的内容视图层次结构。

下图显示了窗口、视图、子视图与视图控制器之间的关系,它们向设备屏幕提供了用户界面:

This diagram illustrates the relationships between the Window, Views, Subviews, and View Controller

可以使用 Xcode 的 Interface Builder 构造这些视图层次结构,但你最好能基本了解如何完全用代码来完成此工作。 本文逐步讲解了开始和使用纯代码用户界面开发的一些基本要点。

创建纯代码项目

iOS 空白项目模板

首先,在 Visual Studio 中使用“文件”>“新建项目”>“Visual C#”>“iPhone 和 iPad”>“iOS 应用(Xamarin)”项目创建一个 iOS 项目,如下所示:

New Project Dialog

然后,选择“空白应用”项目模板:

Select a Template Dialog

“空项目”模板向项目中添加 4 个文件:

Project Files

  1. AppDelegate.cs - 包含一个 UIApplicationDelegate 子类 (AppDelegate),该子类用于处理来自 iOS 的应用程序事件。 应用程序窗口是在 AppDelegateFinishedLaunching 方法中创建的。
  2. Main.cs - 包含应用程序的入口点,该入口点指定了 AppDelegate 的类。
  3. Info.plist - 包含应用程序配置信息的属性列表文件。
  4. Entitlements.plist – 包含应用程序的功能和权限信息的属性列表文件。

iOS 应用程序是使用 MVC 模式构建的。 应用程序显示的第一个屏幕是从窗口的根视图控制器创建的。 有关 MVC 模式本身的更多详细信息,请参阅你好,iOS 多屏指南。

模板添加的 AppDelegate 实现通过以下代码创建应用程序窗口(每个 iOS 应用程序只有一个窗口),并使其可见:

public class AppDelegate : UIApplicationDelegate
{
    public override UIWindow Window
            {
                get;
                set;
            }

    public override bool FinishedLaunching(UIApplication app, NSDictionary options)
    {
        // create a new window instance based on the screen size
        Window = new UIWindow(UIScreen.MainScreen.Bounds);

        // make the window visible
        Window.MakeKeyAndVisible();

        return true;
    }
}

如果你现在要运行此应用程序,可能会引发一个异常,指出 Application windows are expected to have a root view controller at the end of application launch。 让我们添加一个控制器,并将其设为该应用的根视图控制器。

添加控制器

你的应用可以包含许多视图控制器,但它需要有一个根视图控制器来控制所有视图控制器。 通过创建 UIViewController 实例并将其设置为 Window.RootViewController 属性,将控制器添加到窗口中:

public class AppDelegate : UIApplicationDelegate
{
    // class-level declarations

    public override UIWindow Window
    {
        get;
        set;
    }

    public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
    {
        // create a new window instance based on the screen size
        Window = new UIWindow(UIScreen.MainScreen.Bounds);

        var controller = new UIViewController();
        controller.View.BackgroundColor = UIColor.LightGray;

        Window.RootViewController = controller;

        // make the window visible
        Window.MakeKeyAndVisible();

        return true;
    }
}

每个控制器都有一个可通过 View 属性访问的关联视图。 上面的代码将视图的 BackgroundColor 属性更改为 UIColor.LightGray,以使其可见,如下所示:

The View's background is a visible light gray

我们还可以通过此方式将任何 UIViewController 子类设置为 RootViewController,包括 UIKit 中的控制器以及我们自己编写的控制器。 例如,下面的代码将 UINavigationController 添加为 RootViewController

public class AppDelegate : UIApplicationDelegate
{
    // class-level declarations

    public override UIWindow Window
    {
        get;
        set;
    }

    public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
    {
        // create a new window instance based on the screen size
        Window = new UIWindow(UIScreen.MainScreen.Bounds);

        var controller = new UIViewController();
        controller.View.BackgroundColor = UIColor.LightGray;
        controller.Title = "My Controller";

        var navController = new UINavigationController(controller);

        Window.RootViewController = navController;

        // make the window visible
        Window.MakeKeyAndVisible();

        return true;
    }
}

这会生成嵌套在导航控制器中的控制器,如下所示:

The controller nested within the navigation controller

创建视图控制器

现在,我们已经了解了如何将控制器添加为窗口的 RootViewController,接下来让我们了解如何用代码创建自定义视图控制器。

如下所示添加一个名为 CustomViewController 的新类:

该类应当继承自 UIKit 命名空间中的 UIViewController,如下所示:

using System;
using UIKit;

namespace CodeOnlyDemo
{
    class CustomViewController : UIViewController
    {
    }
}

初始化视图

UIViewController 包含一个名为 ViewDidLoad 的方法,首次将视图控制器加载到内存中时将调用该方法。 这是对视图进行初始化的合适位置,例如设置视图的属性。

例如,以下代码添加一个按钮和一个事件处理程序,以便在该按钮被按下时将一个新的视图控制器推送到导航堆栈:

using System;
using CoreGraphics;
using UIKit;

namespace CodyOnlyDemo
{
    public class CustomViewController : UIViewController
    {
        public CustomViewController ()
        {
        }

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

            View.BackgroundColor = UIColor.White;
            Title = "My Custom View Controller";

            var btn = UIButton.FromType (UIButtonType.System);
            btn.Frame = new CGRect (20, 200, 280, 44);
            btn.SetTitle ("Click Me", UIControlState.Normal);

            var user = new UIViewController ();
            user.View.BackgroundColor = UIColor.Magenta;

            btn.TouchUpInside += (sender, e) => {
                this.NavigationController.PushViewController (user, true);
            };

            View.AddSubview (btn);

        }
    }
}

若要在应用程序中加载此控制器并演示简单的导航,请创建 CustomViewController 的一个新实例。 创建一个新的导航控制器,传入你的视图控制器实例,并像前面一样在 AppDelegate 中将新的导航控制器设置为窗口的 RootViewController

var cvc = new CustomViewController ();

var navController = new UINavigationController (cvc);

Window.RootViewController = navController;

现在,当应用程序加载时,CustomViewController 将加载到导航控制器中:

The CustomViewController is loaded inside a navigation controller

单击该按钮会将一个新的视图控制器推送到导航堆栈:

A new View Controller pushed onto the navigation stack

构建视图层次结构

在上面的示例中,我们通过向视图控制器中添加按钮,开始用代码创建用户界面。

iOS 用户界面由视图层次结构组成。 其他视图(例如标签、按钮、滑块等)添加为某些父视图的子视图。

例如,让我们编辑 CustomViewController 来创建登录屏幕,用户可以在登录屏幕中输入用户名和密码。 该屏幕将包含两个文本字段和一个按钮。

添加文本字段

首先,移除在初始化视图部分中添加的按钮和事件处理程序。

通过创建并初始化一个 UITextField 并将其添加到视图层次结构中,来为用户名添加控件,如下所示:

class CustomViewController : UIViewController
{
    UITextField usernameField;

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

        View.BackgroundColor = UIColor.Gray;

        nfloat h = 31.0f;
        nfloat w = View.Bounds.Width;

        usernameField = new UITextField
        {
            Placeholder = "Enter your username",
            BorderStyle = UITextBorderStyle.RoundedRect,
            Frame = new CGRect(10, 82, w - 20, h)
        };

        View.AddSubview(usernameField);
    }
}

创建 UITextField 时,我们设置 Frame 属性来定义其位置和大小。 在 iOS 中,0,0 坐标位于左上角,向右为 +x,向下为 +y。 设置 Frame 以及其他几个属性后,我们调用 View.AddSubview 来将 UITextField 添加到视图层次结构。 这将使 usernameField 成为 View 属性引用的 UIView 实例的子视图。 添加的子视图的 z 顺序高于其父视图,因此它在屏幕上显示在父视图前面。

下面显示了包括 UITextField 的应用程序:

The application with the UITextField included

我们可以通过类似的方式为密码添加 UITextField,但这次我们将 SecureTextEntry 属性设置为 true,如下所示:

public class CustomViewController : UIViewController
{
    UITextField usernameField, passwordField;
    public override void ViewDidLoad()
    {
       // keep the code the username UITextField
        passwordField = new UITextField
        {
            Placeholder = "Enter your password",
            BorderStyle = UITextBorderStyle.RoundedRect,
            Frame = new CGRect(10, 114, w - 20, h),
            SecureTextEntry = true
        };

      View.AddSubview(usernameField);
      View.AddSubview(passwordField);
   }
}

设置 SecureTextEntry = true 将隐藏用户在 UITextField 中输入的文本,如下所示:

Setting SecureTextEntry true hides the text entered by the user

添加按钮

接下来,我们将添加一个按钮,以便用户可以提交用户名和密码。 该按钮将像任何其他控件一样添加到视图层次结构中,方法同样是将其作为参数传递给父视图的 AddSubview 方法。

以下代码添加该按钮并注册 TouchUpInside 事件的事件处理程序:

var submitButton = UIButton.FromType (UIButtonType.RoundedRect);

submitButton.Frame = new CGRect (10, 170, w - 20, 44);
submitButton.SetTitle ("Submit", UIControlState.Normal);

submitButton.TouchUpInside += (sender, e) => {
    Console.WriteLine ("Submit button pressed");
};

View.AddSubview(submitButton);

实现此代码后,登录屏幕现在如下所示:

The login screen

与以前版本的 iOS 不同,默认按钮背景是透明的。 更改按钮的 BackgroundColor 属性将更改以下内容:

submitButton.BackgroundColor = UIColor.White;

这将生成一个正方形按钮,而不是典型的圆角按钮。 若要获得圆角边缘,请使用以下代码段:

submitButton.Layer.CornerRadius = 5f;

进行这些更改后,视图将如下所示:

An example run of the view

将多个视图添加到视图层次结构

iOS 提供了一种机制,通过使用 AddSubviews 将多个视图添加到视图层次结构。

View.AddSubviews(new UIView[] { usernameField, passwordField, submitButton });

添加按钮功能

当按钮被单击时,用户将期望发生某个事情。 例如,显示警报或导航到另一个屏幕。

让我们添加一些代码,将第二个视图控制器推送到导航堆栈。

首先,创建第二个视图控制器:

var loginVC = new UIViewController () { Title = "Login Success!"};
loginVC.View.BackgroundColor = UIColor.Purple;

然后,向 TouchUpInside 事件中添加以下功能:

submitButton.TouchUpInside += (sender, e) => {
                this.NavigationController.PushViewController (loginVC, true);
            };

导航如下所示:

The navigation is illustrated in this chart

请注意,默认情况下,当你使用导航控制器时,iOS 会为应用程序提供一个导航栏和一个后退按钮,以允许你在堆栈中向后移动。

遍历视图层次结构

可以遍历子视图层次结构并选取任何特定视图。 例如,若要查找每个 UIButton 并为该按钮提供一个不同的 BackgroundColor,可以使用以下代码片段

foreach(var subview in View.Subviews)
{
    if (subview is UIButton)
    {
         var btn = subview as UIButton;
         btn.BackgroundColor = UIColor.Green;
    }
}

但是,如果要遍历的视图是一个 UIView,则此方式不起作用,因为添加到父视图的对象本身继承 UIView,所以所有视图都将作为 UIView 返回。

处理旋转

如果用户将设备旋转到横向,控件不会适当调整大小,如以下屏幕截图所示:

If the user rotates the device to landscape, the controls do not resize appropriately

解决此问题的一种方法是在每个视图上设置 AutoresizingMask 属性。 在这种情况下,我们希望控件水平拉伸,因此我们将设置每个 AutoresizingMask。 以下示例适用于 usernameField,但需要将其应用于视图层次结构中的每个小工具。

usernameField.AutoresizingMask = UIViewAutoresizing.FlexibleWidth;

现在,当我们旋转设备或模拟器时,一切都会拉伸以填充更多空间,如下所示:

All the controls stretch to fill the additional space

创建自定义视图

除了使用属于 UIKit 的一部分的控件外,还可以使用自定义视图。 可以通过继承 UIView 和替代 Draw 来创建自定义视图。 让我们创建一个自定义视图并将其添加到视图层次结构以用于演示。

继承 UIView

我们需要做的第一件事是为自定义视图创建类。 我们将如下所述完成此工作:使用 Visual Studio 中的“类”模板添加一个名为 CircleView 的空类。 基类应该设置为 UIView,我们记得它在 UIKit 命名空间中。 我们还需要 System.Drawing 命名空间。 此示例中不会使用其他各种 System.* 命名空间,因此可以随意移除它们。

此类应如下所示:

using System;

namespace CodeOnlyDemo
{
    class CircleView : UIView
    {
    }
}

在 UIView 中绘制

每个 UIView 都有一个 Draw 方法,在需要绘制它时由系统调用。 永远不要直接调用 Draw。 它在运行循环处理过程中由系统调用。 在将视图添加到视图层次结构后第一次通过运行循环时,将调用其 Draw 方法。 当视图被标记为需要通过在视图上调用 SetNeedsDisplaySetNeedsDisplayInRect 来绘制时,将发生对 Draw 的后续调用。

我们可以通过在替代的 Draw 方法中添加这样的代码来向视图中添加绘制代码:

public override void Draw(CGRect rect)
{
    base.Draw(rect);

    //get graphics context
    using (var g = UIGraphics.GetCurrentContext())
    {
        // set up drawing attributes
        g.SetLineWidth(10.0f);
        UIColor.Green.SetFill();
        UIColor.Blue.SetStroke();

        // create geometry
        var path = new CGPath();
        path.AddArc(Bounds.GetMidX(), Bounds.GetMidY(), 50f, 0, 2.0f * (float)Math.PI, true);

        // add geometry to graphics context and draw
        g.AddPath(path);
        g.DrawPath(CGPathDrawingMode.FillStroke);
    }
}

由于 CircleView 是一个 UIView,因此我们也可以设置 UIView 属性。 例如,我们可以在构造函数中设置 BackgroundColor

public CircleView()
{
    BackgroundColor = UIColor.White;
}

若要使用我们刚才创建的 CircleView,我们可以将其作为子视图添加到现有控制器中的视图层次结构(就像之前对 UILabelsUIButton 所做的那样),也可以将其加载为新控制器的视图。 让我们采用后一种方式。

加载视图

UIViewController 有一个名为 LoadView 的方法,控制器调用该方法来创建其视图。 这是用于创建视图并将其分配给控制器的 View 属性的合适方法。

首先,我们需要一个控制器,因此请创建一个名为 CircleController 的新空类。

CircleController 中,添加以下代码来将 View 设置为 CircleView(不应调用你重写的 base 实现):

using UIKit;

namespace CodeOnlyDemo
{
    class CircleController : UIViewController
    {
        CircleView view;

        public override void LoadView()
        {
            view = new CircleView();
            View = view;
        }
    }
}

最后,我们需要在运行时呈现控制器。 为此,请在我们之前添加的提交按钮上添加事件处理程序,如下所示:

submitButton.TouchUpInside += delegate
{
    Console.WriteLine("Submit button clicked");

    //circleController is declared as class variable
    circleController = new CircleController();
    PresentViewController(circleController, true, null);
};

现在,当我们运行应用程序并点击“提交”按钮时,将会显示一个带圆圈的新视图:

The new view with a circle is displayed

创建启动屏幕

当你的应用启动时会显示一个启动屏幕,向用户表明它有响应。 因为启动屏幕是在你的应用正在加载时显示的,所以无法用代码创建它,因为应用程序仍在加载到内存中。

在 Visual Studio 中创建 iOS 项目时,会以 .xib 文件的形式提供启动屏幕,可在项目内的 Resources 文件夹中找到该文件。

可以通过双击该文件并在 Xcode Interface Builder 中打开它来编辑它。

Apple 建议将 .xib 或 Storyboard 文件用于面向 iOS 8 或更高版本的应用程序,当你在 Xcode Interface Builder 中启动这两种文件中的任何一种文件时,可以使用大小类和自动布局来调整布局,使其看起来良好,并且针对所有设备大小正确显示。 除了 .xib 或 Storyboard 之外,还可以使用静态启动图像来支持面向早期版本的应用程序。

有关创建启动屏幕的详细信息,请参阅以下文档:

重要

从 iOS 9 开始,Apple 建议将 Storyboard 用作创建启动屏幕的主要方法。

为 iOS 8 之前的应用程序创建启动图像

如果应用程序面向早于 iOS 8 的版本,则除了 .xib 或 Storyboard 启动屏幕之外,还可以使用静态图像。

可以在 Info.plist 文件中设置此静态图像,也可以将其设置为应用程序中的资产目录(对于 iOS 7)。 你需要为可能用来运行你的应用程序的每个设备大小(320x480、640x960、640x1136)提供单独的图像。 有关启动屏幕大小的详细信息,请查看启动屏幕图像指南。

重要

如果你的应用没有启动屏幕,你可能会注意到它不完全适合屏幕。 如果是这种情况,你应确保在 Info.plist 中包括至少一个大小为 640x1136 且名为 Default-568@2x.png 的图像。

总结

本文讨论了如何在 Visual Studio 中以编程方式开发 iOS 应用程序。 我们介绍了如何基于空项目模板搭建项目,讨论了如何创建根视图控制器并将其添加到窗口中。 然后,我们展示了如何使用 UIKit 中的控件在控制器中创建视图层次结构,以开发应用程序屏幕。 接下来,我们研究了如何使视图在不同的方向适当地进行布局,并探索了如何通过子类化 UIView 来创建自定义视图,以及如何在控制器中加载视图。 最后,我们探索了如何将启动屏幕添加到应用程序。