新项目实施步骤

[toc]

新项目实施步骤

一、建立项目工程(0.5h)

1、工程配置

1.1、pod处理–Podfile

1
2
3
4
5
pod 'SnapKit'
pod 'Masonry'
pod 'CJBaseUIKit'
pod 'CJFoundation'
pod 'CJBaseHelper'

1.2、混编处理

OC+Swift混编问题.md

①、OC 调用 Swift

1、建立XXX-Swift.h

附:如果你是OC工程中第一次创建swift文件,则系统会在自动帮你搭建XXX-Bridging-Header.h的同时,为你生成该文件。即那种情况下,此步可略过

2、Defines Module 设置为YES (对Project操作,非Targets)

image-20201106181320734

3、oc 文件中 #import “XXX-Swift.h”

②、Swift 调用 OC

XXX-Bridging-Header.h

2、工程入口

2.1、入口首页:方便他人进入开发

二、架构设计

1、架构文件管理

  • AppDelegate

    AppDelegate+StartUp.swift

  • CommonUI

  • CommonUtil
  • Service
  • Module
  • Resources

三、功能模块规划

1、登录模块 Page5 24h

1.1、登录模块

Page 5 24h

  • 登录的首页
    • 隐私政策弹窗页
  • 登录方式选择页面
    • 本机号码获取
    • 第三方登录(微信、)
  • 登录的手机号码输入页
  • 登录的手机验证码输入页

1.2、登录后的信息完善

Page

  • 欢迎页 -> 内容介绍页1 -> 内容介绍页2
  • 个人信息完善
    • 个人性别 + 交往喜好(性别+年龄)

1.3、信息完善后的使用引导

Page3 1d

2、主页模块 Page1 4h

2.1、主页框架 Page1 4h

Page1 4h

3、发现模块

Page 17 = 11 + 6 + 2

h 40 = 28 + 8 + 4

3.1、更好使用的信息完善-表 Page11 28h+

涉及的组件开发 8

  • 完善进度组件 2
  • 照片弹窗组件 6
  • 照片列表组件 3

涉及的页面开发(不含请求)20+

  • 昵称完善页 1

  • 生日完善页(日期选择 + 年龄 + 星座) 4

  • 身高完善页 1

  • 家乡完善页 4

  • 学校完善页

  • 职业完善页(选一选+自定义) 4+2

  • 日常完善页

  • 愿望完善页

  • 照片完善页 1

3.2、更好使用的信息完善-里 Page6 8h

3.3、更好使用的信息完善-匹配偏好 Page2 4h

4、我的模块

4.1、我的首页

4.2、资料信息-表

涉及的组件开发 2

  • 完善进度组件 2
  • 照片弹窗组件 已在上述完成
  • 照片列表组件 已在上述完成

涉及的页面开发(不含请求)

  • 表资料 2*8
  • 照片

其他小工具

[toc]

一、截图

二、翻译

  • Bob

  • 沉浸式翻译 immersivetranslate.com

    沉浸式翻译: 双语对照网页翻译 & PDF文档翻译

    沉浸式网页双语翻译扩展,支持PDF翻译,双语Epub电子书制作,Youtube/Netflix/Udemy 等平台双语字幕,支持Deepl/Google等多个翻译

三、投屏(与控制)、录屏

1、投屏(与控制)

参考文章:iphone有线投屏和无线投屏到macbook|电脑上操控手机

应用名 下载地址 好用程度 在电脑上用鼠标操作投屏的ipone
AirServer 最好用
虫洞 https://er.run/ 付费软件(高版本蓝牙有bug)
iMyFone MirrorTo https://www.imyfone.com/screen-mirror/ 付费软件($59.99)
iMyFone MirrorTo 使用简介

2、录屏

五款好用的Mac录屏软件推荐

四、虚拟定位

应用名 应用版本 存放于
AnyGo AnyGo for Mac 7.0.0激活版 百度网盘

聊聊虚拟定位工具新宠儿:AnyGo的原理与识别

macOS 打开软件提示“已损坏”怎么解决?无法确认开发者身份解决方案

六、媒体的转换与合成

1、在线音频视频转换器(格式转化)

https://www.media.io/zh/

在线音频视频转换器

点击下载:

