设备详情 UI 业务包

更新时间:2023-05-22 06:38:28

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

  • 设备信息编辑:包括设备头像、设备所在房间、名称等
  • 设备信息查询:包括设备 ID、信号等
  • 检查固件升级:您需要额外接入 设备 OTA 升级 UI 业务包
  • 常见问题与反馈:您需要额外接入 常见问题与反馈 UI 业务包
  • 创建或编辑群组:从 3.25.0 版本 SDK 开始支持,您需要额外接入 设备群组 UI 业务包
  • 备用网络设置
  • 检查设备网络
  • 离线提醒功能
  • 删除设备
  • 添加到主屏幕
  • 展示遥控器

接入组件

Pod 集成

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

source "https://github.com/tuya/tuya-pod-specs"
source 'https://cdn.cocoapods.org/'
platform :ios, '11.0'

target 'your_target_name' do
# 添加设备详情 UI 业务包
pod 'TuyaSmartDeviceDetailBizBundle'
end

权限声明

设备详情 UI 业务包涉及到设备图标上传时,需要使用系统相册和相机权限。因此,会涉及到部分苹果隐私权限的声明。您需要在工程的 info.plist 中添加如下权限声明:

<!-- 相册 -->
<key>NSPhotoLibraryUsageDescription</key>
<string>App 需要您的同意,才能访问相册</string>
<!-- 相机 -->
<key>NSCameraUsageDescription</key>
<string>App 需要您的同意,才能访问相机</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 设备离线提醒
    group_edit_devices 编辑群组
    group_create 创建群组
    help_and_feedback 常见问题与反馈,您需要额外接入 常见问题与反馈 UI 业务包
    check_firmware_update 检查固件升级,您需要额外接入 设备 OTA 升级 UI 业务包
    add_icon_to_home_screen 添加到主屏幕
    show_infrared_gateway_sub_device 展示遥控器
    footer 移除设备

依赖服务

TYSmartHomeDataProtocol(已废弃),推荐使用TYFamilyProtocol

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

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

Objective-C 示例

#import <TuyaSmartBizCore/TuyaSmartBizCore.h>
#import <TYModuleServices/TYSmartHomeDataProtocol.h>

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

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

Swift 示例

import TuyaSmartDeviceKit

class TYMessageCenterTest: NSObject,TYSmartHomeDataProtocol{

	func test() {
		TuyaSmartBizCore.sharedInstance().registerService(TYSmartHomeDataProtocol.self, withInstance: self)
	}

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

}

可选功能实现

方法调用

方法 参数
跳转到网络检测页 设备 ID
跳转到设备详情页,以 push 方式 TuyaSmartDeviceModel/TuyaSmartGroupModel
查询红外子设备是否需要显示 devId、spaceId
获取红外子设备是否显示 devId、spaceId
设备显示红外子设备状态变化通知名称 -

接口说明

///跳转到网络检测页
/// @param devId 设备 ID
- (void)gotoDeviceDetailNetworkViewControllerWithDeviceId:(NSString *)devId;

///跳转到设备详情页,以 push 方式
/// @param device 设备
/// @param group 群组,若有就传
- (void)gotoDeviceDetailDetailViewControllerWithDevice:(TuyaSmartDeviceModel *)device group:(TuyaSmartGroupModel *)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 <TuyaSmartBizCore/TuyaSmartBizCore.h>
#import <TYModuleServices/TYDeviceDetailProtocol.h>

id<TYDeviceDetailProtocol> impl = [[TuyaSmartBizCore sharedInstance] serviceOfProtocol:@protocol(TYDeviceDetailProtocol)];

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

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

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

Swift 示例

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

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

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

主屏快捷方式的跳转

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

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

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

    {
    	"config": {
    		...
    		"appScheme": "您的 appScheme"
    	},
    	...
    }
    
  3. 在添加 App 的 URL Scheme 中添加上 ty_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 <TYPanelProtocol> impl = [[TuyaSmartBizCore sharedInstance] serviceOfProtocol:@protocol(TYPanelProtocol)];
                TuyaSmartDevice *device = [TuyaSmartDevice 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 = TuyaSmartDevice(deviceId: devId) {
    
                let impl: TYPanelProtocol? = TuyaSmartBizCore.sharedInstance().service(of: TYPanelProtocol.self) as? TYPanelProtocol
                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
    }
    

支持相册选择照片或照相机拍照

实现设备信息支持相册选择照片或者照相机拍照进行设备图标修改,请在ty_custom_config.json 文件中:

  • 配置 is_enable_device_icon_edittrue 后,可以开启普通设备 相册选择照片/照相机拍照 图标修改。

  • 配置 is_enable_group_icon_edittrue,可以开启群组设备 相册选择照片/照相机拍照 图标修改。

    {
        "config": {
            ...
            "is_enable_device_icon_edit": true,
            "is_enable_group_icon_edit": true
        },
    ...
    }
    

设备信息中隐藏位置

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

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

自定义子功能

自定义子功能可以通过配置 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 插入的回调。insertDevMenuItemBlock 会在设备详情刷新时候回调,type 为配置文件中的 cellType
-(void)insertDevMenuItem:(InsertDevMenuItemBlock) insertDevMenuItemBlock customType:(NSString *)type;
//@param type configList.json 里自己添加的 type
//@param device  设备模型
//@param group   群组模型。 根据 group 是否为 nil,来判断设备还是群组
//@return 遵守 TYDeviceDetailCustomMenuModel 协议的对象。返回 nil,该 type 的 item 则不会显示
typedef id<TYDeviceDetailCustomMenuModel> _Nullable (^InsertDevMenuItemBlock)(NSString* _Nonnull type,
		TuyaSmartDeviceModel* _Nullable device,
		TuyaSmartGroupModel* _Nullable group);

异步回调

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

示例代码

第一步:新建一个 Model 类

Objective-C 示例

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

@implementation CustomMenuModel
@end

Swift 示例

class CustomMenuModel: NSObject, TYDeviceDetailCustomMenuModel{
	var title : String?
	var detail : String?
}

第二步:设置数据回调的 block

Objective-C 示例

id<TYDeviceDetailProtocol> impl = [[TuyaSmartBizCore sharedInstance] serviceOfProtocol:@protocol(TYDeviceDetailProtocol)];

		[impl insertDevMenuItem:^ id<TYDeviceDetailCustomMenuModel> (NSString * _Nonnull type, TuyaSmartDeviceModel * _Nonnull device, TuyaSmartGroupModel * _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, TuyaSmartDeviceModel * _Nonnull device, TuyaSmartGroupModel * _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_insert"];

Swift 示例

let impl = TuyaSmartBizCore.sharedInstance().service(of: TYDeviceDetailProtocol.self) as? TYDeviceDetailProtocol

	impl?.insertDevMenuItem({ (type, deviceModel, groupModel) -> TYDeviceDetailCustomMenuModel? 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<TYDeviceDetailProtocol> impl = [[TuyaSmartBizCore sharedInstance] serviceOfProtocol:@protocol(TYDeviceDetailProtocol)];
		[impl clickMenuItem: ^(NSString * _Nonnull type, TuyaSmartDeviceModel * _Nonnull device, TuyaSmartGroupModel * _Nonnull group) {
			NSLog(@"clickItem: type:%@",type);
		}];

Swift 示例

let impl = TuyaSmartBizCore.sharedInstance().service(of: TYDeviceDetailProtocol.self) as? TYDeviceDetailProtocol

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