# 定制界面组件

App开发中会使用大量UI组件，Hippy SDK已经包含了一些基本UI，比如View, Text, Image等。
而用户若想进行自定义组件也十分简单。

# 组件扩展

我们以创建MyView为例，从头介绍如何扩展一个组件。
>本文仅介绍ios端工作，前端工作请查看对应的文档。

扩展一个UI组件需要包括以下工作：

1. 创建对应的ViewManager
2. 注册类并绑定前端组件
3. 绑定View属性及方法
4. 创建对应的shadowView和View

## 创建对应的ViewManager

>ViewManager是对应的视图管理组件，负责前端视图和终端视图直接进行属性、方法的调用。
>SDK中最基础的ViewManager是HippyViewManager，封装了基本的方法，负责管理HippyView。
>用户自定的ViewManager必须继承自HippyViewManager

HippyMyViewManager.h

```objectivec
@interface HippyMyViewManager:HippyViewManager
@end
```

HippyMyViewManager.m

```objectivec
@implementation HippyMyViewManager
HIPPY_EXPORT_MODULE(MyView)
HIPPY_EXPORT_VIEW_PROPERTY(backgroundColor, UIColor)
HIPPY_REMAP_VIEW_PROPERTY(opacity, alpha, CGFloat)
HIPPY_CUSTOM_VIEW_PROPERTY(overflow, CSSOverflow, HippyView)
{
    if (json) {
        view.clipsToBounds = [HippyConvert CSSOverflow:json] != CSSOverflowVisible;
    } else {
        view.clipsToBounds = defaultView.clipsToBounds;
    }
}
- (HippyView *)view {
    return [HippyMyView new];
}
- (HippyShadowView *)shadowView {
    return [HippyShadowView new];
}
HIPPY_EXPORT_METHOD(focus:(nonnull NSNumber *)reactTag) {
    // do sth
}
HIPPY_EXPORT_METHOD(focus:(nonnull NSNumber *)reactTag callback:(HippyResponseSenderBlock)callback) {
    // do sth
    NSArray *result = xxx;
    callback(result);
}
HIPPY_EXPORT_METHOD(focus:(nonnull NSNumber *)reactTag resolver:(HIPPYPromiseResolveBlock)resolve rejecter:(HIPPYPromiseRejectBlock)reject) {
    // do sth
    if (success) {
        resolve(result);
    }
    else {
        reject(@"errorcode", @"errormessage", error);
    }
}
@end
```

## 类型导出

`HIPPY_EXPORT_MODULE()`将HippyMyViewManager类注册，前端在对MyView进行操作时会通过HippyMyViewManager进行实例对象指派。
`HIPPY_EXPORT_MODULE()`中的参数可选。代表的是ViewManager对应的View名称。
若用户不填写，则默认使用类名称。
但是SDK中有个特殊处理逻辑，若参数中的字符串以'Manager'结尾，那SDK在根据字符串查找对应的View时会将删除结尾'Manager'后的字符串作为View名称

## 参数导出

`HIPPY_EXPORT_VIEW_PROPERTY`将终端View的参数和前端参数绑定。当前端设定参数值时，会自动调用setter方法设置到终端对应的参数。
`HIPPY_REMAP_VIEW_PROPERTY()`负责将前端对应的参数名和终端对应的参数名对应起来。以上述代码为例，前端的opacity参数对应终端的alpha参数。此宏一共包含三个参数，第一个为前端参数名，第二个为对应的终端参数名称，第三个为参数类型。另外，此宏在设置终端参数时使用的是keyPath方法，即终端可以使用keyPath参数。
`HIPPY_CUSTOM_VIEW_PROPERTY()`允许终端自行解析前端参数。SDK将前端传递过来的原始json类型数据传递给函数体（用户可以使用HippyConvert类中的方法解析对应的 数据），用户获取后自行解析。
>这个方法带有两个隐藏参数-view, defaultView。view是指当前前端要求渲染的view。default指当前端渲染参数为nil时创建的一个临时view，使用其默认参数赋值。

## 方法导出

`HIPPY_EXPORT_METHOD`能够使前端随时调用终端对应的方法。前端通过三种模式调用，非别是callNative, callNativeWithCallbackId, callNativeWithPromise。终端对应三种方式时，函数体写法可以参照上面的示例。

* callNative 此方法不需要终端返回任何值。

* callNativeWithCallbackId 此方法需要终端在函数体中以单个block形式返回数据。block类型为`HippyResponseSenderBlock`，参数为一个NSArray变量。

* callNativeWithPromise 此方法对应的前端使用的是promise写法，对应到终端需要业务根据自身情况返回resolve block或者reject block。其中resolve block数据类型为`HippyPromiseResolveBlock`, 参数为一个可以被json化的对象。如果参数为nil，则JS端会将其转化为undefined。reject block数据类型为`HippyPromiseRejectBlock`，参数包括一个错误码，错误信息，以及错误实例对象(NSError)。

一个ViewManager可以管理一种类型的多个实例，为了在ViewManager中区分当前操作的是哪个View，每一个导出方法对应的第一个参数都是View对应的tag值，用户可根据这个tag值找到对应操作的view。

> 由于导出方法并不会在主线程中调用，因此如果用户需要进行UI操作，则必须将其分配至主线程。推荐在导出方法中使用[self.bridge, uiManager addUIBlock:]方法。其中的block类型为`HippyViewManagerUIBlock`。

> `typedef void (^HippyViewManagerUIBlock)(HippyUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry)`。第二个参数为字典，其中的key就是对应的view tag值，value就是对应的view。

## 创建shadowView和View

在OC层，HippyUIManager负责将JS层的解析结果，映射到OC层的视图层级，HippyShadowView它不是真正展现的视图，只是一个映射结果而已，每一个HippyShadowView对应一个真正的视图，但它已经完成了基本的布局。
>HippyView会根据HippyShadowView的映射结果构建真正的View视图。因此对于大多数情况下的自定义view manager来说，直接创建一个HippyShadowView即可。

HippyUIManager将调用[HippyMyViewManager view]方法去创建一个真正的view，用户需要实现这个方法并返回自己所需要的HippyMyView。

到此，一个简单的HippyMyViewManager与HippyMyView创建完成。
