React Native 开发笔记
RN 技巧
单独启动 android studio 模拟器(Linux)
sh
./emulator -netdelay none -netspeed full -avd Nexus_5X_API_25
文字过长隐藏的问题
CSS3 中大家可能都会用到 text-oveflow,然而 RN 的 Text 并没有这个属性,不过我们可以通过设置 numberOfLIne 或者 JS 自动计算来实现:
jsx
<Text numberOfLines={1}>your long text here<Text>
获取窗体大小
jsx
import { Dimensions } from "react-native";
export default {
ScreenWidth: Dimensions.get("window").width, // 注意,如果这里的参数传递"screen",会将顶部和底部虚拟按键的高也算进去。
ScreenHeight: Dimensions.get("window").height,
};
关于相对单位
jsx
// 模仿 web 端的 VW,VH,rem
export default {
ScreenWidth: Dimensions.get("window").width,
ScreenHeight: Dimensions.get("window").height,
VW: (value) => (Dimensions.get("window").width / 100) * value,
VH: (value) => (Dimensions.get("window").height / 100) * value,
rem: (value) => (Dimensions.get("window").width / 750) * value,
};
阴影效果
android 和 ios 的阴影设置不一样,ios 可以使用 shadow 来直接设置,android 则需要用 elevation 来设置。
js
const styles = {
container: {
backgroundColor: "#fff",
shadowColor: "#000",
shadowOffset: { h: 10, w: 10 },
shadowRadius: 3,
shadowOpacity: 0.3,
elevation: 4, // 安卓自带的阴影
},
};
组件通信
如果是普通的组件通信可以用 props 解决,但如果是 react-navigation 等管理的组件,这招就不太好用了,比如返回时触发刷新页面等等。
解决方案有 2 种:
第一种是使用共享的状态,即 redux 这类框架去管理状态,这样状态和视图是保持同步的,不需要去告知页面需要刷新。redux 的使用方式这里不再阐述,可以看看我之前写的博文。
第二种是使用 RN 中的 DeviceEventEmitter
js
// 订阅事件
this.subscription = DeviceEventEmitter.addListener("reloadList", () =>
this.getList()
);
js
// 移出
this.subscription.remove();
js
// 发射事件
DeviceEventEmitter.emit("reloadList");
上传文件
以上传图片为例:
使用 RN 中自带的 fetch 方法实现
js
export function uploadImage(imgAry) {
const formData = new FormData();
for (let i = 0; i < imgAry.length; i++) {
let file = {
uri: imgAry[i],
type: "multipart/form-data",
name: "image.jpg",
};
formData.append("file", file); // 这里的 files 就是后台需要的 key
}
console.log("开始上传图片");
return fetch(server + "/common/uploadFile.do", {
method: "POST",
headers: {
"Content-Type": "multipart/form-data",
},
body: formData,
}).then((response) => response.json());
}
拨打电话
jsx
import { Linking } from "react-native";
function callMe() {
return Linking.openURL("tel:10086");
}
富文本效果
RN 的Text
组件 默认没有提供实现富文本效果的属性
效果图:
一开始想通过布局样式实现上图效果,但发现不行
最后通过多个Text
组件来实现
jsx
<Text style={{ lineHeight: 16 }}>
<Text style={{ fontSize: 13, color: "red" }}>{"温馨提示: "}</Text>
<Text
style={{
fontSize: Unity.SmallFont,
marginLeft: 5,
color: Unity.DetailColor,
}}
>
{"你可以对帖子的相关问题进行提问,商家会第一时间回答您的问题!你的咨询" +
"可能会在上方展示供其他网友参考,登录后咨询你可在个人中心-消息提醒中及时查看商家" +
"的回复。"}
</Text>
</Text>
在使用<FlatList />
组件时,如果数据源是对象时,更新数据源数组里某项的某个字段的值时,页面 UI 不进行更新。
需要给<FlatList />
组件指定 数据源
jsx
<FlatList
extraData={this.state} // 指定 数据源
keyExtractor={(item, index) => index.toString()}
data={this.state.dataList}
renderItem={(data) => (
<ListItem data={data} cellClick={(item) => this.cellClick(item)} />
)}
/>
或者在对数据源进行setState
操作时,进行深复制也可以解决
ios 系统 WebView 在底部渲染意外出现黑色边框
将 WebView 的 backgroundColor 设置为透明即可
jsx
<Webview style={{backgroundColor: 'transparent'}} .../>
RN Bug
Android 键盘顶起底部导航的问题
解决方案: 打开 android 工程,在 AndroidManifest.xml
中配置如下:
xml
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:windowSoftInputMode="stateAlwaysHidden|adjustPan">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
iOS12 beta 版闪退
(使用友盟分享 SDK6.8.0 和统计版本 5.4.0 时必现的闪退)
闪退的原因:
友盟的重大 bug 导致,在使用分享 SDK6.8.0 和统计版本 5.4.0 进行注册友盟 key 的时候奔溃,必现。
(注释:使用 [UMConfigure initWithAppkey:KH_UM_APP_ID channel:@"App Store"];
注册友盟 key,这是一个综合注册友盟平台的 API)
奔溃信息如下:
[<UIStatusBarTimeItemView 0x101c29cd0> valueForUndefinedKey:]: this class is not key value coding-compliant for the
原因是 Xcode 在 v10 之后合并了一些底层库文件,导致友盟崩溃,推荐的解决办法是去友盟升级 SDK
对应的解析日志如下:
sh
[UMInternalUtil getSysDateFormate]
[UMInternalUtil Value2]
[UMEnvelopeBuild buildInternalData]
[UMWorkDispatch dispatchEvent:inComponent:]
解决办法:
第一种:升级友盟 SDK 中的 UMCommon.framework, 升级地址, 注:记得下载原生模块下的依赖包。
第二种:遗弃友盟 😄😄
触摸组件在 Android 和 iOS 表现不一致
在 Android 上表现为虚拟组件,即最终渲染后不存在。而 iOS 表现为实体组件。
解决方式:可以在 Android 上嵌套一个 View。
jsx
import React, { Component } from "react";
import {
View,
TouchableNativeFeedback,
TouchableOpacity,
Platform,
} from "react-native";
export default class Touch extends Component {
render() {
if (Platform.OS === "ios") {
return (
<TouchableOpacity
activeOpacity={1}
style={this.props.style}
onPress={this.props.onPress}
>
{this.props.children}
</TouchableOpacity>
);
} else {
return (
<TouchableNativeFeedback onPress={this.props.onPress}>
<View style={this.props.style}>{this.props.children}</View>
</TouchableNativeFeedback>
);
}
}
}
ScrollView 的一些坑
第一个坑:iOS 上 ScrollView 无法直接指定尺寸,必须要通过父级 View。但 Android 上没有此问题。
第二个坑:iOS 键盘弹起时的遮挡问题
推荐使用组件: react-native-keyboard-aware-scroll-view
填坑 - onMessage failing when there are JS warnings and errors on the page
在某些低版本 RN 上会出现
注入一段补丁代码:
js
(function () {
var originalPostMessage = window.postMessage;
var patchedPostMessage = function (message, targetOrigin, transfer) {
originalPostMessage(message, targetOrigin, transfer);
};
patchedPostMessage.toString = function () {
return String(Object.hasOwnProperty).replace(
"hasOwnProperty",
"postMessage"
);
};
window.postMessage = patchedPostMessage;
})();
jsx
const patchPostMessageFunction = function() {
var originalPostMessage = window.postMessage;
var patchedPostMessage = function(message, targetOrigin, transfer) {
originalPostMessage(message, targetOrigin, transfer);
};
patchedPostMessage.toString = function() {
return String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage');
};
window.postMessage = patchedPostMessage;
};
const patchPostMessageJsCode = '(' + String(patchPostMessageFunction) + ')();';
...
<WebView injectedJavaScript={patchPostMessageJsCode} />
borderStyle: "dashed" 在安卓无效的解决方案
js
const style = {
frame: {
borderWidth: 1,
borderStyle: "dashed", // 虚线样式对安卓无效
borderColor: "#dcdcdc",
height: 160,
borderRadius: 0.1, // 加了 borderRadius 虚线就有效了, 原因未知
},
};
某些 RN 版本的 TextInput 在 iOS 上无法输入中文的问题
解决方式如下,重新包装 TextInput:
jsx
import React, { Component } from "react";
import { Platform, TextInput } from "react-native";
class MyTextInput extends Component {
shouldComponentUpdate(nextProps) {
return (
Platform.OS !== "ios" ||
(this.props.value === nextProps.value && !nextProps.defaultValue) ||
(this.props.defaultValue === nextProps.defaultValue && !nextProps.value)
);
}
render() {
return <TextInput {...this.props} />;
}
}
export default MyTextInput;
但是这个解决方案存在值无法回填的 bug,官方解决方案(修改原生代码): https://github.com/facebook/react-native/pull/18456/files?diff=split
watch ENOSPC (linux)
linux 在启动 react-native start
后,过几秒就会出现 watch * * * * * * * * * * *ENOSPC
这个问题
解决方案:
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
或重启电脑
WebView 中文显示乱码的问题
某些安卓上出现此类问题
解决方式方式是在 source 中填写 baseUrl: ''
,具体原因不明。
例如
sh
source={{html: userProtocolStr, baseUrl: ''}}
android 编译时提示编译工具版本不匹配的问题
The SDK Build Tools revision X is too low for project ‘:react-native-picker’. Minimum require X
在根目录下的 gradle 文件中加入以下代码,这样就把每个自工程的 buildToolsVersion 的版本改为要求的版本了
sh
subprojects {
afterEvaluate {project ->
if (project.hasProperty("android")) {
android {
compileSdkVersion 25
buildToolsVersion '25.0.0'
}
}
}
}
在 windows 编译时 JS 报错说路径问题
解决方案:使用 PowerShell 编译 JS,不要使用 CMD。
fetch 如何设置“请求超时”
目前也没有很好的办法解决超时,看网上很多方案都不可靠,主流的方式是抛弃这次 http 请求。
用 setTimeout()
方法,抛弃这次请求。
我看到另外一种,引入一个 polyfill 的文件, 然后在自己的网络请求方法里,采用它定义的 fetch 方法,就可以设置 timeout 参数。
js
import fetch from "react-native-fetch-polyfill";
fetch(url, { timeout: 30 * 1000 })
.then((response) => {
// a successful response
})
.catch((error) => {
// an error when the request fails, such as during a timeout
});
navigation 在登录视图的特殊处理
https://github.com/react-navigation/react-navigation/issues/1815
由于我所做的 app 是先进入空白页面,再判断显示主页还是登录页面,空白页面上用来处理 redux 逻辑,以及显示 loading。为了防止可以退回空白页面,需要做如下处理:
jsx
const prevGetStateForAction = RootStack.router.getStateForAction;
RootStack.router.getStateForAction = (action, state) => {
if (
action.type === "Navigation/BACK" &&
state &&
state.routes[state.routes.length - 1].routeName === "Login"
) {
return null;
}
if (
action.type === "Navigation/BACK" &&
state &&
state.routes[state.routes.length - 1].routeName === "Main"
) {
return null;
}
// Do not allow to go back to Login
if (action.type === "Navigation/BACK" && state) {
const newRoutes = state.routes.filter((r) => r.routeName !== "Login");
const newIndex = newRoutes.length - 1;
return prevGetStateForAction(action, {
index: newIndex,
routes: newRoutes,
});
}
return prevGetStateForAction(action, state);
};
navigation 使用笔记
用数据线链接调试
sh
adb reverse tcp:8081 tcp:8081
用 adb 模拟菜单键
sh
adb shell input keyevent 82
RN 打包
android
先用 Java 工具生成 my-release-key.keystore
sh
keytool -genkey -v -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000
把 my-release-key.keystore 文件放到你工程中的 android/app 文件夹下。
打开 android/app 中的 build.gradle 文件和 android/gradle.properties
build.gradle
sh
signingConfigs {
release {
storeFile file(MYAPP_RELEASE_STORE_FILE)
storePassword MYAPP_RELEASE_STORE_PASSWORD
keyAlias MYAPP_RELEASE_KEY_ALIAS
keyPassword MYAPP_RELEASE_KEY_PASSWORD
}
}
buildTypes {
release {
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
signingConfig signingConfigs.release
}
}
android/gradle.properties
sh
MYAPP_RELEASE_STORE_FILE=my-release-key.keystore
MYAPP_RELEASE_KEY_ALIAS=my-key-alias
MYAPP_RELEASE_STORE_PASSWORD=123456
MYAPP_RELEASE_KEY_PASSWORD=123456
进入 /android/ 目录,cmd 执行 gradlew assembleRelease
下载 android 依赖慢
修改 build.gradle
sh
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/' }
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/' }
jcenter()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android"
}
}
}
找不到 android-sdk
可以在 android/local.properties
中手动配置,如果没有这个文件,手动创建一个。
sh
### This file must *NOT* be checked into Version Control Systems,
## as it contains information specific to your local configuration.
#
## Location of the SDK. This is only used by Gradle.
## For customization when using a Version Control System, please read the
## header note.
#Mon Jul 23 17:38:05 CST 2018
sdk.dir=/media/wolfx/HDD/android/sdk
打包时候报错:app:lintVitalRelease
在 android/app/build.gradle
中加入 lintOptions
:
sh
android {
lintOptions {
checkReleaseBuilds false
abortOnError false
}
...
}
NDK 找不到 mipsel/mips64el
新版 NDK 去掉了 mipsel,需要下载旧版(16b)替换。 https://developer.android.google.cn/ndk/downloads/older_releases
友盟分享、第三方登陆、推送集成
最好下载原生版本的 SDK,不要下载 RN 版本,RN 版本对新系统存在兼容问题。友盟分享的集成也是。 然后下载 GitHub 上的官方 DEMO,复制必要的文件到自己的项目。
基本配置可以参考官方,下面是接收推送的自定义参数:
Android 端集成
修改 MainActivity.java
java
package com.enjoybuild;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import com.facebook.react.ReactActivity;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.umeng.message.PushAgent;
import com.umeng.socialize.UMShareAPI;
import java.util.Set;
public class MainActivity extends ReactActivity {
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "EnjoyBuild";
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
PushModule.initPushSDK(this);
PushAgent.getInstance(this).onAppStart();
ShareModule.initSocialSDK(this);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
UMShareAPI.get(this).onActivityResult(requestCode, resultCode, data);
}
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
@Override
protected void onResume() {
// 获取推送传递的参数
// https://developer.umeng.com/docs/66632/detail/66744#h2-u81EAu5B9Au4E49u53C2u657013
Bundle bun = getIntent().getExtras();
if (bun != null) {
WritableMap params = Arguments.createMap();
Set<String> keySet = bun.keySet();
for (String key : keySet) {
String value = bun.getString(key);
params.putString(key, value);
}
try {
sendEvent(MyModule.reactContext, "notification", params);
} catch (Exception e) {
Log.e("error", e.getMessage());
}
}
super.onResume();
}
private void sendEvent(ReactContext reactContext,
String eventName,
@Nullable WritableMap params) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
}
MyModule.java
是自己定义的模块,为了获取 RN 的上下文。
java
package com.enjoybuild;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
/**
* Created by wolfx on 18-11-6.
*/
public class MyModule extends ReactContextBaseJavaModule {
public static ReactContext reactContext;
public MyModule(ReactApplicationContext rc) {
super(rc);
reactContext = rc;
}
@Override
public String getName() {
return "MyModule";
}
}
iOS 端集成
1、下载相关 SDK
1.1、下载用于友盟集成的RN SDK
1.2、下载UMCommon.framework
注: 这个记得一定要下载 iOS 选项下面的 SDK,后面集成的时候会用到,之前按教程集成的时候没有找到这个文件,就去友盟 github 的 demo 里面复制了这个文件,结果在集成的时候由于不是最新的版本,导致在 Xcode10 上面一直闪退,崩溃提示是
[<UIStatusBarTimeItemView 0x101c29cd0> valueForUndefinedKey:]: this class is not key value coding-compliant for the...
,后面的朋友如果碰到 iOS 升级之后项目编译异常的也可以考虑升级 SDK 试试。
2、集成分享和授权登陆
2.1、首先进入项目的 ios 目录,进入对应的工程名目录,新建两个文件夹:UMReactBridge
和UMComponents
2.2、将1.2
步骤下载的文件iOS目录下的 common 中的 framework,拷入UMComponents
中。
将1.1
步骤下载的文件iOS目录下中share目录中的最后所有文件,拷入UMComponents
中的新建文件夹UMShare
中,在将push文件里最后的所有文件拷入UMComponents
文件夹中。看下图:
集成之后在 xcode 中的目录结构是这样的:
2.3、进入1.1
步骤下载文件的 ReactNative 目录,找到common、 share、push
目录中对应的common_ios
平台中的桥接.h 和 .m
文件,全部拷贝至我们项目刚刚新建的UMReactBridge
文件夹中,看下图:
集成之后 xcode 中的目录结构为:
2.4、在xcode
中打开工程目录,右键黄色项目名Add Files to "xxx"
,options
中选中Create groups 、 Copy items if needed
找到我们新建的UMReactBridge
和UMComponents
,最后点击add
添加。看下图
2.5、Linked Frameworks and Libraries
加入系统依赖库:可参照链接
sh
libsqlite3.tbd
CoreGraphics.framework
SystemConfiguration.framework
CoreTelephony.framework
libc++.tbd
libz.tbd
2.6 配置 SSO 白名单,右键 info.plist 选择 source code 打开(plist 具体设置在 Build Setting -> Packaging -> Info.plist File 可获取 plist 路径)请根据选择的平台对以下配置进行裁剪:
xml
<key>LSApplicationQueriesSchemes</key>
<array>
<!-- 微信 URL Scheme 白名单-->
<string>wechat</string>
<string>weixin</string>
<!-- 新浪微博 URL Scheme 白名单-->
<string>sinaweibohd</string>
<string>sinaweibo</string>
<string>sinaweibosso</string>
<string>weibosdk</string>
<string>weibosdk2.5</string>
<!-- QQ、Qzone URL Scheme 白名单-->
<string>mqqapi</string>
<string>mqq</string>
<string>mqqOpensdkSSoLogin</string>
<string>mqqconnect</string>
<string>mqqopensdkdataline</string>
<string>mqqopensdkgrouptribeshare</string>
<string>mqqopensdkfriend</string>
<string>mqqopensdkapi</string>
<string>mqqopensdkapiV2</string>
<string>mqqopensdkapiV3</string>
<string>mqqopensdkapiV4</string>
<string>mqzoneopensdk</string>
<string>wtloginmqq</string>
<string>wtloginmqq2</string>
<string>mqqwpa</string>
<string>mqzone</string>
<string>mqzonev2</string>
<string>mqzoneshare</string>
<string>wtloginqzone</string>
<string>mqzonewx</string>
<string>mqzoneopensdkapiV2</string>
<string>mqzoneopensdkapi19</string>
<string>mqzoneopensdkapi</string>
<string>mqqbrowser</string>
<string>mttbrowser</string>
<string>tim</string>
<string>timapi</string>
<string>timopensdkfriend</string>
<string>timwpa</string>
<string>timgamebindinggroup</string>
<string>timapiwallet</string>
<string>timOpensdkSSoLogin</string>
<string>wtlogintim</string>
<string>timopensdkgrouptribeshare</string>
<string>timopensdkapiV4</string>
<string>timgamebindinggroup</string>
<string>timopensdkdataline</string>
<string>wtlogintimV1</string>
<string>timapiV1</string>
</array>
2.7、配置 URL Scheme
URL Scheme 是通过系统找到并跳转对应 app 的一类设置,通过向项目中的 info.plist 文件中加入 URL types 可使用第三方平台所注册的 appkey 信息向系统注册你的 app,当跳转到第三方应用授权或分享后,可直接跳转回你的 app。
2.8 初始化AppDelegate.m
设置友盟appkey
和appSecret(如果有推送功能的话需要加)
以及各个平台的appkey
和secret
.
objective-c
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
/* 打开调试日志 */
[[UMSocialManager defaultManager] openLog:YES];
/* 设置友盟appkey */
[RNUMConfigure initWithAppkey:@"5bd*********0db" channel:@"App Store"];
/*
* 关闭强制验证https,可允许http图片分享,但需要在info.plist设置安全域名
*/
[UMSocialGlobal shareInstance].isUsingHttpsWhenShareContent = NO;
/*
设置微信的appKey和appSecret
[微信平台从U-Share 4/5升级说明]http://dev.umeng.com/social/ios/%E8%BF%9B%E9%98%B6%E6%96%87%E6%A1%A3#1_1
*/
[[UMSocialManager defaultManager] setPlaform:UMSocialPlatformType_WechatSession appKey:@"wxd******" appSecret:@"e7b246a7bdcfe75100e77eb9e999bf37" redirectURL:nil];
/* 设置分享到QQ互联的appID
* U-Share SDK为了兼容大部分平台命名,统一用appKey和appSecret进行参数设置,而QQ平台仅需将appID作为U-Share的appKey参数传进即可。
100424468.no permission of union id
[QQ/QZone平台集成说明]http://dev.umeng.com/social/ios/%E8%BF%9B%E9%98%B6%E6%96%87%E6%A1%A3#1_3
*/
[[UMSocialManager defaultManager] setPlaform:UMSocialPlatformType_QQ appKey:@"*****"/*设置QQ平台的appID*/ appSecret:@"*******" redirectURL:@"http://mobile.umeng.com/social"];
// Push's basic setting 推送设置!!
UMessageRegisterEntity * entity = [[UMessageRegisterEntity alloc] init];
//type是对推送的几个参数的选择,可以选择一个或者多个。默认是三个全部打开,即:声音,弹窗,角标
entity.types = UMessageAuthorizationOptionBadge|UMessageAuthorizationOptionAlert|UMessageAuthorizationOptionSound;
[UNUserNotificationCenter currentNotificationCenter].delegate=self;
[UMessage registerForRemoteNotificationsWithLaunchOptions:launchOptions Entity:nil completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
NSLog(@"deviceToken+++++++++");
} else {
}
}];
return YES;
}
//从支付、分享、第三方登陆等跳转会本APP时的回调,必须实现,否则收不到分享成功时的回调!!!!
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
BOOL result = [[UMSocialManager defaultManager] handleOpenURL:url];
if (!result) {
// 其他如支付等SDK的回调
}
return result;
}
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url
{
BOOL result = [[UMSocialManager defaultManager] handleOpenURL:url];
if (!result) {
// 其他如支付等SDK的回调
}
return result;
}
2.9、提供给 RN 调用的方法在UMShareModule.m
中查看.
2.10、进入下载目录的ReactNative
找到common
下的js
中的ShareUtil.js(包含分享和登陆的方法)
,拷贝到我们RN
目录下,即可在 RN 中导入使用分享的方法啦。
2.11、在 RN 中调用
jsx
import ShareUtil from "./ShareUtil";
//分享
ShareUtile.shareboard(text, img, url, title, list, (code, message) => {
if (code == 200) {
this.setState({ result: message });
}
});
// text 为分享内容
// img 为图片地址,可以为链接,本地地址以及res图片(如果使用res,请使用如下写法:res/icon.png)
// url 为分享链接,可以为空
// title 为分享链接的标题
// list 为分享平台数组,如:var list = [0,1,2]
// callback中code为错误码,当为0时,标记成功。message为错误信息
//第三方登陆
ShareUtile.auth(0, (code, result, message) => {
if (code == 200) {
// iOS平台
//登陆成功,返回第三方账号的信息,
//比如openID、nickName、headPath等信息,这时就可以传给自己的后台小伙伴入库啦!!!
}
});
注:android 与 ios 平台成功回调中的 code 值不一致,ios 成功时 code===200,android 成功时 code===0,文档写的是 0,当时还被小坑啦一把。
3、集成消息推送
SDK 端集成已经在分享和登陆步骤中已经集成,都有截图参考,下面直接进入功能实现步骤吧:
- 其实对于推送功能,本质还是集成原生系统端推送功能,大部分代码逻辑还是在原生端实现,比如 注册推送权限、获取 DeviceToken、设置推送提醒的类型,设置收到推送时的回调等。
- 比较烦的是对推送证书的配置,注: 如果做推送的话必须是付费的开发者账号生成证书才可以,否则在 Xcode 里设置开启推送权限的时候会找不到该菜单项。看下图
- 具体的集成步骤以及证书生成参考友盟官网教程: 证书配置集成步骤
- 在推送的时候,可以通过设置不通的 tag 值对用户分组进行推送,也可以通过用户 id 进行精准推送,RN 也提供来对应的方法 PushUtil.addTag(tag,(code,remain) =>{ })和 PushUtil.addAlias(alias,type,(code) =>{ }),具体参考文档。
- 还有待优化的功能: 关于 iOS 如何在接收到推送后打开指定页面, 官方给的地址以及找不到了, 我自己的实现方法是在原生端收到推送消息后参考 RN 文档中原生端调用 RN 的方法使用通知来实现,期待朋友们有好的方法,期待赐教。 推送主要代码:
AppDelegate.h
页面
objective-c
#import <UIKit/UIKit.h>
#import <UMPush/UMessage.h>
//!!!!一定要实现这个代理方法 :UNUserNotificationCenterDelegate
@interface AppDelegate : UIResponder <UIApplicationDelegate,UNUserNotificationCenterDelegate>
@property (nonatomic, strong) UIWindow *window;
@end
AppDelegate.m
页面
objective-c
#import "AppDelegate.h"
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import "RNUMConfigure.h"
#import <UMShare/UMShare.h>
#import "YouMengNotice.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Push's basic setting
UMessageRegisterEntity * entity = [[UMessageRegisterEntity alloc] init];
//type是对推送的几个参数的选择,可以选择一个或者多个。默认是三个全部打开,即:声音,弹窗,角标
entity.types = UMessageAuthorizationOptionBadge|UMessageAuthorizationOptionAlert|UMessageAuthorizationOptionSound;
// 需要实现代理,用于下面代理方法的实现。
[UNUserNotificationCenter currentNotificationCenter].delegate=self;
[UMessage registerForRemoteNotificationsWithLaunchOptions:launchOptions Entity:nil completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
NSLog(@"deviceToken+++++++++");
} else {
}
}];
}
#pragma mark - Push
//在这个方法打印出 deviceToken, 便于在友盟后台添加测试设备进行测试
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
[UMessage registerDeviceToken:deviceToken];
NSLog(@"deviceToken+++++++++%@",[[[[deviceToken description] stringByReplacingOccurrencesOfString: @"<" withString: @""]
stringByReplacingOccurrencesOfString: @">" withString: @""]
stringByReplacingOccurrencesOfString: @" " withString: @""]);
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
}
// 次方法在iOS10以后,已经不再支持
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
//关闭友盟自带的弹出框
[UMessage setAutoAlert:NO];
[UMessage didReceiveRemoteNotification:userInfo];
[YouMengNotice OCsendMessageToReactNative:userInfo];
}
//iOS10新增:处理前台收到通知的代理方法
-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler{
NSDictionary * userInfo = notification.request.content.userInfo;
if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
//应用处于前台时的远程推送接受
//关闭友盟自带的弹出框
[UMessage setAutoAlert:NO];
//必须加这句代码
[UMessage didReceiveRemoteNotification:userInfo];
// 在这里自己实现来在接收到远程推送时,主动调用RN端的,把通知内容传给RN,这样就可以控制RN收到推送时跳转到指定页面来。
[YouMengNotice OCsendMessageToReactNative:userInfo];
}else{
//应用处于前台时的本地推送接受
}
completionHandler(UNNotificationPresentationOptionSound|UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionAlert);
}
//iOS10新增:处理后台点击通知的代理方法
//这个方法是在APP接收到通知之后,点击通知栏跳转到APP时才调用的, 注意::!!如果直接点击APP是没用的
//知识点!! iOS的通知 分APP在前台时显示和点击通知栏跳转到APP显示, 如果直接点击APP的话是不会走上面的两个回调方法的,之前理解错误
-(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler{
NSDictionary * userInfo = response.notification.request.content.userInfo;
if([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
//应用处于后台时的远程推送接受
//必须加这句代码
[UMessage didReceiveRemoteNotification:userInfo];
[YouMengNotice performSelector:@selector(OCsendMessageToReactNative:) withObject:userInfo afterDelay:15];
// [YouMengNotice OCsendMessageToReactNative:userInfo];
}else{
//应用处于后台时的本地推送接受
}
}
RN 端实现:
jsx
import PushUtil from "../Tools/PushUtil.js";
import { NativeModules, NativeEventEmitter } from "react-native";
const { YouMengNotice } = NativeModules;
const myNativeEvt = new NativeEventEmitter(YouMengNotice); //创建自定义事件接口
componentDidMount() {
//收到原生端发来的推送消息,这里处理页面跳转逻辑
myNativeEvt.addListener("OCSendToRN", (e) => {
console.log("原生端的消息");
msg(JSON.stringify(e));
});
//添加tag,即分组,在推送时可以对不同分组用户进行推送
// PushUtil.addTag("ccj",(code, res) =>{
// Alert.alert("提示", ""+code+JSON.stringify(res));
// })
PushUtil.addTag("个人用户", (code, res) => {
// Alert.alert("提示", ""+code+JSON.stringify(res));
});
}
制作 Android 启动页
1.首先编写样式文件 res/values/style.xml
加入:
xml
<style name="Dialog_Fullscreen">
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
</style>
2.编写 res/layout/activity_launch.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/ic_launcher">
</LinearLayout>
3.编写界面 SplashScreen.java
java
import android.app.Activity;
import android.app.Dialog;
public class SplashScreen {
private static Dialog mSplashDialog; // 显示启动页
public static void show(final Activity activity) {
mSplashDialog = new Dialog(activity, R.style.Dialog_Fullscreen);
mSplashDialog.setContentView(R.layout.activity_launch);
mSplashDialog.setCancelable(false);
mSplashDialog.show();
}
public static void hide(Activity activity) {
if (mSplashDialog != null) {
mSplashDialog.dismiss();
}
mSplashDialog = null;
}
}
4.定义暴露给 RN 桥接的方法
java
import android.content.Context;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
public class ModuleHideSplash extends ReactContextBaseJavaModule {
private Context context;
public ModuleHideSplash(ReactApplicationContext reactContext) {
super(reactContext);
context = reactContext;
}
@Override
public String getName() {
return "SplashScreen";
}
@ReactMethod
public void show() {
SplashScreen.show(getCurrentActivity());
}
@ReactMethod
public void hide() {
SplashScreen.hide(getCurrentActivity());
}
}
5.定义桥接包:
java
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class MyPackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new ModuleHideSplash(reactContext));
return modules;
}
}
6.初始化桥接包:
在 MainApplication.java
中添加 MyPackage
:
java
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
...,
new MyPackage()
);
}
7.设置启动时展示:
在 MainActivity.java
中加入
java
@Override
public void onCreate(Bundle savedInstanceState) {
SplashScreen.show(this);
super.onCreate(savedInstanceState);
}
8.RN 端调用
js
import {NativeModules} from "react-native";
...
NativeModules.SplashScreen.hide();
第三方控件推荐
字母表控件
https://github.com/i6mi6/react-native-alphabetlistview
原生渲染富文本
https://github.com/jsdf/react-native-htmlview
轮播
react-native-swiper
或 @nart/react-native-swiper
,后者是修正 Android 上无法垂直滚动的版本。
查看大图
推荐组件 react-native-image-zoom-viewer
jsx
import ImageViewer from "react-native-image-zoom-viewer";
// ...
<ImageViewer
imageUrls={imagePathFormat(this.state.contractPicture).map((url) => {
return { url: url };
})}
index={this.state.imageIndex}
enableImageZoom={true}
/>;
图片上传、拍照、图片压缩
推荐组件 react-native-image-picker
下面代码是我们项目中的代码片段,可以配合上面的文件上传代码使用。
jsx
import React, { Component } from "react";
import { Alert, Image, TouchableOpacity, View } from "react-native";
import ImagePicker from "react-native-image-picker";
// 图片选择器参数设置
const options = {
title: "请选择图片来源",
cancelButtonTitle: "取消",
takePhotoButtonTitle: "拍照",
chooseFromLibraryButtonTitle: "相册图片",
quality: 0.7,
maxWidth: 800,
maxHeight: 800,
// 此参数用于照片是否存储照片到手机相册
// storageOptions: {
// skipBackup: true,
// path: 'images'
// }
};
const styles = {
frame: {
borderTopWidth: 1,
borderTopColor: "#707070",
borderLeftWidth: 1,
borderLeftColor: "#707070",
borderRightWidth: 1,
borderRightColor: "#707070",
borderBottomWidth: 1,
borderBottomColor: "#707070",
justifyContent: "center",
alignItems: "center",
width: 80,
height: 80,
},
};
export default class MyImagePicker extends Component {
selectImage() {
ImagePicker.showImagePicker(options, (response) =>
this.selectImageCallback(response)
);
}
selectImageCallback(response) {
if (response.didCancel) {
Alert.alert("提示", "用户取消了选择!");
} else if (response.error) {
Alert.alert("ImagePicker发生错误:", response.error);
} else {
const source = { uri: response.uri };
// You can also display the image using data:
// let source = { uri: 'data:image/jpeg;base64,' + response.data };
this.props.onChange(source);
}
}
remove(index) {
Alert.alert(
"提示",
"确认移除这张图?",
[
{
text: "确认",
onPress: () => {
const newFiles = this.props.files;
newFiles.splice(index, 1);
this.props.onChange(newFiles);
},
},
{ text: "取消", onPress: () => {}, style: "cancel" },
],
{ cancelable: false }
);
}
render() {
return (
<View style={{ flexDirection: "row" }}>
{this.props.files.map((file, index) => {
return (
<TouchableOpacity onPress={() => this.remove(index)}>
<Image
resizeMode={"cover"}
style={{
width: 80,
height: 80,
marginLeft: index === 0 ? 0 : 10,
marginBottom: 10,
}}
source={{ uri: file.url }}
/>
</TouchableOpacity>
);
})}
<TouchableOpacity onPress={() => this.selectImage()}>
{this.props.selectable && (
<View
style={[
styles.frame,
{ marginLeft: this.props.files.length > 0 ? 10 : 0 },
]}
>
<Image
resizeMode={"contain"}
style={{ width: 50, height: 50 }}
source={require("../Assets/imgs/plus.png")}
/>
</View>
)}
</TouchableOpacity>
</View>
);
}
}
react-native Android 全面屏手机 底部留有一大块黑屏
解决方案:在 AndroidManifest.xml
中 配置
xml
<meta-data android:name="android.max_aspect" android:value="2.1" />
Could not find com.android.tools.build:aapt2:3.2.0-alpha14-4748712
在最上级的 builde.gralde
增加谷歌库 解决问题
allprojects {
repositories {
google()
jcenter()
}
}
为 R-N 配置 TypeScript 开发环境
typescript 是 javascript 的超集,在 javascript 的基础上添加了可选的静态类型,非常适合团队开,这次我们尝试使用 typescript 来开发 react-native 应用。 搭建 react-native 开发环境
安装 yarn 和 react-native 命令行工具
sh
npm install -g yarn react-native-cli
Yarn 是 Facebook 提供的替代 npm 的工具,可以加速 node 模块的下载。React Native 的命令行工具用于执行创建、初始化、更新项目、运行打包服务(packager)等任务。
React Native 目前需要 Xcode 7.0 或更高版本。你可以通过 App Store 或是到 Apple 开发者官网上下载。这一步骤会同时安装 Xcode IDE 和 Xcode 的命令行工具。
虽然一般来说命令行工具都是默认安装了,但你最好还是启动 Xcode,并在 Xcode | Preferences | Locations 菜单中检查一下是否装有某个版本的 Command Line Tools。Xcode 的命令行工具中也包含一些必须的工具,比如 git 等。 搭建 typescript 开发环境
先安装 typescript
sh
npm install -g typescript
接下来安装 typings typings 是 typescript 的依赖管理器,如果你使用 sublime text 或者 vscode,会非常方便的补全代码
sh
npm install -g typings
typings install 安装缓慢:追加参数 --registry=https://registry.npm.taobao.org
解决
使用 react-native 命令行工具初始化 react-native 项目
sh
react-native init ReactNativeApp
等待片刻后,进入刚刚新建的项目,创建一个名为"tsconfig.json" 的文件。tsconfig.json 是一个 typescript 项目的配置文件,可以通过读取它来设置 ts 编译器的编译参数 内容如下:
json
{
"compilerOptions": {
"target": "es6",
"allowJs": true,
"jsx": "react",
"outDir": "./dist",
"sourceMap": true,
"noImplicitAny": false
},
"include": ["typings/**/*.d.ts", "src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules"]
}
在项目下新建一个目录"src",typescripe 源代码就放在这里
现在安装 typings 依赖
sh
typings install npm~react --save
typings install dt~react-native --globals --save
编写 Hello world
在 src 目录下新建 myview.tsx,内容如下
jsx
import * as React from "react";
import { Text } from "react-native";
/**
* Hello
*/
export default class Hello extends React.Component<null, null> {
render() {
return <Text>Hello world!</Text>;
}
}
返回根目录,编译刚刚写好的 tsx 文件
sh
tsc
修改 index.ios.js
jsx
import React, { Component } from "react";
import Hello from "./dist/myview";
import { AppRegistry, StyleSheet, Text, View } from "react-native";
export default class ReactNativeApp extends Component {
render() {
return (
<View style={styles.container}>
<Hello />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#F5FCFF",
},
});
AppRegistry.registerComponent("ReactNativeApp", () => ReactNativeApp);
运行 run-ios 试试效果
sh
react-native run-ios
Gradle DSL method not found
错误提示:
sh
Gradle DSL method not found: 'compileOnly()' or Gradle DSL method not found: 'implementation()'
解决方式有两种:
- 升级 gradle 版本
- 把对应的方法更改成当前 gradle 中的方法。比如,
compileOnly()
和implementation()
都可以换成compile()
发布后运行闪退的问题
大概率是 RN 的包没有打进 APK,运行下面代码:
sh
react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/