设备详情 UI 业务包

更新时间:2023-07-13 07:14:39下载pdf

涂鸦设备详情 UI 业务包提供了以下功能:

  • 设备信息编辑:包括设备头像、设备所在房间、名称等
  • 设备信息查询:包括设备 ID、信号等
  • 备用网络设置
  • 检查设备网络
  • 离线提醒功能
  • 添加到主屏幕
  • 展示遥控器
  • 删除设备

接入组件

Pod 集成

在工程的 Podfile 文件中添加业务包组件,并执行 pod update 命令。

source "https://github.com/tuya/tuya-private-specs"
source 'https://cdn.cocoapods.org/'

target 'your_target_name' do
# 添加设备详情 UI 业务包
pod 'ThingSmartDeviceDetailBizBundle'
# 如果使用蓝牙功能,需要引入蓝牙相关组件
# pod 'ThingBluetoothInterface'
# pod 'ThingBLEMeshInterfaceImpl'
end

权限声明

设备详情 UI 业务包涉及到设备图标上传时,需要使用系统相册和相机权限。因此,会涉及到部分苹果隐私权限的声明。

您需要在工程的 info.plist 中添加如下权限声明:

<!-- 相册 -->
<key>NSPhotoLibraryUsageDescription</key>
<string>App 需要您的同意,才能访问相册</string>
<!-- 相机 -->
<key>NSCameraUsageDescription</key>
<string>App 需要您的同意,才能访问相机</string>

如果接入了蓝牙设备,使用创建群组或者移除设备等依赖蓝牙能力的业务,需要在info.plist 中添加如下权限声明:

<!-- 蓝牙 -->
<key>NSBluetoothAlwaysUsageDescription</key>
<string>这将允许应用程序找到并连接蓝牙配件。这个应用程序也可以使用蓝牙定位蓝牙设备。</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>如果需要添加或使用蓝牙设备,请开启手机蓝牙功能。</string>

配置文件

  1. 在主工程中,新建 deviceDetailConfig.json 文件。如果已经存在该文件,则不用再新建。

  2. deviceDetailConfig.json 中添加以下内容:

    [
        {
            "name": "headerSection",
            "items": [
                {
                    "cellType": "header"
                },{
                    "cellType": "device_info"
                },{
                    "cellType": "net_setting"
                },{
                    "cellType":"group_edit_devices"
                }
            ]
        },{
            "name": "offLineWarnSection",
            "header": {
                "cellType":"section_off_line_warn"
            },
            "items": [
                {
                    "cellType" : "off_line_warn"
                }
            ]
        },{
            "name": "otherSection",
            "header": {
                "cellType":"section_other"
            },
            "items": [
                {
                    "cellType":"bind_multi_control_link"
                },
                {
                    "cellType":"group_create"
                },
                {
                    "cellType":"help_and_feedback"
                },
                {
                    "cellType":"add_icon_to_home_screen"
                },
                {
                    "cellType":"show_infrared_gateway_sub_device"
                },
                {
                    "cellType":"check_device_network"
                },
                {
                    "cellType":"check_firmware_update"
                }
            ]
        },{
            "name": "footerSection",
            "items": [
                {
                    "cellType":"footer"
                }
            ],
            "margin": {
                "top": 16
            }
        }
    ]
    
  3. 自定义设备详情。

    deviceDetail 数组 item 的顺序影响设备详情页 item 展示的顺序。如果移除 item,则相应也会移除设备详情页子功能。

    deviceDetail 类型 设备详情页子功能
    header 查看修改设备图标,名称,位置
    device_info 设备信息
    net_setting 设备备用网络
    off_line_warn 设备离线提醒
    section_other 分区头,无实际功能
    group_edit_devices 编辑群组
    group_create 创建群组
    add_icon_to_home_screen 添加到主屏幕
    show_infrared_gateway_sub_device 展示遥控器
    footer 移除设备

服务协议

依赖服务(需要实现)

ThingSmartHomeDataProtocol 协议已废弃,推荐使用ThingFamilyProtocol 协议。

/**
要使用该 API 获取当前家庭,请务必在更新当前家庭 ID 的时候使用该协议的’updateCurrentHomeId:‘Api。
获取当前的家庭,当前没有家庭的时候,返回 nil。

@return ThingSmartHome
*/
- (ThingSmartHome *)getCurrentHome;

Objective-C 示例