在线音频视频转换器2

2、Mac使用imovie合成音频和视频

Mac使用imovie合成音频和视频

七、免会员、免登录等

1、code-box : 免登录一键复制代码

以访问 UITableView 滑动到最后一个cell自动加载下一页的数据 为例,其需要会员才能解锁文章全部内容。

code-box

其他

第2节:Flutter控件Widget的自定义与封装

[TOC]

前言

如果你不是没有任何开发经验,那么你一定知道任何app里都有可能有重用性比较高的控件。所以对于那么重用性比较高的,或者需要你自定义的控件的,我们需要将它们给封装起来,以便下次或者其他app中继续使用。这也正式本节想要说的内容Flutter中如何封装Widget。

下面我从自己实现一个满意的封装,分别介绍你可能用到的三种封装方式

  • 1、函数式封装
  • 2、以继承 StatefulWidget 的方式封装
  • 3、继承父类式封装(推荐)

下面我们以登录页的文本框的自定义来谈封装。

用户名登录的UI图

一、函数式封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/// 蓝色背景按钮(常用于:登录按钮)
/// 方法1:以函数的方法实现
FlatButton blueButton(String text, bool enable, VoidCallback enableOnPressed) {
return FlatButton(
child: Text(text),
splashColor: Colors.transparent,
color: Color(0xff01adfe),
textColor: Colors.white,
highlightColor: Color(0xff1393d7),
disabledColor: Color(0xffd3d3d5),
disabledTextColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5.0)
),
//onPressed: enable ? enableOnPressed : null,
onPressed: enable ? () {
enableOnPressed();
} : null,
);
}

乍看没什么问题,好像很简洁。但当你也用这种方式来实现文本框的时候,其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/// 文本框(常用于:登录用户名、密码文本框)
/// 方法1:以函数的方法实现
TextField loginTextField(String placeholder, String prefixIconImageName, ValueChanged<String> onSubmitted) {
return TextField(
//autofocus: shouldAutofocusUserNameTextField,
style: TextStyle(color: Colors.black, fontSize: 17.0),
decoration: InputDecoration(
contentPadding: EdgeInsets.all(0.0),
//labelText: "用户名",
hintText: placeholder,
//prefixIcon: Icon(Icons.person),
prefixIcon: new Image.asset(
prefixIconImageName,
width: 14.0,
height: 15.0,
),
enabledBorder: loginTextFieldDecorationBorder(),
focusedBorder: loginTextFieldDecorationBorder(),
),
// keyboardType: TextInputType.text,
// controller: _usernameController,
// textInputAction: TextInputAction.next,
// focusNode: usernameFocusNode,
// onSubmitted: (text) {
// print("current userName:" + text);
// if (null == currentFocusNode) {
// currentFocusNode = FocusScope.of(context);
// }
// currentFocusNode.requestFocus(passwordFocusNode);
// }
onSubmitted: onSubmitted,
);
}

// 文本框border
InputBorder loginTextFieldDecorationBorder() {
return new OutlineInputBorder(
borderSide: new BorderSide(color: Color(0xffd2d2d2), width: 0.6),
borderRadius: new BorderRadius.circular(6.0)
);
}

可见这种函数的方式,没办法处理过多属性的自定义。因为它并不像我们iOS中的UIView,可以对得到的控件在后续再定制。所以,在Flutter中这种函数式的封装不适合,因为它无法满足使用。

附:以下是iOS中的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (CJTextField *)userNameTextField {
if (_userNameTextField == nil) {
UIImage *normalImage = [UIImage imageNamed:@"login_username_gray"];
UIImage *selectedImage = [UIImage imageNamed:@"login_username_blue"];
_userNameTextField = [CJDemoTextFieldFactory textFieldWithNormalImage:normalImage selectedImage:selectedImage];
_userNameTextField.placeholder = NSLocalizedString(@"用户名", nil);
_userNameTextField.returnKeyType = UIReturnKeyNext;
_userNameTextField.clearButtonMode = UITextFieldViewModeWhileEditing;
_userNameTextField.delegate = self;
}
return _userNameTextField;
}

