MonoTouch.Dialog简称MT.D,是Xamarin.iOS的一个RAD工具包。它提供易于使用的声明式API,不需要使用导航控制器、表格等ViewController来定义复杂的应用程序UI,使得快速开发应用程序UI成为可能。

MT.D的作者是Xamarin的CTO:Miguel de Icaza,MT.D基于表格来创建UI,它提供的API使得创建基于表格的UI变得更加简单。

 

API介绍

MonoTouch.Dialog提供了两种API来定义用户界面:

  • Low-level Elements API: 低级别的元素API,通过层次化的树型结构(类似于DOM)来表示UI及其部件。它提供了最大的灵活性用于创建和控制UI。此外,元素API支持通过定义JSON方式来动态生成UI。
  • High-Level Reflection API:高级反射API,也称为绑定API(Binding API),通过Attribute在实体类上标记元素类型等信息,然后基于对象提供的信息自动创建UI,并且提供对象与UI元素之间的自动绑定。注意:此API不提供细粒度的控制。

MT.D内置了一套UI元素,开发人员也可以通过扩展现有UI元素或者创建新的元素来支持自定义布局。

此外,MT.D内置了一些增强用户体验的特性,比如”pull-to-refresh”下拉刷新、异步加载图片、和搜索的支持。

在使用MT.D之前,有必要对它的组成部分进行了解:

  • DialogViewController:简称DVC,继承自UITableViewController,所有MT.D的元素都需要通过它来显示到屏幕上。当然,也可以像普通的UITableViewController一样来使用它。
  • RootElement:是DVC的顶层容器,它包含多个Section,然后每个Section包含UI元素。RootElement不会被呈现在界面上,而是它们的子元素Section及Element被呈现。
  • Section:Section在表格中作为一个单元格的分组呈现(与UITableView的Section一样),它有Header和Footer属性,可以设置成文字或者是自定义视图。
  • Element:Element即元素作为最基本的控件,表示TableView的实际单元格,MT.D内置了各种不同类型的元素。

 

DialogViewController(DVC)

元素API和反射API都是使用DialogViewController来呈现,DVC继承自UITableViewController,所有UITableViewController的属性与方法都可以在DVC上使用。

DVC提供了多个构造函数来对它进行初始化,这里只看参数最多的一个构造函数:

DialogViewController(UITableViewStyle style, RootElement root, bool pushing)

style即列表样式,默认都是UITableViewStyle.Grouped分组显示,可以设置为UITableViewStyle.Plain不分组。

root即根元素,它下面的所有Section/Element都会被DVC呈现出来。

pushing参数用于是否显示返回按钮,一般用在有UINavigationController的时候。

例如,创建一个不分组显示的DVC:

var dvc = new DialogViewController(UITableViewStyle.Plain, root)

在实际应用开发中,一般很少会直接创建DVC的实例,而是通过继承的方法对每一个视图进行定制:

class LwmeViewController: DialogViewController {
public LwmeViewController(): base(UITableViewStyle.Plain, null) {
this.Root = new RootElement("囧月") {
//...创建Section及Element
};
}
}

然后通过重写DVC的一些方法来定制自己的视图。

在完全使用MT.D开发的app中,可以把DVC做为根视图控制器:

[Register("AppDelegate")]
public partial class AppDelegate : UIApplicationDelegate
{
UIWindow window;
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
window = new UIWindow(UIScreen.MainScreen.Bounds);
window.RootViewController = new LwmeViewController();
window.MakeKeyAndVisible();
return true;
}
// ...
}

假如app需要使用UINavigationController,可以把DVC作为UINavigationController的根视图控制器:

nav = new UINavigationController(new DialogViewController(root));
window.RootViewController = nav;

 

RootElements

DialogViewController需要一个RootElement作为根节点,它的子节点只能是Section,各种Element必须作为Section的子节点来呈现。

// 在使用NavigationController的时候,RootElement的Caption会被呈现为NavigationItem的内容
var root = new RootElement ("囧月 - 博客园") {
new Section("随笔") { // 分组的文字
new StringElement("MonoTouch.Dialog") // 元素
}
new Section("评论") {
new EntryElement("内容")
}
}

