【前言】
就目前而言,由于之前从未接触过iOS开发工作,目前尚处于摸索过程中,一切工作成果以初步实现为最基准。
本文记载我研究如何使用 flutter 的过程,以 iOS 端为基础端,以及遇到的一些问题。
==================
一、本机调试运行
2020-12-28 起改为 flutter 框架。
现已成功上传至 testFlight
【研发行动步骤】
-
✓找一个适合的 coding 编辑器
vs code for mac -
✓真机调试
(need registered device)
flutter run -
✓了解代码结构、代码拆分
-
✓创建一般页面
-
✓添加页面交互
就是特定可交互组件的特定属性值的设定 -
路由实现:
还是静态路由香
✓1.一般前进导向
Navigator.of(context).push(new MaterialPageRoute(builder:(context){...}))
✓2.带参数前进导向
就像是props一样直接往方法的括号里面放,但是要注意强类型语言的参数定义与传递跟js略有不同。
方式一:
// 传参
Navigator.of(context)
.pushNamed('/navigation2', arguments: {"start": start, "end": end});
// 接收参数
var obj = ModalRoute.of(context).settings.arguments;
✓3. 后退
由push构成的一般前进动作,appBar 部分会自带后退按钮,无需自定义
✓4.带参数后退(或者带参数伪后退)
但是只能带一个参数回来
✓5.不可逆前进(如登陆后、若不注销账户则不再能退到登陆页)
//从登录到主页
Navigator.of(context).pushReplacementNamed('/home');
//一般前进
Navigator.of(context).pushNamed("/page1");
// 退出登录
while (Navigator.of(context).canPop()) {
Navigator.of(context).pop();
}
Navigator.of(context).pushReplacementNamed("/login");
✓6.底部菜单式目录
直接在 scaffold 的 bottomNavigationBar 接口处使用 BottomNavigationBar 组件
- 组件拆分:props传递与state管理
class TopBar extends StatelessWidget with PreferredSizeWidget {
/**********************关键代码**********************/
final title;
TopBar({@required this.title});
/**********************关键代码**********************/
@override
Widget build(BuildContext context) {
return AppBar(title: Text('${this.title}'));
}
@override
Size get preferredSize => Size.fromHeight(kToolbarHeight);
}
外部调用
TopBar(title: 'HomePage Title'),
-
基本样式掌控
✓1.沉浸式状态栏
好像 ios app 默认都是沉浸式无需特殊设置,而网上大部分沉浸式状态栏都是在针对安卓 app。
✓2.自定义顶部条高度、颜色、字体;后退按键颜色
✓3.自定义底部条高度、颜色、字体 -
创建外接式html
简单粗暴,没有问题。
h5 与 flutter 的交互实现:
h5 => flutter:
理论:在 flutter 端注册 javascriptChannels ,然后监听特定 name 的回传再进行后续动作。
flutter 端:
// 创建 JavascriptChannel
JavascriptChannel _toastJsChannel(BuildContext context) => JavascriptChannel(
name: 'show_flutter_toast',
onMessageReceived: (JavascriptMessage message) {
print("get message from JS, message is: ${message.message}");
// _webViewController.evaluateJavascript('zbchell("rtzl hasaki")');
});
@override
Widget build(BuildContext context) {
return WebView(
...,
javascriptChannels: <JavascriptChannel>[
_toastJsChannel(context),
].toSet(),
);
}
JS 端:
if(window.show_flutter_toast)
show_flutter_toast.postMessage(`someString`)
flutter => h5
理论:flutter 通过 webviewcontroller 的 evaluateJavascript 方法调起 web 端 window 名下的方法。
JS端:
// 注册可以被调起的函数
window.zbchell = function (text) {
...,
// setState
this.setState({
... // if u need
})
}
注意不要使用箭头函数声明,以免之后绑定 this 失败,除非你的这个方法当中并不需要使用特殊指向的 this。
flutter 端:
// 创建 JavascriptChannel
JavascriptChannel _toastJsChannel(BuildContext context) => JavascriptChannel(
name: 'show_flutter_toast',
onMessageReceived: (JavascriptMessage message) {
print("get message from JS, message is: ${message.message}");
// 执行!!
_webViewController.evaluateJavascript('zbchell("rtzl hasaki")');
});
-
创建内嵌式html文件与互相通信、内嵌html且外接css
1.本地html文件读取运行
2.本地html与 flutter 互相通信
暂且免谈
症状:只能渲染本地的 html 文件,而无法加载 html 对 css 和 js 文件的引入
解决思路:
a.整个网页全部外接(部署在服务器上)
结论:简单粗暴,没有问题。
b.将所有文件打包至一个 .html 文件当中(付诸实践...)
结论:大图片等外部资源只要有引入,则一概无法衔接(暂时废弃)
至此,套壳应用已理论上可实现。
接下来的内容为边做边研究
目的:开发一款包含截至目前所有组件的 rtzl 的开发者 app。
〇、ALL OUT
1.修改 app 桌面图标:
有很多尺寸的图标(共15个不同命名的图标,尺寸可能有重叠部分),由 Contents.json 管理配置。
2.修改 app 桌面名称:
到 info.plist 当中修改。(虽然 flutter 号称跨平台框架,然而这部分 ios android 各自为政)。
3.设置 app 启动画面:
有几个尺寸命名,由 Contents.json 管理配置。不过这里也可以使 app 无视屏幕尺寸大小,统一使用一个图片。
一、核心技术代码
1.等价于 React 的 componentDidMount 的方法:
需要使用 statefulWidgets 来实现
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _didMount(context));
}
// 这里就是自定义的在 build 被调用完之后触发动作的地方
_didMount(context) {
Navigator.of(context).pushNamed("/choose-entry");
}
2.权限申请
正规的 app 操作统一为在使用时询问用户是否予以功能使用授权,特殊页面用到时动态申请或者 app 第一次运行时申请。
iOS 与 Android 是一样的步骤:
Android 需要在 AndroidMenifest.xml 文件当中声明,然后再在运行时代码中写入申请动作;
iOS 则是在 info.plist 当中先行注册,然后再在运行时代码中写入申请动作。
全权限注册参照表
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>permission_handler_example</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<!-- Permission options for the `location` group -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>Need location when in use</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Always and when in use!</string>
<key>NSLocationUsageDescription</key>
<string>Older devices need location.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Can I have location always?</string>
<!-- Permission options for the `mediaLibrary` group -->
<key>NSAppleMusicUsageDescription</key>
<string>Music!</string>
<key>kTCCServiceMediaLibrary</key>
<string>media</string>
<!-- Permission options for the `calendar` group -->
<key>NSCalendarsUsageDescription</key>
<string>Calendars</string>
<!-- Permission options for the `camera` group -->
<key>NSCameraUsageDescription</key>
<string>camera</string>
<!-- Permission options for the `contacts` group -->
<key>NSContactsUsageDescription</key>
<string>contacts</string>
<!-- Permission options for the `microphone` group -->
<key>NSMicrophoneUsageDescription</key>
<string>microphone</string>
<!-- Permission options for the `speech` group -->
<key>NSSpeechRecognitionUsageDescription</key>
<string>speech</string>
<!-- Permission options for the `sensors` group -->
<key>NSMotionUsageDescription</key>
<string>motion</string>
<!-- Permission options for the `photos` group -->
<key>NSPhotoLibraryUsageDescription</key>
<string>photos</string>
<!-- Permission options for the `reminder` group -->
<key>NSRemindersUsageDescription</key>
<string>reminders</string>
</dict>
</plist>
权限申请(.request())时,都是隐式检测如果已授权,则什么都不发生,如果未授权才会提示申请权限,当你需要在有/无权限时分别有其他动作的时候才会用得到权限状态判定。
3.系统提示框调用
目前没有任何方法可以调用出原生的提示框,只能使用flutter提供的Widgets实现。初步分析可能涉及到 flutter 调用原生接口的过程。
4.本地数据存储与调用
任务一:开发一个网站显示页的前置配置页(用于配置访问地址)
5.使iOS应用允许http不安全通讯请求
info.plist 里面直接添加
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>
5.在Mac上部署flutter android开发环境时,中文未提到的细节
- 如果报错,可能是使用的java版本有问题,找了好久才知道是该使用 java 8 ,不要搞错了版本。
- 设置java环境变量,随便找个位置
export JAVA_HOME=`/usr/libexec/java_home -v 1.8.0_241`
注意尾部的版本号,可能与文中有所不同
网友评论