一、UniApp与原生App混合开发须知
首先务必确认uni-app和原生代码,谁是主谁是从的问题。两种情况如下:
1.1、uni-app为主
如果你的应用是uni-app开发的,需要扩展一些原生能力,那么首先去插件市场看看有没有现成的插件,如果没有,就自己开发。
原生插件开发的教程,可以参考我的另外一篇文章 《uniapp ios原生插件开发 (framework,cocopods)》
1.2、ios原生为主
如果你的App是原生开发的,部分功能栏目想通过uni-app实现,有2种方式:
- 在原生App里集成uni小程序sdk,参考:《uni小程序SDK》,相当于原生app拥有了小程序能力。
- 如果不想集成原生sdk,那就把uni-app代码发布成H5方式,在原生App里通过webview打开。
接下来本文就重点介绍:在原生应用为主的前提下,如何进行uni小程序的集成 。
二、原生应用集成uniapp小程序
uni小程序SDK,是为原生App打造的可运行基于 uni-app 开发的小程序前端项目的框架,从而帮助原生App快速获取小程序的能力,效果如下:
2.1 uni小程序sdk下载
参见官网下载地址:《UniMP_iOS_SDK》
下载成功后进行解压,解压后的目录结构如下:
其中,UniMPSDK
目录为SDK lib库所在目录, 结构如下:
除SDK lib库所在的目录外,还包含了oc与swift的demo示例。
2.2 sdk集成
2.2.1 打开xcode新建一个App工程
输入工程名:
HelloUniMPDemo
, 完成工程创建, 完成后的工程如下:
2.2.2 集成SDK到工程中
将解压出来的uni小程序sdk UniMPSDK
目录拷贝到新建工程同目录下, 拷贝后的工程目录结构如下:
接着在打开的.xcodeproj
工程中, 右键弹出菜单,选择Add Files to xxx
会弹出添加文件对话框, 选择与工程文件同目录下的Sdk库文件夹, 勾选上 Create groups
被引入后的目录结构如下:
接着去掉不需要的文件引用,只保留
核心库
。 如下图,红框内的文件,删除文件引用
点击Remove References
按钮,删除文件引用。 处理完成后的目录结构如下:
至此,我们已经把uni小程序的SDK集成到工程中了, 编译一下工程,看是否能编译通过。(正常情况下,是可以成功编译的)
2.3 sdk初始化
1) 首页,在AppDelegate.m 中导入头文件 DCUniMP.h
, WeexSDK.h
#import "AppDelegate.h"
#import "DCUniMP.h"
#import "WeexSDK.h"
@interface AppDelegate ()
@end
2) 然后在代理方法 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
中进行初始化
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// 配置参数
NSMutableDictionary *options = [NSMutableDictionary dictionaryWithDictionary:launchOptions];
// 设置 debug YES 会在控制台输出 js log,默认不输出 log,注:需要引入 liblibLog.a 库
[options setObject:[NSNumber numberWithBool:YES] forKey:@"debug"];
// 初始化引擎
[DCUniMPSDKEngine initSDKEnvironmentWithLaunchOptions:options];
return YES;
}
3) 监听App 生命周期方法
#pragma mark - App 生命周期方法
- (void)applicationDidBecomeActive:(UIApplication *)application {
[DCUniMPSDKEngine applicationDidBecomeActive:application];
}
- (void)applicationWillResignActive:(UIApplication *)application {
[DCUniMPSDKEngine applicationWillResignActive:application];
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
[DCUniMPSDKEngine applicationDidEnterBackground:application];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
[DCUniMPSDKEngine applicationWillEnterForeground:application];
}
- (void)applicationWillTerminate:(UIApplication *)application {
[DCUniMPSDKEngine destory];
}
可选,非必须: 如果用到以下AppDelegate的回调,则实现对应的方法。没用到则不需要实现。
#pragma mark - 如果需要使用 URL Scheme 或 通用链接相关功能,请实现以下方法
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
// 通过 url scheme 唤起 App
[DCUniMPSDKEngine application:app openURL:url options:options];
return YES;
}
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
// 通过通用链接唤起 App
[DCUniMPSDKEngine application:application continueUserActivity:userActivity];
return YES;
}
#pragma mark - 如需使用远程推送相关功能,请实现以下方法
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
// 远程通知注册成功,收到 deviceToken 调用sdk方法,传入 deviceToken
[DCUniMPSDKEngine application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
// 远程通知注册失败
[DCUniMPSDKEngine application:application didFailToRegisterForRemoteNotificationsWithError:error];
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
// 收到远程推送消息
[DCUniMPSDKEngine application:application didReceiveRemoteNotification:userInfo];
completionHandler(UIBackgroundFetchResultNewData);
}
#pragma mark - 如需使用本地推送通知功能,请实现以下方法
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
// 收到本地推送消息
[DCUniMPSDKEngine application:application didReceiveLocalNotification:notification];
}
4) 最后编译程序。 你会发现编译报错, 出现很多错误信息。 根据错误信息在 项目的 Build Pase-> link binary with Libraries
添加如下系统库:
libc++.tdb
JavaScriptCore.framework
MediaPlayer.framework
AddressBook.framework
CoreLocation.framework
CoreTelephony.framework
QuickLook.framework
WebKit.framework
AssetsLibrary.framework
此外,还需要在项目的 Build Settings -> Other link flags
中添加 -ObjC
, 且将项目的 BitCode
的值 设置为 NO
通过上面的一顿操作后,再尝试重新编译。 如果都配置正常,则项目工程可编译通过。
2.4 sdk的使用
1) 在UniMPSDK下创建一个group: Apps,sdk解压缩包HelloUniMPDemo/UniMP/Apps拷贝一个wgt到Apps下面。
2) 添加加载测试小程序逻辑。
/// 启动小程序
- (void)openUniMP {
// 获取配置信息
DCUniMPConfiguration *configuration = [self getUniMPConfiguration];
// 配置启动小程序时传递的参数(参数可以在小程序中通过 plus.runtime.arguments 获取此参数)
configuration.arguments = @{ @"arguments":@"Hello uni microprogram" };
__weak __typeof(self)weakSelf = self;
// 打开小程序
[DCUniMPSDKEngine openUniMP:k_AppId configuration:configuration completed:^(DCUniMPInstance * _Nullable uniMPInstance, NSError * _Nullable error) {
if (uniMPInstance) {
weakSelf.uniMPInstance = uniMPInstance;
} else {
NSLog(@"打开小程序出错:%@",error);
}
}];
}
3) 实现DCUniMPSDKEngineDelegate的方法。
完整的代码如下:
#import "UniappViewController.h"
#import "DCUniMP.h"
#define k_AppId @"__UNI__11E9B73" // 小程序名称
@interface UniappViewController ()<DCUniMPSDKEngineDelegate>
@property (nonatomic, weak) DCUniMPInstance *uniMPInstance; // 声明DCUniMPInstance
@end
@implementation UniappViewController
- (void)viewDidLoad {
[super viewDidLoad];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(labelTouchUpInside:)];
UILabel *lable = [[UILabel alloc] initWithFrame:CGRectMake(100,100, 100,100)];
lable.text = @"打开小程序";
lable.textColor = [UIColor redColor];
lable.userInteractionEnabled = YES;
[lable addGestureRecognizer:tap];
[self.view addSubview:lable];
[self checkUniMPResource];
}
-(void) labelTouchUpInside:(UITapGestureRecognizer *)recognizer{
UILabel *label=(UILabel*)recognizer.view;
NSLog(@"%@被点击了",label.text);
[self openUniMP];
}
/// 小程序配置信息
- (DCUniMPConfiguration *)getUniMPConfiguration {
/// 初始化小程序的配置信息
DCUniMPConfiguration *configuration = [[DCUniMPConfiguration alloc] init];
// 开启后台运行
configuration.enableBackground = YES;
// 设置 push 打开方式
configuration.openMode = DCUniMPOpenModePush;
// 启用侧滑手势关闭小程序
configuration.enableGestureClose = YES;
return configuration;
}
/// 检查运行目录是否存在应用资源,不存在将应用资源部署到运行目录
- (void)checkUniMPResource {
if (![DCUniMPSDKEngine isExistsApp:k_AppId]) {
// 读取导入到工程中的wgt应用资源
NSString *appResourcePath = [[NSBundle mainBundle] pathForResource:k_AppId ofType:@"wgt"];
if (!appResourcePath) {
NSLog(@"资源路径不正确,请检查");
return;
}
// 将应用资源部署到运行路径中
if ([DCUniMPSDKEngine releaseAppResourceToRunPathWithAppid:k_AppId resourceFilePath:appResourcePath]) {
NSLog(@"小程序 %@ 应用资源文件部署成功",k_AppId);
}
// 设置 delegate
[DCUniMPSDKEngine setDelegate:self];
}
}
/// 启动小程序
- (void)openUniMP {
// 获取配置信息
DCUniMPConfiguration *configuration = [self getUniMPConfiguration];
// 配置启动小程序时传递的参数(参数可以在小程序中通过 plus.runtime.arguments 获取此参数)
configuration.arguments = @{ @"arguments":@"Hello uni microprogram" };
__weak __typeof(self)weakSelf = self;
// 打开小程序
[DCUniMPSDKEngine openUniMP:k_AppId configuration:configuration completed:^(DCUniMPInstance * _Nullable uniMPInstance, NSError * _Nullable error) {
if (uniMPInstance) {
weakSelf.uniMPInstance = uniMPInstance;
} else {
NSLog(@"打开小程序出错:%@",error);
}
}];
}
#pragma mark - DCUniMPSDKEngineDelegate
/// DCUniMPMenuActionSheetItem 点击触发回调方法
- (void)defaultMenuItemClicked:(NSString *)identifier {
NSLog(@"标识为 %@ 的 item 被点击了", identifier);
// 将小程序隐藏到后台
if ([identifier isEqualToString:@"enterBackground"]) {
__weak __typeof(self)weakSelf = self;
[self.uniMPInstance hideWithCompletion:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(@"小程序 %@ 进入后台",weakSelf.uniMPInstance.appid);
} else {
NSLog(@"hide 小程序出错:%@",error);
}
}];
}
// 关闭小程序
else if ([identifier isEqualToString:@"closeUniMP"]) {
[self.uniMPInstance closeWithCompletion:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(@"小程序 closed");
} else {
NSLog(@"close 小程序出错:%@",error);
}
}];
}
// 向小程序发送消息
else if ([identifier isEqualToString:@"SendUniMPEvent"]) {
[DCUniMPSDKEngine sendUniMPEvent:@"NativeEvent" data:@{@"msg":@"native message"}];
}
}
/// 返回打开小程序时的自定义闪屏视图
- (UIView *)splashViewForApp:(NSString *)appid {
UIView *splashView = [[[NSBundle mainBundle] loadNibNamed:@"SplashView" owner:self options:nil] lastObject];
return splashView;
}
/// 小程序关闭回调方法
- (void)uniMPOnClose:(NSString *)appid {
NSLog(@"小程序 %@ 被关闭了",appid);
self.uniMPInstance = nil;
}
/// 监听小程序发送的事件回调方法
/// @param event 事件
/// @param data 参数
/// @param callback 回调方法,回传数据给小程序
- (void)onUniMPEventReceive:(NSString *)event data:(id)data callback:(DCUniMPKeepAliveCallback)callback {
NSLog(@"Receive UniMP event: %@ data: %@",event,data);
// 回传数据给小程序
// DCUniMPKeepAliveCallback 用法请查看定义说明
if (callback) {
callback(@"native callback message",NO);
}
}
@end
在Xcode中运行代码,看到测试小程序被正常调起了,说明uniapp开发的小程序集成OK了。
三、uniapp小程序SDK使用说明
3.1 应用场景
- 宿主App构建自己的应用生态,可以面向广泛开发者,也可以定向部分开发者
- 原生App利用小程序模式对模块解耦,让不同模块的开发团队各自独立发版,灵活更新
- 原生App中部分功能使用uni-app实现,降低开发成本、提升发布效率
3.2 集成流程
- 宿主开发者下载uni小程序sdk,根据文档集成到宿主App中
- 小程序开发者使用 HBuilderX 创建 uni-app 项目并开发功能,开发阶段可以直接在 HBuilderX 内置基座进行测试,开发完毕后在发行菜单导出wgt包
- 宿主开发者将 wgt 集成到宿主App中,可离线集成直接打包进去,也可以在线下载wgt
- 通过 uni小程序sdk 的 api 将 wgt 资源部署到运行路径,然后即可跳转到该资源对应的小程序应用中
- uni小程序支持 wgt升级,可以在小程序中直接调用 api 在线更新 wgt 资源,即热更新 参考文档
3.3 名词解释
- 宿主:想要构建小程序的原生App,集成uni小程序SDK的宿主
- 小程序:运行在宿主中的前端代码编写的小程序项目,使用 uni-app 框架开发
- wgt:小程序应用资源包,即使用 uni-app 框架开发的项目,导出的小程序应用资源包
- HBuilderX:开发 uni-app 的 IDE 工具
3.4 注意事项
- uni小程序sdk 仅支持在原生App中集成使用,不支持 HBuilderX 打包生成的App中集成。如需在uni-app项目中使用,请加QQ群984388064申请
- uni小程序sdk 支持同时运行多个小程序实例,但android平台最多打开三个
- uni 小程序 sdk 无法使用插件市场中付费的原生插件