RootElement还可以作为Section的子元素,当这个RootElement被点击的时候,实际上会打开一个新的视图,如下(官方DEMO):

var root = new RootElement ("Meals") {
new Section ("Dinner"){
new RootElement ("Dessert", new RadioGroup ("dessert", 2)) {
new Section () {
new RadioElement ("Ice Cream", "dessert"),
new RadioElement ("Milkshake", "dessert"),
new RadioElement ("Chocolate Cake", "dessert")
}
}
}
}

此外,还可以通过LINQ(语句或表达式)和C# 3.0新增的对象和集合初始化语法来创建元素的层次结构:

var root = new RootElement("囧月-lwme.cnblogs.com") {
new string[] {"随笔", "评论", "RSS"}.Select(
x => new Section(x) {
"内容1,内容2,内容3,内容4".Split(',').Select(
s => new StringElement(s, delegate {
Console.WriteLine("内容被点击");
})
)
}
)
}

通过这种做法,可以很容易的结合XML或数据库,完全从数据创建复杂的应用程序。

 

Sections

Section用来对Element元素进行分组显示,它可以包含任何标准内容(Element/UIView/RootElement),但RootElement只能包含它。

可以把Section的Header/Footer设置为字符串或者UIView:

var section = new Section("Header", "Footer") // 使用字符串
var section = new Section(new UIImageView(Image.FromBundle("header.png"))); // 使用UIView

 

内置元素介绍

MT.D内置了这些元素:

  • StringElement:呈现为普通的文本,左边为Caption右边为Value
  • StyledStringElement:继承自StringElement,使用内置的单元格样式或自定义格式,提供了字体、颜色、背景、换行方式、显示的行数等属性可供设置
  • MultilineElement:呈现为多行的文本
  • StyledMultilineElement:继承自MultilineElement,多了一些可以设置的属性(类似StyledStringElement)
  • EntryElement:文本框,用于输入普通字符串或者密码(isPassword参数),除了Caption/Value外,还有Placeholder属性用于设置文本框提示文本。除此之外,还可以设置KeyboardType属性,用来限制数据输入:
    • Numeric 数字
    • Phone 电话
    • Url 网址
    • Email 邮件地址
  • BooleanElement:呈现为UISwitch
  • CheckboxElement:呈现为复选框
  • RadioElement:呈现为单选框,需要放置在有RadioGroup的RootElement的Section中,使用起来显得有点麻烦
  • BadgeElement:呈现为垂直居中的文本左边一个图标 (57x57)
  • ImageElement:用于选取图片
  • ImageStringElement:继承自StringElement,类似于BadgeElement
  • FloatElement:呈现为UISlider
  • ActivityElement:呈现为loading加载动画
  • DateElement:日期选择
  • TimeElement:时间选择
  • DateTimeElement:日期时间选择
  • HtmlElement:呈现为一个普通的文本,通过Url属性设置网址,点击之后自动打开一个UIWebView加载网站
  • MessageElement:呈现为类似收件箱邮件的样式,有许多属性可以设置(Body/Caption/Date/Message/NewFlag/Sender/Subject)
  • LoadMoreElement:呈现为一个用于加载更多的普通文本,点击后显示加载动画,在相应的事件里进行一些逻辑处理
  • UIViewElement:所有类型的UIView都可以通过UIViewElement来呈现到表格上
  • OwnerDrawnElement:这是一个抽象类,可以通过继承它来创建自定义的视图
  • JsonElement:继承自RootElement,用于加载JSON内容来自动创建视图(从本地/网络上的json文件/字符串)

官方也给出了一个元素的结构树:

    Element
BadgeElement
BoolElement
BooleanElement - uses an on/off slider
BooleanImageElement - uses images for true/false
EntryElement
FloatElement
HtmlElement
ImageElement
MessageElement
MultilineElement
RootElement (container for Sections)
Section (only valid container for Elements)
StringElement
CheckboxElement
DateTimeElement
DateElement
TimeElement
ImageStringElement
RadioElement
StyleStringElement
UIViewElement

 

处理动作

Element提供了NSAction类型的委托作为回调函数来处理动作(大部分Element都有一个NSAction类型的Tapped事件),比如处理一个触摸事件:

new Section () {
new StringElement ("点我 - 囧月",
delegate { Console.WriteLine ("元素被点击"); })
}

 

检索元素的值

继承自Element的元素默认有Caption属性,用来在单元格左边显示标题;大部分Element都有一个Value属性,用来显示在单元格右边。

在回调函数中通过Element的属性来获取对应的值:

var element = new EntryElement ("评论", "输入评论内容", null);
var taskElement = new RootElement ("囧月-博客-评论"){
new Section () { element },
new Section ("获取评论内容") {
new StringElement ("获取",
delegate { Console.WriteLine (element.Value); })
}
};

 

设置元素的值

如果元素的属性是可操作的,如EntryElement.Value,可以直接通过属性设置它的值。

不可操作的如EntryElement.Caption,或者StringElement.Value/StringElement.Caption属性,直接设置元素的值不会反映在界面上,需要通过RootElement.Reload方法来重新加载才可以更新内容:

var ee = new EntryElement ("评论", "输入评论内容", null);
var se = new StringElement("时间", DateTime.Now.ToString());
var root = new RootElement ("囧月-博客-评论"){
new Section () { ee, se },
new Section ("获取评论内容") {
new StringElement ("获取",
delegate {
Console.WriteLine (element.Value);
// 直接设置元素内容
ee.Value = DateTime.Now.ToString();
// 不可直接设置的属性
se.Caption = "新标题";
se.Value = DateTime.Now.ToString();
root.Reload(se, UITableViewRowAnimation.None);
})
}
};

 

反射API

反射API通过使得创建UI界面变得非常简单:

  • 创建一个类,并使用MT.D的Attribute来标记它的字段/属性
  • 创建BindingContext的实例,并把上一步类型的实例作为参数
  • 创建DialogViewController,并把它的Root设置为BindingContext的Root

先来一个简单的例子:

class Blogger {
[Section("登录博客"),
Entry("输入用户名"), Caption("用户名")]
public string Username; [Password("输入密码"), Caption("密码")]
public string Password; [Checkbox, Caption("下次自动登录")]
public bool Remember; [Section("开始登录", "请确认你输入的信息"),
Caption("登录"),
OnTap("Login")]
public string DoLogin;
} public class LwmeViewController: DialogViewController {
BindingContext context;
Blogger blog;
public LwmeViewController(): base(UITableViewStyle.Grouped, null) {
blog = new Blogger { Username = "囧月" };
context = new BindingContext(this, blog, null);
this.Root = context.Root;
} public void Login() {
context.Fetch(); // 通过Fetch方法把文本框输入的信息反馈到blog实例上
if (string.IsNullOrWhiteSpace(blog.Username) ||
string.IsNullOrWhiteSpace(blog.Password)) {
var tip = new UIAlertViewController( "出错提示", "用户名和密码必须填写", null, "确定", null);
tip.Show();
}
// 进行登录操作...
}
}

为了避免阻塞UI线程(用户界面假死),一般都会使用异步操作,比如上面的登录可能使用WebClient的UploadStringAsync异步方法,然后在相应事件中进行操作;这里需要注意,使用了异步方法之后,在相应的事件中可能就不是UI线程,将不能直接对UI相关元素进行操作,类似于Winform/Wpf,MonoTouch提供了两个方法用于在非UI线程操作UI元素:InvokeOnMainThread/BeginInvokeOnMainThread

现在,来看一下MT.D为反射API提供了多少Attribute:

  • EntryAttribute:文本框
  • PasswordAttribute:密码输入框,继承自EntryAttribute
  • CheckboxAttribute:复选框
  • DateAttribute:日期
  • TimeAttribute:时间
  • DateTimeAttribute:日期时间
  • HtmlAttribute:普通的文本,点击后打开一个UIWebView
  • MultilineAttribute:多行文本
  • RadioSelectionAttribute:呈现为RadioElement,字段/属性需要是int类型,数据源需要实现IEnumerable接口
  • CaptionAttribute:用于设置元素的Caption,如果不设置的话,将使用元素的属性/字段名
  • AlignmentAttribute:用于设置元素内容的对齐方式
  • OnTapAttribute:用于设置点击事件,参数为一个字符串对应执行的方法名
  • RangeAttribute:用于设置UISlider的值范围
  • SkipAttribute:使用此Attribute的属性/字段将不被用于作为UI元素呈现