#import <ThingSmartBizCore/ThingSmartBizCore.h>
#import <ThingModuleServices/ThingSmartHomeDataProtocol.h>

- (void)initCurrentHome {
    // 注册要实现的协议
    [[ThingSmartBizCore sharedInstance] registerService:@protocol(ThingSmartHomeDataProtocol) withInstance:self];
}

// 实现对应的协议方法
- (ThingSmartHome *)getCurrentHome {
    ThingSmartHome *home = [ThingSmartHome homeWithHomeId:@"当前家庭 ID"];
    return home;
}

Swift 示例

import ThingSmartDeviceKit

class ThingMessageCenterTest: NSObject,ThingSmartHomeDataProtocol{

    func test() {
        ThingSmartBizCore.sharedInstance().registerService(ThingSmartHomeDataProtocol.self, withInstance: self)
    }

    func getCurrentHome() -> ThingSmartHome! {
        let home = ThingSmartHome.init(homeId: 111)
        return home
    }

}

可选功能实现

  • 蓝牙功能:在 Podfile、Info.plist 添加相关依赖和声明后,在业务使用前,调用蓝牙启动方法。

    #import <ThingBluetoothInterface/ThingBluetoothInterface.h>
    
    [TheBLEInterfaceService startScan];
    

提供服务(方法调用)

方法 参数
跳转到网络检测页 设备 ID
跳转到设备详情页,以 Push 方式 ThingSmartDeviceModelThingSmartGroupModel
查询红外子设备是否需要显示 devIdspaceId
获取红外子设备是否显示 devIdspaceId
设备显示红外子设备状态变化通知名称
///跳转到网络检测页
/// @param devId 设备 ID
- (void)gotoDeviceDetailNetworkViewControllerWithDeviceId:(NSString *)devId;

///跳转到设备详情页,以 push 方式
/// @param device 设备
/// @param group 群组,若有就传
- (void)gotoDeviceDetailDetailViewControllerWithDevice:(ThingSmartDeviceModel *)device group:(ThingSmartGroupModel *)group;

@optional
/// 查询红外子设备是否需要显示
/// @param devId 设备 ID,传入非红外线网关或者子设备将返回 YES
/// @param spaceId 空间 ID
/// @return YES,展示。NO,不展示。
- (BOOL)getInfraredDisplayStatusWithDevId:(nullable NSString *)devId spaceId:(long long)spaceId;

@optional
/// 判断设备是否开启了显示红外子设备
/// @param devId 设备 ID
/// @param spaceId 空间 ID
/// @return YES,展示。NO,不展示。
- (BOOL)cacheOfInfraredDisplayStatusWithDevId:(nullable NSString *)devId spaceId:(long long)spaceId;

@optional
/// 设备显示红外子设备状态变化通知名称
- (nullable NSNotificationName)subDeviceDisplayStatusChangeNotificationName;

Objective-C 示例

#import <ThingSmartBizCore/ThingSmartBizCore.h>
#import <ThingModuleServices/ThingDeviceDetailProtocol.h>

id<ThingDeviceDetailProtocol> impl = [[ThingSmartBizCore sharedInstance] serviceOfProtocol:@protocol(ThingDeviceDetailProtocol)];

// 跳转到设备网络检测页
[impl gotoDeviceDetailNetworkViewControllerWithDeviceId:@"设备 ID"];

    // 跳转到设备详情页,以 push 方式

    // 如果是设备,new ThingSmartDevice
ThingSmartDevice * device = [ThingSmartDevice deviceWithDeviceId:@"设备 ID"]
    [impl gotoDeviceDetailDetailViewControllerWithDevice: device.deviceModel group: nil];
    // 如果是群组,new ThingSmartGroup
ThingSmartGroup * group = [ThingSmartDevice groupWithGroupId:@"群组 ID"]
    [impl gotoDeviceDetailDetailViewControllerWithDevice: nil group: group.deviceModel];

Swift 示例

let impl = ThingSmartBizCore.sharedInstance().service(of: ThingDeviceDetailProtocol.self) as? ThingDeviceDetailProtocol
//跳转到设备网络检测页
impl?.gotoDeviceDetailNetworkViewController(withDeviceId: "设备 ID")

    // 跳转到设备详情页,以 push 方式

    //如果是设备,new ThingSmartDevice
    let device = ThingSmartDevice.init(deviceId: "vdevo160888044089994")
    impl?.gotoDeviceDetailDetailViewController(withDevice: device?.deviceModel, group: nil)
    //如果是群组,new ThingSmartGroup
    let group = ThingSmartGroup.init(groupId: "群组 ID")
    impl?.gotoDeviceDetailDetailViewController(withDevice: nil, group: group?.groupModel)

