距离上篇文章过去了一个半月了,主要双端都开发完毕,不能继续带薪学习了,需要干点正事,让这么久的辛勤成果发挥它应有的价值-上架。Android的需要上架各个应用商店,iOS的上架App Store,第一次干这活可比攻坚技术还让人心力交瘁,一把心酸泪在心里流淌,光吐槽都能再水2000个字。这么长时间也不能光忙活个上架的事,协助同事完善了一下公司的后台管理系统,还还开发了另一款相机插件,那是后话了。
经过了Android端的开发,我们已经把插件的基本功能全部摸清楚,在项目中也正常的跑通了,按道理来讲,现在只需要了解一下双端开发差异,将kotlin的代码转换为swift端的代码,还有这些功能涉及到的权限申请重新在iOS端复现一遍即可。那岂不是看一遍文档,了解一下开发差异,再给我三天就搞定?想到这就笑出了声。
但当实际上手的时候,还是发现自己Too young, too simple。开发者账号要准备,必须使用的xcode编辑器要下载,xcode的文件目录完全看不懂,官方文档搜索功能也不会用,想打包发给同事测试还要先交100刀乐,以及其他种种问题。上一次这么难受的时候还是第一次用iPhone,用了一个月,还是回归安卓,作为用户还可以选择不用,现在但作为开发者,不用也得用。
最让人想吐槽的就是,其他编程语言的保留字和报错描述都用人们熟悉的单词,只有苹果非要发明一套,不反逻辑,但就是要反常识。
还有更蛋疼的,就是感觉iOS的文章特别的少,免费课都是落后的知识,付费的系统课时间太长了,没时间去学习,而且冗余的知识也用不上。
吐槽完毕,进入正题。
一、xcode文件目录
还是右键项目目录,鼠标放在Flutter
选项上,再点击子选项open iOS module in Xcode
,即可看到ios的代码文件。
目录 | 文件夹 | 包含功能描述 |
---|---|---|
Runner | Flutter | xcconfig项目配置文件 |
Runner | 主程序代码编写文件,权限配置文件 | |
Products | app运行程序,不能直接运行 | |
Pods | 第三方插件配置文件 | |
Frameworks | Pods的库 | |
Pods | Podfile(文件) | Pods依赖关系说明文件 |
Development Pods | 插件开发写代码的地方 | |
Frameworks | 保存官方提供的插件 | |
Products | 插件自身也是一个freamwork | |
Targets Support Files | Pods的一些配置文件 |
(上面的目录只有几个我们用的到,而且描述都非常不准确,都是按照自己理解写的)
这其中Runner的部分是在根目录/example/ios
中,一些项目的配置需要在这里修改,真正跑起来的也是这个,Pods是在根目录/ios
中的,这里才是我们主要编写方法的地方。
二、面向未来swift
知道了在哪写,该学习怎么写了。
面向相同用途的语言都会有不少类似的地方,学了Android的开发语言,再学习iOS的,就会发现有很多概念相同的地方,这也能减轻我们的心智负担。
众所周知,如同另一平台一样,iOS平台也有2种语言可以选择,老牌的object-c
和新生代的swift
。任何语言都是越老越稳定,资料多,资源多,但缺少现代化语言的功能;而新的语言总是有不稳定,版本更新变化大,资料少的问题,也有语法精炼,有不少好用的现代化功能。而在今天看来,swift已经到了第7个年头,也到了第五个大版本,不稳定的问题也微乎其微了,最关键相对于前者同样的功能代码量少,易于理解,毕竟代码主要是给人看的。
那么,再根据买手机配电脑的经验,出来吧,SWIFT!!!
1、数据类型
当然还是先看数据类型,上篇文章中讲到,在dart中会使用null
、bool
、int
、String
、Map
以及Uint8List
这几种类型,还是在看这个表:
其他的数据都行想必都很熟悉了,在这里也是一样,了解不同数据类型的方法。主要注意一下TypedData
,需要传递这种数据类型到flutter时,需要用FlutterStandrdTypedData
进行包装一下。
2、基础语法
我们这个项目用的语法也不多,关键学习一下我们用到的。顺便说一句,为何不选OC,主要因为dart、kotlin、swift这三种语言有个最大的共通处,都是带有null salfty
机制的语言,更符合我们的思维习惯。
变量和常量
任何语言的第一步就是声明变量。
- 常量和变量必须在使用前声明。
- 使用 var 来声明变量,值可读可写。
- let 来声明常量,只能在声明的时候确定.
- 声明的时候可以标明值的类型,也可以自动判断并不可更改类型。
//声明一个常量
let TAG:String = "plugin"
//变量,可重新赋值
var status: NSNumber = 0
//可为空的变量,稍后赋值
var img:UIImage?
流程控制
swift有for in循环, while循环,if条件判断,switch 判断,三元运算。
代码语言:javascript复制// for in 循环,同样可以循环字典获得键和值
let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
print("Hello, (name)!")
}
// while 循环
var index = 10
while index < 20 {
print( "index 的值为 (index)")
index = index 1
}
//if判断
if index > 20 {
print( "index 的值大于20")
} else {
print( "index 的值小于20")
}
// switch判断
switch index {
case 100 :
print( "index 的值为 100")
case 10,15 :
print( "index 的值为 10 或 15")
case 5 :
print( "index 的值为 5")
default :
print( "默认 case")
}
//三元运算
index > 20 ? print( "index 的值大于20") : print( "index 的值小于20")
函数和表达式
使用func来声明一个函数,对传递的参数及返回的值可以声明类型。
代码语言:javascript复制//无返回值,返回值类型可以省略
func getInfo(name: String, site: String) -> String {
return name site
}
闭包
闭包的作用和其他的语言的 Lambda 表达式有点相似,但在这里属实有点抽象,可以查看这篇文章swift中的闭包。
闭包理解起来比较困难,但我们只需要知道他是如何声明和实现的就好,通过案例,可以简单的理解为使用{}
加in
关键字,可以使用闭包的参数。
// 闭包的声明
var closure: ((String, Int) -> String)!
// 闭包的实现
closure = { (name, age) in
return "(name)是(age)岁"
}
class类
所有编程语言的类都大同小异,我们可以为类定义属性和方法,在类的内部可以通过self
关键字调用自身的方法和熟悉,而且swift会自动生成面向其它代码的外部接口。
class Marks {
var mark: Int
init(mark: Int) {
selfmark = mark
}
func suffix(mark: Int) -> String{
return "(mark)分"
}
func getSuffix() -> String {
return selfsuffix(mark: selfmark)
}
}
let m1 = Marks(mark: 100)
print(m1.getSuffix())
//打印:100分
print(m1.suffix(50))
// 打印:50分
虽然和kt的语法大同小异,但很多概念还是很抽象的。当然开始学习的时候,不需要了解那些概念,只需要把他当成一个黑盒,写法不变的情况下,输入什么参数就能输出预期的结果就够了。
三、功能实现
这里的流程依然和安卓端的非常类似,无外乎导入插件
-执行插件的方法
。但不同平台最大的差异不在代码编写上,反而在项目配置,目录结构,插件导入等开发支出工作上。
1、pods了解
在上面的文件目录介绍中,会发现里面有2个跟目录,一个Runner
,一个是pods
,前者是主项目文件,项目运行起来全是依靠他,而后者就是所谓的Cocoapods
,iOS项目的依赖管理工具,类似于Android的gradle,flutter的pubspec,还有前端开发的npm。
安装远程依赖
在ios中安装第三方依赖可以像flutter一样,直接在文件中配置,这里就可以直接在Pods/Podfile
文件中进行配置。
target 'QSAppDemo' do
pod 'AFNetworking'
pod 'YYModel', '~> 1.0.4'
pod 'OOMDetector', '1.3'
# Debug模式下生效
pod 'FLEX', '~> 2.0', :configurations => ['Debug']
pod'WebViewJavascriptBridge',:git=>'https://github.com/marcuswestin/WebViewJavascriptBridge.git'
end
安装本地依赖
这里直接在Development Pods/{项目名}/Pod/{项目名}.podspec
文件中添加如下配置
// 引用框架库
s.vendored_frameworks = "{路径}/{文件名}.framework"
// 引用静态库
s.vendored_libraries = "{路径}/{文件名}.a"
// 引用头文件
s.source_files = "{路径}/**/*"
上面都配置完成后,必须使用pod install
命令来下载第三方库,或者使用pod update
命令来更新配置文件,使用依赖生效。
原生依赖
除了第三方依赖,当然还需要添加官方的原生依赖,依然是在.podspec
后缀的文件中添加如下配置。
// 引用框架库
s.frameworks = "NetworkExtension", "CoreLocation"
// 引用动态库 .lib、tbd ,去掉头尾的lib、tbd
s.libraries = "bz2.1.0.5", "iconv.2.4.0", "z", "c "
获得这里的文件名称,和查看是否配置成功,可以直接点击pods
打开Pods.xcodeproj
的可视化编辑文件。
再点击左侧target
-项目名
打开此项目的配置,在点击Build Phases
-Link Binary With Librarys
查看依赖。
2、导入本地依赖
由于我们的项目中使用的是本地依赖,而且是一个framework,所以我们只需要将此依赖导入到项目中,并配置podspace文件就好。
文件导入
在flutter项目中,打开ios目录,新建Freamwork
文件夹,并将本地的的依赖复制粘贴进来。
pods更新
打开xcode编辑器,再到pods
中的.podspec
文件中添加如下配置
// 引用框架库
s.vendored_frameworks = "Framework/*.framework"
再打开命令行工具,执行pod update
本地依赖不会再
Link Binary With Librarys
中显示,可以直接在编码文件尝试导入,有提示则依赖导入成功。
3、数据交互初始化
开始正式编写业务代码,这里就开始看出kotlin和swift的相似之处
代码语言:javascript复制import Flutter
import UIKit
import CoreLocation
import CoreTelephony
import HZCameraSDK
import Foundation
public class SwiftHzCameraPlugin: NSObject, FlutterPlugin, HZCameraSocketDelegate{
var cllocationManager: CLLocationManager!
var eventSink:FlutterEventSink?
var mPrevivew: HZDisplayView = HZDisplayView()
var img:UIImage?
public static let instance: SwiftHzCameraPlugin = SwiftHzCameraPlugin()
// 数据流监听
class SwiftStreamHandler: NSObject, FlutterStreamHandler {
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
instance.eventSink = events
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
instance.eventSink = nil
return nil
}
}
public static func register(with registrar: FlutterPluginRegistrar) {
// 绑定MethodChannel
let channel = FlutterMethodChannel(name: "hz_camera", binaryMessenger: registrar.messenger())
// 绑定EventChannel
let eventChannel = FlutterEventChannel(name: "HzCamera_event", binaryMessenger: registrar.messenger())
eventChannel.setStreamHandler(SwiftStreamHandler())
registrar.addMethodCallDelegate(instance, channel: channel)
}
}
4、执行方法
安卓和iOS对权限申请和连接硬件有一点差别,但整体是差不多的。
申请权限
代码语言:javascript复制func checkPermission() {
CTCellularData().cellularDataRestrictionDidUpdateNotifier = { (state) in
if state == CTCellularDataRestrictedState.restrictedStateUnknown {
DispatchQueue.main.async {
self.requestWLANAuth()
}
}
else if state == CTCellularDataRestrictedState.restricted {
DispatchQueue.main.async {
self.requestWLANAuth()
}
}
}
}
连接相机
代码语言:javascript复制// 2、连接相机
func connectCamera() {
do {
try HZCameraConnector.default().connectToCameraError()
} catch let error {
self.eventSink!([
"code": 0,
"data": "连接失败",
] as [String: Any])
}
// 连接相机成功后直接初始化
setUpCamera()
}
相机连接状态改变的时候执行
代码语言:javascript复制// 3、相机连接状态改变的时候执行
public func cameraConnectionStateChange(_ state: E_SOCKET_STATE) {
if E_SOCKET_STATE_CONNECTED != state {
NSLog("相机已断开")
} else {
NSLog("相机已连接")
setUpCamera()
}
}
相机初始化,获取相关权限
安卓连接相机只需要进行一次初始化,所以把这个方法放在前面,而iOS每次连接相机都需要初始化。
代码语言:javascript复制// 4、相机初始化
func setUpCamera() {
requestWLANAuth()
HZCameraMedia.default().setupCamera(
completion: {},
fail: {(err) in
},
progress: {(process) in
}
)
}
开始&结束预览
调用预览数据方法,会获取到RGB
图像数据,可以将此数据传递到flutter层进行渲染。
// 5、开始预览
func startPreview() {
mPrevivew.startGetRgbData({ (data: Data, w: Int32, h: Int32) in
self.eventSink!([
"code": 1,
"data": [
"frameData": FlutterStandardTypedData(bytes: data),
"width": w,
"height": h
],
] as [String: Any])
}, fail: { (err) in
NSLog(err.localizedDescription)
})
}
func stopPreview() {
mPrevivew.stop()
}
拍摄照片
代码语言:javascript复制// 6、拍摄照片
func takePhoto() {
// 获取相机地址
HZCameraMedia.default().onlyTakePhoto() { (picAddress) in
// 拍摄成功去拼接照片
self.genPanoramaPhoto(path: picAddress)
} cameraStatus: { (status: E_TURN_STATUS) in
// 相机状态
} fail: { (err) in
// 拍摄失败
}
}
拼接照片
代码语言:javascript复制// 7、拼接照片
func genPanoramaPhoto(path: String) {
let homedDirectory = NSHomeDirectory() "/Documents/"
HZCameraFileManager.default().setAlbumLocalAddress(homedDirectory)
HZCameraFileManager.default().getPhotoWithName(path) { (img) in
// 拼接完成的照片路径
} fail: { (err) in
// 拼接失败
} progress: { (process) in
// 拼接过程
}
}
获取相机参数
代码语言:javascript复制// 8、获取相机参数
func getSystemInfo(result: @escaping FlutterResult) {
HZCameraSettings.default().getCameraMemoryInfo {(info) in
result([
"mBatteryPercent": info.chargeInfo,
"mChargingState": info.batteryStatus == E_BATTERY_CHARGING ? "充电中" : "未充电",
"freeMemorySpaceWithUnitG": String(format: "%.2f", Float(info.memoryFreeStorage)/1000),
] as [String: Any])
} fail: { (err) in
print(err)
}
}
5、将数据返回到flutter层
数据通信依然是那2种基本方式,result
及时返回数据和event
实时监听数据。
及时返回数据
比如获取相机参数
代码语言:javascript复制result([
"mBatteryPercent": info.chargeInfo,
"mChargingState": info.batteryStatus == E_BATTERY_CHARGING ? "充电中" : "未充电",
]as [String: Any])
在安卓中,我们使用的是hashMap
类型来传递参数,而在iOS中对应的则是Dictionary
类型,所以可以直接使用[] as [String:Any]
来声明一个字典类型。
监听实时数据
相机的图像是动态的,需要监听图像数据并实时刷新。
代码语言:javascript复制// 在项目初始化中声明event方法。
var eventSink:FlutterEventSink?
self.eventSink!([
"code": 1,
"data": [
"frameData": FlutterStandardTypedData(bytes: data),
"width": w,
"height": h
],
] as [String: Any])
图片数据是Unit8List
,所以传递到flutter中还需要一层包装。
四、总结
一边学swift一边实现功能,可以说是现学现卖了,但其实也只花费了8个工作日左右的时间,学会了dart,了解了kotlin,再来学swift可以说过一遍文档,写写小例子就可以开工了。这些功能在安卓端已经跑通了一遍,只是重写一遍,根据双端差异调整一下接口执行的流程就好。
说起来好像很简单,但iOS开发的第一步,熟悉xcode的目录加导入插件就花费了5天时间,还是花了几次学费请教了一下ios开发大佬,加起来也花费了13天时间。
这些功能完善只算是入了原生开发的门,只学会了kotlin和swift的皮毛,以及了解双端项目开发流程。对于原生布局,其他各种原生功能都还没有涉及到,需要更多的实战来掌握更多的知识点。
这些功能完成过了一个月才有空写下这篇短文。而这段时间开发另一个插件,不得不用原生布局,再配合上
PlatformView
,掌握了编程语言和布局方法,就像前端学会了html5 js,能做的都可以做了,算是更加入门了原生开发,又可以水2篇文章了,当然那都是后话了。