- (UIButton *)loginButton {
if (_loginButton == nil) {
_loginButton = [CJDemoButtonFactory blueButton];
[_loginButton setTitle:NSLocalizedString(@"登录", nil) forState:UIControlStateNormal];
_loginButton.enabled = NO;
[_loginButton addTarget:self action:@selector(loginButtonAction) forControlEvents:UIControlEventTouchUpInside];
}
return _loginButton;
}

二、以继承 StatefulWidget 的方式封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/// 文本框(常用于:登录用户名、密码文本框)
/// 方法2:以继承 StatefulWidget 的方式实现
class LoginTextField extends StatefulWidget {
final String placeholder;
final String prefixIconImageName;
final bool autofocus;
final TextEditingController controller;
final TextInputAction textInputAction;
final FocusNode focusNode;
final ValueChanged<String> onSubmitted;
final TextInputType keyboardType;

LoginTextField({
Key key,
this.placeholder,
this.prefixIconImageName,
this.autofocus,
this.keyboardType,
this.controller,
this.textInputAction,
this.focusNode,
this.onSubmitted

}) : super(key: key);

@override
_LoginTextFieldState createState() => _LoginTextFieldState();
}

class _LoginTextFieldState extends State<LoginTextField> {
@override
Widget build(BuildContext context) {
return Container(
child: TextField(
autofocus: widget.autofocus,
style: TextStyle(color: Colors.black, fontSize: 17.0),
decoration: InputDecoration(
contentPadding: EdgeInsets.all(0.0),
//labelText: "用户名",
hintText: widget.placeholder,
//prefixIcon: Icon(Icons.person),
prefixIcon: new Image.asset(
widget.prefixIconImageName,
width: 14.0,
height: 15.0,
),
enabledBorder: loginTextFieldDecorationBorder(),
focusedBorder: loginTextFieldDecorationBorder(),
),
keyboardType: widget.keyboardType,
controller: widget.controller,
textInputAction: widget.textInputAction,
focusNode: widget.focusNode,
onSubmitted: widget.onSubmitted
),
);
}
}

// 文本框border
InputBorder loginTextFieldDecorationBorder() {
return new OutlineInputBorder(
borderSide: new BorderSide(color: Color(0xffd2d2d2), width: 0.6),
borderRadius: new BorderRadius.circular(6.0)
);
}

使用的时候:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
TextField userNameTextField() {
return TextField(
autofocus: shouldAutofocusUserNameTextField,
style: TextStyle(color: Colors.black, fontSize: 17.0),
decoration: InputDecoration(
contentPadding: EdgeInsets.all(0.0),
//labelText: "用户名",
hintText: "用户名",
//prefixIcon: Icon(Icons.person),
prefixIcon: new Image.asset(
userNameValid ? 'lib/Resources/login/login_username_blue.png' : 'lib/Resources/login/login_username_gray.png',
width: 14.0,
height: 15.0,
),
enabledBorder: loginTextFieldDecorationBorder(),
focusedBorder: loginTextFieldDecorationBorder(),
),
keyboardType: TextInputType.text,
controller: _usernameController,
textInputAction: TextInputAction.next,
focusNode: usernameFocusNode, //usernameFocusNode
onSubmitted: (text) {
print("current userName:" + text);
if (null == currentFocusNode) {
currentFocusNode = FocusScope.of(context);
}
currentFocusNode.requestFocus(passwordFocusNode);
}
);
}

虽然使用上看似没什么问题,但是整个TextField的继承代码难道你不觉得有更简洁的写法吗?

所以下面将讲解直接继承TextFiled的方法。