主屏快捷方式的跳转

添加到主屏幕 会在屏幕上创建书签,用户单击书签后可以唤起 App 进入设备面板页面。您需要进行简单的配置,才能支持这个功能。

  1. 请在 deviceDetailConfig.json 中添加add_icon_to_home_screen,否则该功能不会展示。

  2. 配置 thing_custom_config.json文件中的 appScheme,例如此处的值为: “您的 appScheme”,您需要将它替换为一个自定义的值。

    {
        "config": {
            ...
            "appScheme": "您的 appScheme"
        },
        ...
    }
    
  3. 在添加 App 的 URL Scheme 中添加上 thing_custom_config.json 文件中 appScheme 对应的值,例如上一步配置的 "您的 appScheme",同样,您需要将它替换为一个自定义的值。

  4. AppDelegate 中处理路由事件:

    Objective-C 示例

    - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options {
        if ([[url.scheme lowercaseString] isEqualToString:@"oralcaretest"]) {
    
            NSDictionary *params = [self dictionaryFromQuery:url.query usingEncoding:NSUTF8StringEncoding];
            NSString *host = url.host;
    
            if ([host isEqualToString:@"panelEx"]) {
                id <ThingPanelProtocol> impl = [[ThingSmartBizCore sharedInstance] serviceOfProtocol:@protocol(ThingPanelProtocol)];
                ThingSmartDevice *device = [ThingSmartDevice deviceWithDeviceId:params[@"devId"]?: @""];
                [impl getPanelViewControllerWithDeviceModel:device.deviceModel initialProps:nil contextProps:nil completionHandler:^(__kindof UIViewController * _Nullable panelViewController, NSError * _Nullable error) {
                    if (error) {
                        NSLog(@"Load error: %@", error);
                    }
                }];
            }
    
    
            return YES;
        }
        return NO;
    }
    
    - (NSDictionary *)dictionaryFromQuery:(NSString *)query usingEncoding:(NSStringEncoding)encoding {
        if (!([query isKindOfClass:[NSString class]] && query.length > 0)) {
            return nil;
        }
        NSCharacterSet *delimiterSet = [NSCharacterSet characterSetWithCharactersInString:@"&;"];
        NSMutableDictionary* pairs = [NSMutableDictionary dictionary];
        NSScanner* scanner = [[NSScanner alloc] initWithString:query];
        while (![scanner isAtEnd]) {
            NSString* pairString = nil;
            [scanner scanUpToCharactersFromSet:delimiterSet intoString:&pairString];
            [scanner scanCharactersFromSet:delimiterSet intoString:NULL];
            NSArray* kvPair = [pairString componentsSeparatedByString:@"="];
            if (kvPair.count == 2) {
                NSString* key = [[kvPair objectAtIndex:0]
                                stringByReplacingPercentEscapesUsingEncoding:encoding];
                NSString* value = [[kvPair objectAtIndex:1]
                                stringByReplacingPercentEscapesUsingEncoding:encoding];
                [pairs setObject:value forKey:key];
            }
        }
    
        return [NSDictionary dictionaryWithDictionary:pairs];
    }
    

    Swift 示例

    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        if let scheme = url.scheme, scheme.lowercased() == "oralcaretest" {
    
            let params = self.params(from: url.query, .utf8)
    
            if let host = url.host, host == "panelEx", let devId = params?["devId"] as? String, let device = ThingSmartDevice(deviceId: devId) {
    
                let impl: ThingPanelProtocol? = ThingSmartBizCore.sharedInstance().service(of: ThingPanelProtocol.self) as? ThingPanelProtocol
                impl?.getPanelViewController(with: device.deviceModel, initialProps: nil, contextProps: nil, completionHandler: { vc, error in
                    if error != nil {
                        print(error!.localizedDescription)
                    }else{
                        print("push the vc")
                    }
                })
            }
    
            return true
        }
        return false
    }
    
    func params(from query: String?, _ encoding: String.Encoding) -> Dictionary<String, Any>? {
        guard let queryString = query, queryString.count > 0 else {
            return nil
        }
    
        let set = CharacterSet(charactersIn: "&;")
        var pairs = Dictionary<String, Any>()
        let scanner = Scanner(string: queryString)
        while !scanner.isAtEnd {
            var pairString: NSString? = ""
            scanner.scanUpToCharacters(from: set, into: &pairString)
            scanner.scanCharacters(from: set, into: nil)
    
            if let kvPair = pairString?.components(separatedBy: "="), kvPair.count == 2 {
                let key: String = kvPair.first!.removingPercentEncoding!
                let value: String = kvPair.last!
                pairs[key] = value
            }
    
        }
        return pairs
    }
    