除了以上列出的,还有3个元素没有对应的Attribute:

  • StringElement:即普通文本,没有对应的Attribute,string类型的字段/属性默认会被呈现为StringElement
  • BooleanElement:即UISwitch,bool类型的字段/属性会被呈现为BooleanElement
  • FloatElement:即UISlider,float类型的字段/属性会被呈现为FloatElement

再来一个例子:

class Blogger {
public string Username = "囧月"; // 呈现为StringElement
public bool Remember; // 呈现为BooleanElement
public float Value; // 呈现为FloatElement
[Multiline]
public string Description;
[Range(0, 100)]
public float Value2; // 可以使用Range来标明范围
[Skip]
public string ignoreField; // 不被呈现
}

另外,对于RadioElement类型的元素,除了可以使用RadioSelectionAttribute外,MT.D还提供了一个方法支持直接从Enum类型:

public enum Category {
Blog,
Post,
Comment
}
class Blogger {
public Category ContentCategory;
} class Blogger2 {
[RadioSelection("CategorySource")] // 设置数据源
public int ContentCategory; // 字段/属性必须是int类型
// 数据源只要实现IEnumerable接口,不限制类型
public List<string> CategorySource = new List<string>{ "Blog", "Post", "Comment" };
}

注意字段/属性的类型必须与相应的Element的值类型对应,否则不会被呈现,比如:

  • EntryElement只能使用string类型,用int就不会被呈现
  • FloatElement只能使用float类型,double/decimal类型都无效
  • BooleanElement只能使用bool类型
  • RadioElement类型只能使用enum类型或者int类型并设置数据源
  • DateElement/TimeElement/DateTimeElement只能使用日期相关类型

反射API大大简化了UI界面的开发,但是它不能很好支持细粒度控制,如果对UI定制要求比较高,建议还是直接使用元素API。

当然,如果只是偶尔需要直接访问某个Element,可以通过DVC的Root属性来找到对应的Element,但是操作起来比较繁琐:

var section1 = this.Root[0];
var element1 = section1[0] as StringElement;

 

JSON元素

MT.D支持从本地/远程的json文件、或者已解析的JsonObject对象实例来创建JSON元素。

假如有这么一个简单的json文件:

{
"title": "囧月",
"sections": [
{
"elements" : [
{
"id" : "lwme-username",
"type": "entry",
"caption": "用户名",
"placeholder": "输入用户名"
},
{
"id" : "lwme-date",
"type": "date",
"caption": "日期",
"value": "00:00"
}
]
}
]
}

通过内置的方法来加载它:

var root = JsonElement.FromFile("lwme.json"); // 加载本地json
var root = new JsonElement("load from json", "lwme.cnblogs.com/lwme.json"); // 加载远程json
var dvc = new DialogViewController(root); // 可以直接把JsonElement作为根元素

另外,还可以通过json文件里设置的id来获得对应的Element:

var username = taskElement ["lwme-username"] as EntryElement;
var date = taskElement ["lwme-date"] as DateElement;

通过json元素这种方式,可以创建非常灵活的界面,同时也能大大减小客户端的大小。

json文件里各种标记的属性对应元素的各种属性,完整的JSON格式见官方文档:http://docs.xamarin.com/guides/ios/user_interface/monotouch.dialog/monotouch.dialog_json_markup/

 

其他特性

 

Pull-to-Refresh(下拉刷新)支持

DialogViewController提供了一个RefreshRequested事件,只需要实现它就可以为表格提供下拉刷新支持:

var dvc = new DialogViewController(root);
dvc.RefreshRequested += (s, e) {
// 处理数据... lwme.cnblogs.com
dvc.ReloadComplete(); // 处理完成之后调用这个方法完成加载
};

另外,也有TriggerRefresh()方法来直接调用下拉刷新;还可以通过重写MakeRefreshTableHeaderView(RectangleF)方法来自定义刷新头部的内容。

 

搜索支持

