#Swift 5.3

# 前言 (Notion 好讀版)
UITextView 是 Swift 原生控件用戶最常用的輸入元件,諸如 Email 、筆記本或是聊天軟體,輸入框鮮少有開發者自己刻。
內建的功能完整,其功能幾乎涵蓋大部分情境,不足的地方只要在原生條件下去擴充即可。
因此,UITextView 底層有非常多未公開的流程,再擴充時如果能了解一二可以解少很多開發時間。

📌 此篇專注於 UITextView 原生觸控事件,建議先參考 前一篇 觸控事件初探

UITextView init and draw

預設下的UITextView 初始化後就已經自帶 22 個 UIGestureRecongnizers

UIView init and draw (對照)


Swift 5.3

紀錄 UIKit iOS 觸控事件的相關的測試心得,得出結果非常複雜,有興趣的人也可以點擊連結進入 Notion 好讀版閱讀,有錯誤的地方也請不吝指教,謝謝。

傳遞觸控事件

func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?

處理觸控事件傳遞鏈中繼點,負責檢查self 是否為最佳響應者,目前接收者如果不是最佳響應者,則往上傳遞給子層(next)。

swift 觸控事件傳遞很難介入控制,除非子類化UIApplication修改 sendEvent(event:)

觸控事件流程

UIGestureRecognizer (高優先) > UIControl(如 UIButton)

以下介紹的流程屬於一般情況(子類化 UIControl / UIResponder 或是最簡單的 UIView):
越複雜的原生UI元件,此過程可能被 Apple 改寫(比如: UITextView, UITableViewCell 等)。

整個觸控事件流程分為二個階段,三種事件。

單一個 View 的觸控交互:

  1. 手指觸碰 View
  2. (由下往上傳遞): hitTest(_) → UIView? 判斷 View 是否為最佳響應者,如果成立回傳自己,不成立傳遞給下一個響應者。
  3. (由上往下確認): hitTest(_) → UIView? 判斷 View 是否為最佳響應者,如果成立回傳自己,回傳上一層通知 nil 。
    - 成立:執行 touchesBegan > beginTracking (UIControl)。紀錄響應者,執行第四個步驟
  4. 跟蹤手指運動,劃分動作類型(如: tap, swipe, move 等)
  5. 依照動作依照順序執行物件表中符合的物件事件 ,解釋有點複雜,請看下面偽代碼。

總結

  • iOS 觸控事件回由下往上通知(責任鏈模式),依序判定響應者是誰 ,並檢查最後一個響應者 (hitTest 所回傳的view )所有觸控事件。
  • 當響應者有添加 UIGestureRecognizer ,並且執行完成後 ,預設情況 cancelsTouchesInView = false ,執行 touchCabcel 。
    (
    UIControl 事件不會被觸發)。
  • endTracking 結束後不一定觸發 cancelTracking。

藉由更改 UIGestureRecongnizer 的 cancelsTouchesInView 的值,控制執行 UIResponder 與 UIControl。

很重要再提醒一遍- 越複雜的原生 UI 元件,Apple 是可能改寫傳遞Event 的過程。 如: UITextView、UIButton。- UIView 會執行其父層 UIResponder ,藉由 override UIResponder(如:touchBegan,不實現super.touchBegan) 阻止其往父層執行。

未完待續…(尚有原生元件觸控研究心得)


#swift 5.3

這篇紀錄的是關於 iPhone 環境下的 Windows 操作。
與 iPad 的 多視窗(muti-windows) 不相同。

Get the Window

有時候,需求會需要我們必須從UIApplication 要最頂層的ViewController。在iOS 13 以前,沒有 sceneDelegate 的時候,我們會用 keyWindow 找。

  • keyWindow: 一次只會有一個是 keyWindow
// iOS 13 以前 appDelegate 環境下,會這樣找
let window = UIApplication.shared.windows.filter { $0.isKeyWindow }.first

但是在 iOS 13以後 sceneDelegate 環境下,啟動App 後如果你 …


#Swift

可以前往連結看更易閱讀的版本:Notion

紀錄有關 ctx 與 bezierPath 做形變的心得, 最近正在龜速完善一直想要完成的自定義動畫模組, 過去僅在 layer 層做些操控, 或是一些很基礎的 ctx 操作, 實際想要做形變控制後踩了一個大坑。

閱讀此篇之前你需要會:

基本介紹

  • CGContext:Quartz 2D 為基礎設計的上下文系統,簡單說就是一個很大的畫板,可以在上面做程式繪圖。(上下文:會因為你前後操作的順序,而影響結果)
  • UIBezierPath:對 CGContext 打包後的模板工具,可以節省實際操作 CGContext時所需的細節。

座標系統

繪圖時總是需要做一些形狀位移或是旋轉縮放。 了解座標系統讓事情變得簡單。

Quartz 2D 概念裡有兩個座標系統,一個是 …


#Swift 5.1

最近在side project 有需求提取照片庫照片,並且客製UI介面。
記錄一下使用心得。

文章連結


Swift 5.1

近期收到PM通知,發票 QRCode 有時候會掃不到,同一張發票Android掃得到,但是公司另一個專案的 iOS 版的可以掃 。 這問題可大可小,經過幾次試驗以後,掃不到的那張發票用 Swift metaDataOutput 回傳的都是 nil, 上網查也查不出原因,有看到swift 開發者上有人發問,也不了了之。

為此特地請PM去問出另一個專案用的是 Firebase ML Kit………….