设备信息支持隐藏位置

默认情况下,SDK 不会展示设备信息中的位置信息隐藏功能。如果需要展示,请在thing_custom_config.json 文件中配置 is_support_home_managertrue

{
    "config": {
        ...
        "is_support_home_manager": true,
    },
   ...
}

删除设备时自定义返回页面

需要自定义实现 ThingDeviceDetailExternalProtocol

/// @brief 移除设备后,自定义回退到哪个页面
/// @return YES 自定义处理,NO 默认逻辑
- (BOOL)customExitWhenDeleted;

自定义子功能

自定义子功能可以通过配置 deviceDetailConfig.json 来实现。在 deviceDetailConfig.json 文件的 deviceDetail 插入自定义 type

type 值必须以 c_ 开头。例如,插入 c_test_insertc_test_async_insert

[
    {
        "name": "headerSection",
        "items": [
            {
                "cellType": "header"
            },{
                "cellType": "device_info"
            },{
                "cellType": "net_setting"
            },{
                "cellType":"group_edit_devices"
            }
        ]
    },{
        "name": "offLineWarnSection",
        "header": {
            "cellType":"section_off_line_warn"
        },
        "items": [
            {
                "cellType" : "off_line_warn"
            }
        ]
    },{
        "name": "otherSection",
        "header": {
            "cellType":"section_other"
        },
        "items": [
            {
                "cellType":"bind_multi_control_link"
            },
            {
                "cellType":"group_create"
            },
            {
                "cellType":"help_and_feedback"
            },
            {
                "cellType":"add_icon_to_home_screen"
            },
            {
                "cellType":"show_infrared_gateway_sub_device"
            },
            {
                "cellType":"check_device_network"
            },
            {
                "cellType":"check_firmware_update"
            },
            {
                "cellType":"c_test_insert"
            },
            {
                "cellType":"c_test_async_insert"
            }
        ]
    },{
        "name": "footerSection",
        "items": [
            {
                "cellType":"footer"
            }
        ],
        "margin": {
            "top": 16
        }
    }
]

返回 item 数据

接口说明:清空数据

进行自定义 item 插入前,调用此方法可清空数据。

/// 清空设置的自定义 item
- (void)clearInsertItem;

接口说明:同步返回 item 数据

/// 设置-》同步处理 item 插入的回调。insertDevMenuItemBlock 会在设备详情刷新时候回调,type 为配置文件中的 cellType
-(void)insertDevMenuItem:(InsertDevMenuItemBlock) insertDevMenuItemBlock customType:(NSString *)type;
//@param type configList.json 里自己添加的 type
//@param device 设备模型
//@param group   群组模型。根据 group 是否为 nil,来判断设备还是群组
//@return 遵守 ThingDeviceDetailCustomMenuModel 协议的对象。返回 nil,该 type 的 item 则不会显示
typedef id<ThingDeviceDetailCustomMenuModel> _Nullable (^InsertDevMenuItemBlock)(NSString* _Nonnull type,
        ThingSmartDeviceModel* _Nullable device,
        ThingSmartGroupModel* _Nullable group);

接口说明:异步回调 item 数据

/// 设置-》异步处理 item 插入的回调,insertDevMenuItemAsyncBlock 会在设备详情刷新时候回调,type 为配置文件中的 cellType
-(void)insertDevMenuItemAsync:(InsertDevMenuItemAsyncBlock) insertDevMenuItemAsyncBlock customType:(NSString *)type;
//异步处理 item 插入,当异步操作结束以后,调用 complete(id<ThingDeviceDetailCustomMenuMode>),回调数据给设备详情,并进行刷新列表。
typedef void (^InsertDevMenuItemAsyncBlock)(NSString* _Nonnull type,
        ThingSmartDeviceModel* _Nullable device,
        ThingSmartGroupModel* _Nullable group,
        InsertDevMenuItemComplete _Nonnull complete);