DialogViewController提供了一些属性及方法用于搜索的支持:

  • EnableSearch:启用搜索支持
  • SearchPlaceholder:搜索框提示文本
  • StartSearch():开始搜索
  • FinishSearch():完成搜索
  • PerformFilter():执行过滤
  • SearchButtonClicked():按下搜索按钮
  • OnSearchTextChanged():搜索文本框内容改变
  • SearchTextChanged:事件,同上

一般情况下只需要通过EnableSearch属性来启用搜索即可,更多的定制可以通过以上的方法/事件来实现。

 

后台加载图片

MT.D提供了一个ImageLoader用于在后台加载图片:

new BadgeElement( ImageLoader.DefaultRequestImage( new Uri("http://lwme.cnblogs.com/xx.png"), this), "囧月")
// 等同于ImageLoader.DefaultLoader.RequestImage方法

下载的图片会被缓存在内存中(默认缓存50张图片),ImageLoader.Purge()方法可用于清理缓存。更多的自定义操作可以通过创建ImageLoader实例来实现。

 

创建自定义元素

可以通过继承Element或者更具体的类型来创建自定义的元素。创建自定义元素将需要重写以下方法:

// 为元素创建UITableViewCell,设置内容及样式并呈现在表格上
UITableViewCell GetCell (UITableView tv)
// (可选)设置元素的高度,重写这个方法需要实现IElementSizing接口
float GetHeight (UITableView tableView, NSIndexPath indexPath);
// (可选)释放资源
void Dispose (bool disposing);
// (可选)为元素呈现摘要内容,比如StringElement就呈现为Caption
string Summary ()
// (可选)元素被点击/触摸时,很多元素的Tapped事件就是在这个方法里实现
void Selected (DialogViewController dvc, UITableView tableView, NSIndexPath path)
// (可选)如果需要支持搜索,需要在方法中检测用户输入是否匹配
bool Matches (string text)

如果重写了GetCell方法,并且在方法内部调用了base.GetCell(tv)方法来返回cell,那么还需要重写CellKey属性来返回一个唯一的key用于自定义元素:

static NSString MyKey = new NSString ("lwmeCustomElementKey");
protected override NSString CellKey {
get {
return MyKey;
}
}

 

关于数据验证

MT.D没有为Element提供任何验证的方法,如果需要对用户输入进行验证,自己实现验证逻辑,比如元素的Tapped事件中进行数据验证:

var ee = new EntryElement ("评论", "输入评论内容", null);
var root = new RootElement ("囧月-博客-评论"){
new Section () { ee },
new Section ("获取评论内容") {
new StringElement ("获取",
delegate {
if (string.IsNullOrEmpty(ee.Value)) {
var tip = new UIAlertViewController(
"出错提示", "内容必须填写", null, "确定", null);
tip.Show();
}
})
}
};

 

参考

官方文档(本文内容主要来源):http://docs.xamarin.com/guides/ios/user_interface/monotouch.dialog/

Miguel de Icaza的文章(Simplified User Interfaces on the iPhone with MonoTouch.Dialog):http://tirania.org/blog/archive/2010/Feb-23.html

获取最新源码:https://github.com/migueldeicaza/MonoTouch.Dialog (Sample目录下也提供了不少例子)

完整的官方例子:https://github.com/migueldeicaza/TweetStation