三、继承父类式封装(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/// 文本框(常用于:登录用户名、密码文本框)
/// 方法3:以继承 TextField 的方式实现
class LoginTextField extends TextField {
LoginTextField({
Key key,
String text,

String placeholder,

/// prefix icon
bool prefixIconSelected,
String prefixIconNormalImageName,
String prefixIconSelectedImageName,

bool autofocus = false,
bool obscureText = false,
TextInputType keyboardType,
TextEditingController controller,
bool showClear = false,
TextInputAction textInputAction,
FocusNode focusNode,
ValueChanged<String> onSubmitted,
}) : super(
key: key,
autofocus: autofocus,
obscureText: obscureText,
style: TextStyle(color: Colors.black, fontSize: 17.0),
decoration: InputDecoration(
contentPadding: EdgeInsets.all(0.0),
//labelText: "用户名",
hintText: placeholder,
//prefixIcon: Icon(Icons.person),
prefixIcon: new Image.asset(
!prefixIconSelected ? prefixIconNormalImageName :prefixIconSelectedImageName,
width: 14.0,
height: 15.0,
),
suffixIcon: !showClear ? null : clearButtonWithOnPressed(controller.clear),
enabledBorder: loginTextFieldDecorationBorder(),
focusedBorder: loginTextFieldDecorationBorder(),
),
keyboardType: keyboardType,
controller: controller,
textInputAction: textInputAction,
focusNode: focusNode,
onSubmitted: onSubmitted
);
}

/// selected Image
class SelectedImage extends Image {
SelectedImage({
Key key,
bool selected,
String normalImageName,
String selectedImageName,
}) :super (
key: key,
image: AssetImage(!selected ? normalImageName :selectedImageName)
);
}


/// 文本框border
InputBorder loginTextFieldDecorationBorder() {
return new OutlineInputBorder(
borderSide: new BorderSide(color: Color(0xffd2d2d2), width: 0.6),
borderRadius: new BorderRadius.circular(6.0)
);
}

使用时候

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 用户名文本框
LoginTextField userNameTextField() {
return LoginTextField(
placeholder: "用户名",
prefixIconSelected: userNameValid,
prefixIconNormalImageName: 'assets/images/login/login_username_gray.png',
prefixIconSelectedImageName: 'assets/images/login/login_username_blue.png',
autofocus: shouldAutofocusUserNameTextField,
keyboardType: TextInputType.text,
controller: _usernameController,
textInputAction: TextInputAction.next,
focusNode: usernameFocusNode,
onSubmitted: (text) {
print("current userName:" + text);
if (null == currentFocusNode) {
currentFocusNode = FocusScope.of(context);
}
currentFocusNode.requestFocus(passwordFocusNode);
});
}

可见使用继承父类式封装这种方式,不管在封装时候,还是在使用时候,写的代码都是最简洁的。而且后期如果要直接使用系统样式,也只需要改回类名,其他结构和属性都不用动即可

四、强调自定义类的设计规范

在前面,我们已经知道使用继承父类式封装这种方式,不管在封装时候,还是在使用时候,写的代码都是最简洁的。而且后期如果要直接使用系统样式,也只需要改回类名,其他结构和属性都不用动即可

所以,即使是你所定义的类只有一个入参,也一定要遵守使用继承父类式封装的设计规范。

以下以按钮中 textStyle 的传值为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
import 'package:flutter_baseui_kit/flutter_baseui_kit.dart';

ThemeBGButton(
//width: 300, // 不设置会根据内容自适应
//height: 80, // 不设置会根据内容自适应
bgColorType: ThemeBGType.pink,
title: '红底白字的按钮',
//titleStyle: ButtonThemeUtil.PingFang_FontSize_Bold(18.0), // bad
titleStyle: ButtonBoldTextStyle(fontSize: 18.0), // good
cornerRadius: 20,
//enable: true, // 不设置,默认true
onPressed: () {},
),

附:bad 和 good 两种实现方式的代码分别如下:

bad:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'package:flutter/material.dart';

// 按钮上的文本样式(按钮上的文字颜色,已通过其他属性设置;不需要 TextStyle 中设置;其他类的文本需要在 TextStyle 设置文本颜色,所以此类最多只提供给按钮使用)
class ButtonThemeUtil {
// 类命名注意:系统有 ButtonTheme 类,别取重名,否则外部取不到

static TextStyle PingFang_FontSize_Medium(double fontSize) {
return TextStyle(
fontFamily: 'PingFang SC',
fontSize: fontSize,
fontWeight: FontWeight.w500,
);
}
}

good:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import 'package:flutter/material.dart';

// medium 的文本样式
class ButtonMediumTextStyle extends TextStyle {
final double fontSize;
// final Color color;

ButtonMediumTextStyle({
@required this.fontSize,
// this.color,
}) : assert(fontSize != null),
// assert(color != null),
super(
fontFamily: 'PingFang SC',
fontSize: fontSize,
fontWeight: FontWeight.w500,
// color: color,
);
}

所以,综上在Flutter中对于一个Widget的封装,我们采用直接继承其父类的方式来处理,且其具体的写法如上。

End

从登录页进入Flutter的开发规范

[TOC]

前言

良好的规范让你的代码不仅看上来更简洁,也让你的代码更加具有可读性,及大大的减少了后期维护的成本。

以下从几个方面谈谈我对Flutter良好的规范的理解。

一、页面Page规范前后效果对比

我们先从页面开始(以登录页的优化为例),然后逐步的从点到面铺开描述。

目标:页面布局、控件定义、事件处理分离。

1、演示的Page的UI图

用户名登录的UI图

2、规范前的效果

规范前:未进行任何处理的时候,你的代码是这样的。

LoginPage_widgetUnsplit

3、规范后的效果

规范后**:按规范处理后你代码的效果是这样的,

LoginPage_widgetSplit

可见通过处理后Page的整体代码更加简洁了。

二、页面Page开发规范的细则

1、规范一:将每个Widget的定义整理到单独的函数里

写:

Flutter规范_Widget_right

不写:

Flutter规范_Widget_wrong

2、规范二:将各个Widget组成界面的布局放到widgets()方法里处理

写法如下:

Flutter规范_Widgets布局_right

如此通过以上两步后,Widget build中的代码即会变为如下:

Flutter规范_WidgetBuild_right

如此通过以上几个拆分,我们的Page页面就显得很简洁和很好维护了。

3、规范三、Widget的封装

细心的你,可能发现了我们这里有一个系统没有的LoginTextField文本框类。是的,该类是我们自定义封装到Package后来使用的。

对于一个控件怎么封装,详情查看之前的文章《Flutter进阶(2):控件Widget的自定义与封装》

三、页面Page规范小结

  • 1、Widget build方法

build方法里尽量用最少的代码实现整体视图。如下,我们将登录页中的所有Widget通过loginWidgets()函数封装起来。

  • 2、各个Widget组成的视图布局

loginWidgets()方法里实现页面Widgets的布局。而对于Widget的定义为了不让页面布局控件定义因写在一堆,而造成代码一大坨。我们对每个Widget单独提供定义的函数,如userNameTextField()即为定义用户文本框的函数。

  • 3、抽离每个Widget的定义

  • 4、封装单个Widget

四、业务逻辑的开发规范

说完了页面上的Widgets的开发规范,下面就轮到页面上的业务逻辑的开发规范了。

拿登录页需要获取上次登录的账号来说,它的业务是getDefaultLoginAccountAction,下面以混编时候的项目为例

1、未去解耦业务时候的登录页

getDefaultLoginAccountAction在Page中的实现一般为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// LoginPage.dart 中的业务代码
static const callNativeMethodChannel = const MethodChannel('com.dvlproad.ciyouzen/callNativeLoginMethodChannel');
Future<Null> _getDefaultLoginAccountAction() async {
try {
final Map nativeResponse = await callNativeMethodChannel.invokeMethod('getDefaultLoginAccountAction');
final Map nativeResult = callNativeNativeResult(nativeResponse);

setState(() {
userName = nativeResult["userName"];
password = nativeResult["password"];
print(userName + ":" + password);
_usernameController.text = userName;
_passwordController.text = password;
});
} on PlatformException {}
}

相应的它的调用为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/// LoginPage.dart 未去解耦时候的登录页
class MyLoginPage extends StatefulWidget {
MyLoginPage({Key key, this.title}) : super(key: key);

final String title;

@override
_MyLoginPageState createState() => _MyLoginPageState();
}

class _MyLoginPageState extends State<MyLoginPage> {
@override
void initState() {
// TODO: implement initState
super.initState();
print("Login Page initState");

_getDefaultLoginAccountAction();
}

// other things
}

可见上述的业务处理getDefaultLoginAccountAction中,掺杂了对页面Page的setState操作,这不适合我们以后只对业务或者只对页面做修改的处理。所以下面我们将它们解耦。

2、解耦业务时候的登录页

Flutter的ViewModel

getDefaultLoginAccountAction在ChannelModel中的实现一般为:

1
2
3
4
5
6
7
8
9
10
11
/// LoginChannelModel.dart 文件
Future<Map<String, dynamic>> getDefaultLoginAccountAction() async {
try {
final Map nativeResponse = await callNativeMethodChannel.invokeMethod('getDefaultLoginAccountAction');
final Map nativeResult = callNativeNativeResult(nativeResponse);

return nativeResult;
} on PlatformException {
return null;
}
}

相应的它的调用为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/// LoginPage.dart 解耦时候的登录页
class MyLoginPage extends StatefulWidget {
MyLoginPage({Key key, this.title}) : super(key: key);

final String title;

@override
_MyLoginPageState createState() => _MyLoginPageState();
}

class _MyLoginPageState extends State<MyLoginPage> {
@override
void initState() {
// TODO: implement initState
super.initState();
print("Login Page initState");

getDefaultLoginAccountAction().then((nativeResult){
setState(() {
userName = nativeResult["userName"];
password = nativeResult["password"];
print(userName + ":" + password);
_usernameController.text = userName;
_passwordController.text = password;
});
});
}

// other things
}

五、Flutter Channel的数据规范

在使用Flutter与原生项目混编的时候,因为涉及到数据传输,所以我们有必要在前期就对传输数据的进行规范统一。

MySQL问题整理

一、MySQL的下载安装

1、进入官网MySQL地址:https://dev.mysql.com/downloads/mysql/ 以下载安装包

MySQL安装1

2、选择适合您系统的指定MySQL版本,下载安装

MySQL安装2

3、MySQL安装成功后的截图如下:

MySQL安装成功

二、终端操作MySQL的问题

1、终端 -bash: mysql: command not found

遇上-bash: mysql: command not found的情况别着急,这个是因为/usr/local/bin目录下缺失mysql导致,只需要一下方法建立软链接,即可以解决:把mysql安装目录,映射到/usr/local/bin目录下即可,即将本要链接到/usr/bin下的改链接到/usr/local/bin下就好了。命令如下:

sudo ln -s /usr/local/mysql/bin/mysql /usr/local/bin

MySQL -bash- mysql- command not found

第1节:iOS项目集成Flutter高级

[TOC]

前言

要想知道怎么去高级的进行混编,那么久需要你首先对官网的混编方式进行分析。所以,下面我们先对官网的混编方式进行分析,然后在进行正式的高级混编。

一、官网的混编方式分析

1、Podfile

1
2
3
#Flutter项目路径
flutter_application_path = "../../cj_nativeflutter_fluttermodule"
eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)

