距離上篇文章過去了一個半月了,主要雙端都開發完畢,不能繼續帶薪學習了,需要乾點正事,讓這麼久的辛勤成果發揮它應有的價值-上架。Android的需要上架各個應用商店,iOS的上架App Store,第一次幹這活可比攻堅技術還讓人心力交瘁,一把心酸淚在心裡流淌,光吐槽都能再水2000個字。這麼長時間也不能光忙活個上架的事,協助同事完善了一下公司的後臺管理系統,還還開發了另一款相機外掛,那是後話了。
經過了Android端的開發,我們已經把外掛的基本功能全部摸清楚,在專案中也正常的跑通了,按道理來講,現在只需要瞭解一下雙端開發差異,將kotlin的程式碼轉換為swift端的程式碼,還有這些功能涉及到的許可權申請重新在iOS端復現一遍即可。那豈不是看一遍文件,瞭解一下開發差異,再給我三天就搞定?想到這就笑出了聲。
但當實際上手的時候,還是發現自己Too young, too simple。開發者賬號要準備,必須使用的xcode編輯器要下載,xcode的檔案目錄完全看不懂,官方文件搜尋功能也不會用,想打包發給同事測試還要先交100刀樂,以及其他種種問題。上一次這麼難受的時候還是第一次用iPhone,用了一個月,還是迴歸安卓,作為使用者還可以選擇不用,現在但作為開發者,不用也得用。
最讓人想吐槽的就是,其他程式語言的保留字和報錯描述都用人們熟悉的單詞,只有蘋果非要發明一套,不反邏輯,但就是要反常識。
還有更蛋疼的,就是感覺iOS的文章特別的少,免費課都是落後的知識,付費的系統課時間太長了,沒時間去學習,而且冗餘的知識也用不上。
吐槽完畢,進入正題。
一、xcode檔案目錄
話不多說,首先來看看Android和iOS對比,第一眼就能讓我們發現的差異就是檔案目錄的差異。日常在Flutter的開發中就偶爾會和Android目錄的檔案打交道,而且都是比較直觀,src,res一看就懂,grale一百度到處都是文章,所以在我的中篇就沒有對目錄做介紹。但在iOS的目錄中,花了半天才找到寫程式碼的地方。
還是右鍵專案目錄,滑鼠放在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!!!
10000+字長文|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 判斷,三元運算。
// 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來宣告一個函式,對傳遞的引數及返回的值可以宣告型別。
//無返回值,返回值型別可以省略
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
檢視依賴。
可以在Cocoapods使用小記學習Cocoapods的詳細使用
2、匯入本地依賴
由於我們的專案中使用的是本地依賴,而且是一個framework,所以我們只需要將此依賴匯入到專案中,並配置podspace檔案就好。
檔案匯入
在flutter專案中,開啟ios目錄,新建Freamwork
資料夾,並將本地的的依賴複製貼上進來。
pods更新
開啟xcode編輯器,再到pods
中的.podspec
檔案中新增如下配置
// 引用框架庫
s.vendored_frameworks = "Framework/*.framework"
複製程式碼
再開啟命令列工具,執行pod update
本地依賴不會再
Link Binary With Librarys
中顯示,可以直接在編碼檔案嘗試匯入,有提示則依賴匯入成功。
3、資料互動初始化
開始正式編寫業務程式碼,這裡就開始看出kotlin和swift的相似之處
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對許可權申請和連線硬體有一點差別,但整體是差不多的。
申請許可權
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()
}
}
}
}
複製程式碼
連線相機
// 2、連線相機
func connectCamera() {
do {
try HZCameraConnector.default().connectToCameraError()
} catch let error {
self.eventSink!([
"code": 0,
"data": "連線失敗",
] as [String: Any])
}
// 連線相機成功後直接初始化
setUpCamera()
}
複製程式碼
相機連線狀態改變的時候執行
// 3、相機連線狀態改變的時候執行
public func cameraConnectionStateChange(_ state: E_SOCKET_STATE) {
if E_SOCKET_STATE_CONNECTED != state {
NSLog("相機已斷開")
} else {
NSLog("相機已連線")
setUpCamera()
}
}
複製程式碼
相機初始化,獲取相關許可權
安卓連線相機只需要進行一次初始化,所以把這個方法放在前面,而iOS每次連線相機都需要初始化。
// 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()
}
複製程式碼
拍攝照片
// 6、拍攝照片
func takePhoto() {
// 獲取相機地址
HZCameraMedia.default().onlyTakePhoto() { (picAddress) in
// 拍攝成功去拼接照片
self.genPanoramaPhoto(path: picAddress)
} cameraStatus: { (status: E_TURN_STATUS) in
// 相機狀態
} fail: { (err) in
// 拍攝失敗
}
}
複製程式碼
拼接照片
// 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
// 拼接過程
}
}
複製程式碼
獲取相機引數
// 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
實時監聽資料。
及時返回資料
比如獲取相機引數
result([
"mBatteryPercent": info.chargeInfo,
"mChargingState": info.batteryStatus == E_BATTERY_CHARGING ? "充電中" : "未充電",
]as [String: Any])
複製程式碼
在安卓中,我們使用的是hashMap
型別來傳遞引數,而在iOS中對應的則是Dictionary
型別,所以可以直接使用[] as [String:Any]
來宣告一個字典型別。
監聽實時資料
相機的影像是動態的,需要監聽影像資料並實時重新整理。
// 在專案初始化中宣告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篇文章了,當然那都是後話了。