使用MonoTouch.Dialog简化iOS界面开发的更多相关文章

  1. 20个可以帮你简化iOS app开发流程的工具

    这里推荐20个可以帮你简化iOS app开发流程的工具.很多开发者都使用过这些工具,涉及原型和设计.编程.测试以及最后的营销,基本上涵盖了整个开发过程. 原型和设计 有了一个很好的创意后,你要做的不是 ...

  2. iOS界面开发

    [转载] iOS界面开发 发布于:2014-07-29 11:49阅读数:13399 iOS 8 和 OS X 10.10 中一个被强调了多次的主题就是大一统,Apple 希望通过 Hand-off ...

  3. iOS 界面开发

    iOS 自动布局 iOS 界面 之 EALayout 无需反复编译,可视化实时界面,告别Storyboard AutoLayout Xib等等烦人的工具 iOS应用国际化教程(2014版) iOS开发 ...

  4. WWDC 2014 Session笔记 - iOS界面开发的大一统

    本文是我的 WWDC 2014 笔记 中的一篇,涉及的 Session 有 What's New in Cocoa Touch Building Adaptive Apps with UIKit Wh ...

  5. 学习方法和阶段介绍 、 iOS界面开发引入 、 构造第一个App 、 视图控制器和视图 、 控件与事件 、 InterfaceBuilder

    1 创建并运行第一个App 1.1 问题 使用Xcode创建一个App项目,该应用实现功能在界面上显示Hello World标签,在模拟器中的运行结果如图-1所示: 图-1 1.2 方案 分析图-1, ...

  6. 【iOS开发】IOS界面开发使用viewWithTag:(int)findTag方法获取界面元素

    http://blog.csdn.net/lxp1021/article/details/43952551 今天在开发OS界面的时候,遇到通过界面UIview viewWithTag:(int)fin ...

  7. 简化MonoTouch.Dialog的使用

    读了一位园友写的使用MonoTouch.Dialog简化iOS界面开发,我来做个补充: 相信使用过DialogViewController(以下简称DVC)的同学都知道它的强大,但是缺点也是明显的,应 ...

  8. iOS常用开发资源整理

    在行--专家付费咨询 杂项 App Release Checklist—iOS App发布清单. Hey Focus—帮助你专注于一个任务. Objective Cloud—Objective C A ...

  9. iOS移动开发周报-第19期

    iOS移动开发周报-第19期 前言 欢迎国内的iOS同行或技术作者向我提交周报线索,线索可以是新闻.教程.开发工具或开源项目,将相关文章的简介和链接在微博上发布并 @唐巧_boy 即可. [摘要]:本 ...

随机推荐

  1. React 入门教程

    React 起源于Facebook内部项目,是一个用来构建用户界面的 javascript 库,相当于MVC架构中的V层框架,与市面上其他框架不同的是,React 把每一个组件当成了一个状态机,组件内 ...

  2. ASP.NET Core 1.1.0 Release Notes

    ASP.NET Core 1.1.0 Release Notes We are pleased to announce the release of ASP.NET Core 1.1.0! Antif ...

  3. CSS HTML元素布局及Display属性

    本篇文章主要介绍HTML的内联元素.块级元素的分类与布局,以及dispaly属性对布局的影响. 目录 1. HTML 元素分类:介绍内联元素.块级元素的分类. 2. HTML 元素布局:介绍内联元素. ...

  4. android 使用Tabhost 发生could not create tab content because could not find view with id 错误

    使用Tabhost的时候经常报:could not create tab content because could not find view with id 错误. 总结一下发生错误的原因,一般的 ...

  5. C++11特性——变量部分(using类型别名、constexpr常量表达式、auto类型推断、nullptr空指针等)

    #include <iostream> using namespace std; int main() { using cullptr = const unsigned long long ...

  6. CSS margin详解

    以下的分享是本人最近几天学习了margin知识后,大有启发,感觉以前对margin的了解简直太浅薄.所以写成以下文章,一是供自己整理思路:二是把知识分享出来,避免各位对margin属性的误解.内容可能 ...

  7. ExtJS 项目准备工作(一)

    首先,需要从网上下载两个文件,一个是SenchaCmd-6.2.0-windows-64bit(我的电脑是window 10 64位) 另一个是ExtJs6的源码包(ext-6.0.0.415). 源 ...

  8. Oracle 列数据聚合方法汇总

    网上流传众多列数据聚合方法,现将各方法整理汇总,以做备忘. wm_concat 该方法来自wmsys下的wm_concat函数,属于Oracle内部函数,返回值类型varchar2,最大字符数4000 ...

  9. linux中kvm的安装及快照管理

    一.kvm的安装及状态查看 1.安装软件 yum -y install kvm virt-manager libvirt2.启动libvirtd 报错,升级device-mapper-libs yum ...

  10. mono for android 自定义titleBar Actionbar 顶部导航栏 修改 样式 学习

    以前的我是没有做笔记的习惯的,学习了后觉得自己能记住,但是最近发现很多学的东西都忘记了,所有现在一有新的知识,就记下来吧. 最近又做一个mono for android 的项目 这次调整比较大,上次做 ...