移動端小白,30天掌握Flutter雙端外掛開發-下(iOS篇)

樂凡丶發表於2021-07-28

距離上篇文章過去了一個半月了,主要雙端都開發完畢,不能繼續帶薪學習了,需要乾點正事,讓這麼久的辛勤成果發揮它應有的價值-上架。Android的需要上架各個應用商店,iOS的上架App Store,第一次幹這活可比攻堅技術還讓人心力交瘁,一把心酸淚在心裡流淌,光吐槽都能再水2000個字。這麼長時間也不能光忙活個上架的事,協助同事完善了一下公司的後臺管理系統,還還開發了另一款相機外掛,那是後話了。

經過了Android端的開發,我們已經把外掛的基本功能全部摸清楚,在專案中也正常的跑通了,按道理來講,現在只需要瞭解一下雙端開發差異,將kotlin的程式碼轉換為swift端的程式碼,還有這些功能涉及到的許可權申請重新在iOS端復現一遍即可。那豈不是看一遍文件,瞭解一下開發差異,再給我三天就搞定?想到這就笑出了聲。

但當實際上手的時候,還是發現自己Too young, too simple。開發者賬號要準備,必須使用的xcode編輯器要下載,xcode的檔案目錄完全看不懂,官方文件搜尋功能也不會用,想打包發給同事測試還要先交100刀樂,以及其他種種問題。上一次這麼難受的時候還是第一次用iPhone,用了一個月,還是迴歸安卓,作為使用者還可以選擇不用,現在但作為開發者,不用也得用。

最讓人想吐槽的就是,其他程式語言的保留字和報錯描述都用人們熟悉的單詞,只有蘋果非要發明一套,不反邏輯,但就是要反常識。

學一點點,不能學多了.jpg

還有更蛋疼的,就是感覺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中會使用nullboolintStringMap以及Uint8List這幾種型別,還是在看這個表:

image.png

其他的資料都行想必都很熟悉了,在這裡也是一樣,瞭解不同資料型別的方法。主要注意一下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檢視依賴。

image.png

可以在Cocoapods使用小記學習Cocoapods的詳細使用

2、匯入本地依賴

由於我們的專案中使用的是本地依賴,而且是一個framework,所以我們只需要將此依賴匯入到專案中,並配置podspace檔案就好。

檔案匯入

在flutter專案中,開啟ios目錄,新建Freamwork資料夾,並將本地的的依賴複製貼上進來。

image.png

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篇文章了,當然那都是後話了。

相關文章