Recognize text in images with ML Kit on iOS | Google Developers

需求:

  • QRCode 掃瞄器 :很簡單,Firebase SDK 包得很完整
  • 發票號碼驗證 :不需要更改,原本寫得可通用
  • 掃瞄框範圍限制 :整個需要重寫,captureOutput 輸出的是 sampleBuffer ,要實作很多東西Q_Q

動工

原本使用的是原生 metaOutput,只要簡單的將 metaData object 的 bounds 轉換後去比較即可,沒有難度。 但是改成 captureOutput 就比較複雜一點。

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {

第一步:取得 sampleBuffer imageSize

let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)! 
let imgWidth = CVPixelBufferGetWidth(imageBuffer)
let imgHeight = CVPixelBufferGetHeight(imageBuffer)
let imgSize = CGSize(width: imgWidth, height: imgHeight)

第二步:取出 解析出的QRCode 的Frame 在轉換在 sampleBuffer 上的比例座標

// size: inSampleBufferSize  
// barcode: VisionBarcode
let frame = barcode.frame
// 比例座標
let normalizedRect = CGRect(x: frame.origin.x / size.width, y: frame.origin.y / size.height, width: frame.size.width / size.width, height: frame.size.height / size.height)

第三部:將比例座標轉換成 videoPreviewLayer UIKit 的座標

// videoPreviewLayer: AVCaptureVideoPreviewLayer videoPreviewLayer.layerRectConverted(fromMetadataOutputRect: normalizedRect)

完工,拿到的的座標可以套用任何你想做的事情了。

小提醒:使用VideoCapture 要記得處理鏡頭方向。

### 完整Demo
### 我的筆記本
### GitHub 筆記本


前幾天PM 提出一個需求,需求是A頁面點擊 Button A 會跳轉到 B 頁面, 而 B頁面有一個 Button B 點擊後會重新推出一個新的 A頁面。 所以依照這需求邏輯,當用戶不停的反覆 butoon A -> button B ,ViewController 會無限疊上去。 雖然最後了解這問題是故意設計的,不用處理,但是我還是稍微測試一下。

方案一、

navigationController?.popViewController(animated: false)
navigationController?.pushViewController(vc, animated: true)

在 push 之前做好邏輯判斷後,先退出之前的畫面再push。

  • 成效:解決一半。
  • 缺點:pop 雖然沒有動畫,但是還是看得到消失時產生的閃動。

方案二、

navigationController?.pushViewController(vc, animated: true)removeSurplusViewController()

推出新畫面後,在篩選出多餘的ViewController ,從 nc.viewControllers 中移除。

  • 成效:完美解決。
  • 缺點:稍微複雜點,頁面越多邏輯會越複雜。

Originally published at http://github.com.


分享自己工作上swift 心得

最近比較專注於將工作心得存放在gitHub,做寫小的Demo 或是解說方便未來瀏覽查看,也分享給其他新手一起閱讀。有興趣的可以直接到我的github瀏覽。目前內容不多,未來有時間會再整理出教學。

  • Swift 資料夾: 紀錄一些原生API 的使用心得。包括系統‘、UIKit等
  • Charts: 工作需要用到大量Charts 。

持續更新中…


一個荒廢少年、青年、壯年時間,中年轉職工程師的心路歷程。
希望能給正在進行式或是即將進行式的人一個小小的明燈。

高中:台北某市立高中畢業。
大學:大學讀了兩間(數學系),都沒超過2年級就休學。
退伍後因為沒有一技之長又沒有什麼自覺自學,只能挑選在台灣入行沒有太高門檻的客服工作做到36歲。

2020年1月,過年的前幾天、生日的後幾天,發現自己終於36歲如果不再做些什麼,未來就這樣子了吧。
36歲的這天工作遇到瓶頸,在知名外商擔任資深客服,薪水在台灣客服雖然是站在頂點,但是並不快樂。個性性向與現職工作差異性過大,這個差異性隨著年紀增長,受的影響也越深。終於在36歲已經負擔不起,我想如果擔任過客服的多少都有經歷過。
所以在那一天,我思考了一整天,從個人潛在興趣到能力與未來性,我決定試著自學成為一個工 …


在編寫Swift UIKit 的模式下,在處理不同class相互溝通的方式,最常用的就是Delegate(代理)。利用 protocol 與代理的特性,做為傳值或是通知其他class 做事的模式。
Delegate 是個好方法,但是有時候你的 class 可能會有太多代理造成它很肥大,或是事後管理不方便。所以我會使用另一種方法作為替代性,它不會是最好的方法,不過就是提供另一種可能性的選擇。

使用的技巧,建議事前先了解:
- void 作為型別操作
- 變數 willset() 方法

其他技巧:
- NetWork 低耦合的編寫
- URLRequest API 的方法
- Json Decode
- Delegate 代理

請先下載完整 github

開始之前,先解釋整個運作邏輯與相對的源文件。
Screen 一組 View+Label 與一個 button ,Button透過API索取JsonData,如果成功View 會變成藍色,Label 文字也會一併改變。

Networker.swift :Request與網路索取Data / 解碼後回傳給DataCenter
JsonDecode.swift: 協助Networker解碼
DataCenter.swift: 像Networker 提出request,判斷Data 獲取是否成功。

我將會關注在DataCenter 與 ViewController 的互動。
順便一提 DataCenter 是透過 Delegate 接收回傳的檔案,可以順便看看之間的差別。

DataCenter:

Woody Liu

I am a Swift developer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store