示例代码

  1. 首先,新建一个 Model 类,遵守 ThingDeviceDetailCustomMenuModel 协议。

    Objective-C 示例:

    //自定义一个类遵守 ThingDeviceDetailCustomMenuModel 协议
    @interface CustomMenuModel : NSObject<ThingDeviceDetailCustomMenuModel>
    ///标题
    @property (nonatomic,copy) NSString *title;
    ///子标题
    @property (nonatomic,copy) NSString *detail;
    @end
    
    @implementation CustomMenuModel
    @end
    

    Swift 示例:

    class CustomMenuModel: NSObject, ThingDeviceDetailCustomMenuModel{
        var title : String?
        var detail : String?
    }
    
  2. 然后,设置数据回调的 block

    Objective-C 示例:

    id<ThingDeviceDetailProtocol> impl = [[ThingSmartBizCore sharedInstance] serviceOfProtocol:@protocol(ThingDeviceDetailProtocol)];
            [impl clearInsertItem];// 插入前清空数据
            [impl insertDevMenuItem:^ id<ThingDeviceDetailCustomMenuModel> (NSString * _Nonnull type, ThingSmartDeviceModel * _Nonnull device, ThingSmartGroupModel * _Nonnull group) {
                if ([type isEqualToString:@"c_test_insert"]) {
                    CustomMenuModel *model = [CustomMenuModel new];
                    if (group) { //根据 group 是否为 nil,来判断设备还是群组
                        model.title = type;
                        model.detail = @"group";
                    }else{
                        model.title = type;
                        model.detail = @"device";
                    }
                    return model;
                }
                return nil;
            } customType:@"c_test_insert"];
    
            [impl insertDevMenuItemAsync:^(NSString * _Nonnull type, ThingSmartDeviceModel * _Nonnull device, ThingSmartGroupModel * _Nonnull group, InsertDevMenuItemComplete _Nonnull complete) {
                if ([type isEqualToString:@"c_test_async_insert"]) {
                //耗时操作
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                    CustomMenuModel *model = [CustomMenuModel new];
                    if (group) { //根据 group 是否为 nil,来判断设备还是群组
                        model.title = type;
                        model.detail = @"group";
                    }else{
                        model.title = type;
                        model.detail = @"device";
                    }
                    complete(model);
                });
                }
    
            } customType:@"c_test_async_insert"];
    

    Swift 示例:

    let impl = ThingSmartBizCore.sharedInstance().service(of: ThingDeviceDetailProtocol.self) as? ThingDeviceDetailProtocol
        impl?.clearInsertItem // 插入前清空数据
        impl?.insertDevMenuItem({ (type, deviceModel, groupModel) -> ThingDeviceDetailCustomMenuModel? in
                if type == "c_test_insert" {//根据 group 是否为 nil,来判断设备还是群组
                    let model = CustomMenuModel.init()
                    if groupModel != nil {
                        model.title = type
                        model.detail = "group"
                    }else{
                        model.title = type
                        model.detail = "device"
                    }
                    return model;
                }
                return nil;
            } , customType: "c_test_insert")
    
    impl?.insertDevMenuItemAsync({ (type, deviceModel, groupModel, complete) in
                if type == "c_test_async_insert" {
                    //耗时操作
                    DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                        let model = CustomMenuModel.init()
                        if groupModel != nil {
                            model.title = type
                            model.detail = "group"
                        }else{
                            model.title = type
                            model.detail = "device"
                        }
                        complete(model);
                    }
                }
            } , customType: "c_test_async_insert");
    

插入 item 点击事件处理

Objective-C 示例

      id<ThingDeviceDetailProtocol> impl = [[ThingSmartBizCore sharedInstance] serviceOfProtocol:@protocol(ThingDeviceDetailProtocol)];
        [impl clickMenuItem: ^(NSString * _Nonnull type, ThingSmartDeviceModel * _Nonnull device, ThingSmartGroupModel * _Nonnull group) {
            NSLog(@"clickItem: type:%@",type);
        }];

Swift 示例

let impl = ThingSmartBizCore.sharedInstance().service(of: ThingDeviceDetailProtocol.self) as? ThingDeviceDetailProtocol

impl?.clickMenuItem({ (type, deviceModel, groupModel) in
            print("clickItem: type:"+type);
    })