2、podhelper.rb

2.1、podhelper.rb的位置

podhelper.rb在flutter SDK中的路径为:/Applications/flutter/packages/flutter_tools/templates/module/ios/library/Flutter.tmpl

flutter_tools

Flutter SDK中的podhelper.rb

在项目中的路径为:

项目中的podhelper.rb

2.2、podhelper.rb处理的事情

podhelper.rb的内容1

事情①:获取Flutter.podspecFlutterPluginRegistrant.podspec,然后pod

获取Flutter.podspec

image-20190310171127737

获取FlutterPluginRegistrant.podspec

image-20190310171011325

情况1问题:如果engine文件夹不存在,即不存在我们需要的Flutter.podspecFlutter.framework

如果engine文件夹不存在,即不存在我们需要的Flutter.podspecFlutter.framework,则我们需要从flutter sdk中的如下目录拷贝Flutter.podspecFlutter.framework,那么Flutter SDK的根目录从哪里找呢?

/Applications/flutter/bin/cache/artifacts/engine/ios/

image-20190310174505419

情况1解决:获取flutter sdk的根目录flutter_root_dir

而这个flutter sdk目录flutter_root_dir的获取,我们只需要从根据flutter_application_path路径下的Generated.xcconfig文件中的FLUTTER_ROOT获取即可。

image-20190310181117074

获取的方法为:

1
flutter_root_dir = flutter_root(flutter_application_path)

该flutter_root函数的方法为:

podhelper.rb的内容2

Generated.xcconfig数组的获取方法为:

podhelper.rb的内容3

3、xcode_backend.sh

3.1、xcode_backend.sh的位置

xcode_backend.sh的目录为/Applications/flutter/packages/flutter_tools/bin/

Flutter SDK中的xcode_backend.sh