数据绑定

gic中的数据绑定以两对{}表示,支持两种方式。

  1. 直接以 exp形式表达。这样默认是以once来实现绑定,也就是一次性绑定。比如:

    1
    text="{{ '姓名:' + name }}"

    如果exp为空,那么直接数据源的desc属性作为value,这种直接为空的的绑定方式在数据源是字符串的时候尤其方便。

  2. exp=exp,mode=1,cvt=converter的形式来写。事实上这种方式才是一种比较完整的数据绑定的写法,exp:表示绑定的表达式,支持一小段简单的js代码。mode:表示绑定模式,下面会讲。cvt:转换器,也就是将经过exp中的表达式运算过后得到的字符串转换成其他的数据。

    1
    text="{{ exp=timeStamp,mode=1,cvt=TimestampConverter }}"

exp(表达式)

绑定表达式支持一小段js代码,也支持以单个的数据源属性的名称进行绑定。

mode(绑定模式)

数据绑定有三种模式,分别是:once(一次性)oneway(单向)towway(双向)。默认是once,这个主要考虑了性能问题,另外一个也是考虑了大多数绑定的数据不会发生变更。

枚举值 对应枚举 说明
0 GICBingdingMode_Once 只会绑定一次,数据的改变不会再次更新
1 GICBingdingMode_OneWay 单向绑定,每次数据源改变,都会更新绑定
2 GICBingdingMode_TowWay 双向绑定,在单向绑定的基础上,增加了反向绑定,也就是说当本身某个数据改变的时候也会影响数据源本身的数据 。目前gic本身的类库中支持双向绑定的,只有inputintput-view中的text属性支持双向绑定。你也可以在自己的自定义元素中对某些属性进行双向绑定的支持,只要实现GICLayoutElementProtocol协议中的gic_createTowWayBindingWithAttributeName方法即可。双向绑定的表达式只能是单个的属性,不支持js表达式。

cvt(转换器)

转换器其实类似于angularjs中的piple,也即是将表达式的值转换成其他的数据。转换器必须是继承自GICDataBingdingValueConverter的某个类,实现里面的convert方法即可,返回值就是对应的属性数据类型,具体某个属性对应的数据类型可以参考文档。

这里以实现将时间戳转换成年月日为例:

1
2
3
4
5
6
7
8
9
10
@interface TimestampConverter : GICDataBingdingValueConverter
@end
@implementation TimestampConverter
-(id)convert:(NSString *)stringValue{
NSDate *date = [NSDate dateWithTimeIntervalSince1970:[stringValue doubleValue]];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy MM dd HH:mm:ss"];
return [formatter stringFromDate:date];
}
@end

然后在xml如下写:

1
text="{{ exp=timeStamp,mode=1,cvt=TimestampConverter }}"

数据源(data-context)

绑定需要数据源,有数据源才能将绑定的表达式根据数据源的属于计算出对应的value。在xml中可以有三种方式来设置数据源,数据源可以在任何元素上设置。

  1. 直接以一个字符串作为数据源,这种数据源没有属性的概念,因此只能以空的表达式绑定。比如:

    1
    <lable text="{{}}" font-color="black" data-context="我是直接绑定字符串"/>
  2. 以json字符串作为数据源。比如:

    1
    2
    3
    4
    <stack-panel data-context='{"name":"海伟","age":"20"}'>
    <lable text="{{ '姓名:' + name }}" font-color="black" />
    <lable text="{{ '年龄:'+age+'岁' }}" font-color="black" />
    </stack-panel>
  3. 以某个ViewModel的类作为数据源。gic可以说是一个MVVM框架,UI跟ViewModel是分离的,而ViewModel可以是任何一个NSObject的子类。

    1
    2
    3
    4
    <stack-panel data-context='DataBindingUserInfo'>
    <lable text="{{ '姓名:' + name }}" font-color="black" />
    <lable text="{{ '年龄:'+age+'岁' }}" font-color="black" />
    </stack-panel>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @interface DataBindingUserInfo : NSObject
    @property (nonatomic,strong)NSString *name;
    @property (nonatomic,assign)NSInteger age;
    @end
    @implementation DataBindingUserInfo
    -(id)init{
    self = [super init];
    _name = @"海伟1";
    _age = 22;
    return self;
    }
    @end

上面三种方式虽然在xml中是以字符串的形式设置,但是gic在解析的时候,会按照jsonViewModel字符串的顺序依次解析。

而且你也无需为每个元素都设置数据源,所有的子孙元素都共享离该元素最近的数据源,因此,你如果xml中的根元素上设置了一个数据源,那么所有的元素都共享该数据源。

你也可以直接通过OC代码来设置数据源。代码如下:

1
el.gic_DataContenxt = [DataBindingUserInfo new];

也就是对该元素的gic_DataContenxt属性赋值即可。

这里对于双向绑定的数据源有几个点需要注意下:

  1. 对于需要双向绑定的数据源,必须确保绑定的数据源属性是可写的。在iOS中也就是意味着该属性必须是可以通过setValue:forKey:方法设置的。而NSDictionay就不支持了,NSMutbleDictionay是支持的。

data-path

data-pathdata-context其实是同级的,两个属性是互斥的,你不应该在同一个节点上同时设置这两个属性。data-path也是用来设置当前元素的数据源的,但是跟data-context不一样的地方在于,data-context是直接设置当前元素的数据源,而data-path是将当前元素的数据源绑定到父元素数据源的某个属性上面。举个例子:

有两个类A、B,A有个属性的实例是B,也就是说A包含B。这时候我在XML中希望将某个元素的数据源设置为B,那么这时候只要直接将该元素的data-path设置为B就行了。

而实现这一点的原理其实很简单,gic直接通过valueForKey:的方法获取b的实例,然后再将B设置为该元素的data-context。

当然设计data-path的目的其实也是为了弥补当前数据绑定不支持按照path获取value的问题。也就是说你无法通过path那样的方式实现单向、双向数据绑定,但是once绑定是可以支持path的。

data-context元素(0.2.0新增)

0.2.0版本新增了data-context元素,你现在可以直接将json字符串添加到data-context元素中,data-context会自动将解析出的json数据作为父元素的数据源。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<page title="布局系统">
<list background-color="white" separator-style="1">
<data-context>
[{
"name": "panel(绝对布局)",
"pagePath": "layout/Panel.xml"
}, {
"name": "stack-panel(flex 布局)",
"pagePath": "layout/StackPanel.xml"
}, {
"name": "stack-panel 动态演示",
"pagePath": "layout/StackPanel2.xml"
}, {
"name": "inset-panel(padding 布局)",
"pagePath": "layout/InsetPanel.xml"
}, {
"name": "dock-panel(停靠 布局)",
"pagePath": "layout/DockPanel.xml"
}, {
"name": "background-panel(背景 布局)",
"pagePath": "layout/BackgroundPanel.xml"
}, {
"name": "ratio-panel(比例布局)",
"pagePath": "layout/RatioPanel.xml"
}, {
"name": "panel(绝对布局)",
"pagePath": "layout/Panel.xml"
}]
</data-context>
<for>
<list-item selection-style="2">
<inset-panel background-color="white" inset="15">
<behaviors>
<bev-router-link path="{{pagePath}}"/>
</behaviors>
<lable text="{{ name }}" font-size="15"></lable>
</inset-panel>
</list-item>
</for>
</list>
</page>