必讀
iOS 知識小集不知不覺已經更新了 15 期,也許有些同學會問,前 14 期在哪裡能找到呢?每天一條iOS知識小集在哪裡能第一時間看到呢?我有必要說明一下,我們的文章會在公眾號上首發,其它平臺只會發部分文章,而且更新都會比公眾號晚。每天早晨 7:30左右,會在小集微信群和微博發出每天一條iOS知識小集和公眾號文章。如果你想關注我們的公眾號或加小集微信群(2號群還差13個名額就滿了),請看文章結尾。
這周公眾號釋出的文章:
- WWDC 2018,蘋果是否會構建新的生態系統
- React 狀態管理:從 props 和 state 說起
- 從原始碼看微信小程式啟動過程
- 如何快速教會3歲小孩程式設計
- iOS 原生 vs. Flutter 評測
本期知識小集:
- 如何定製一個 UIView 型別控制元件的出入動畫
- UIView 的事件透傳
- iOS 如何除錯 WebView (二)
- 一個結構較為合理的下載模組該怎麼設計
- 再談 iOS 輸入框的字數統計/最大長度限制
截止目前為止iOS知識小集已經發布183條,我想看看
如何定製一個 UIView 型別控制元件的出入動畫
作者: halohily
在 iOS 開發中,自定義的彈層元件非常常見,比如分享框、自定義的 actionSheet 元件等。有的場景下,會選擇使用 UIViewController 型別來實現,這時定製這個檢視的出現、隱藏動畫非常方便。然而,有時候需要選擇輕量級的 UIView 型別來實現。這時該怎麼定製它的出現、隱藏動畫呢?這裡提供一個思路:
使用 UIView 的
willMoveToSuperview:
和didMoveToSuperview
這組方法,它們會在UIView
作為subView 被新增到其他 UIView 中時呼叫。這裡需要注意,自身呼叫removeFromSuperview
方法時,同樣會觸發這組方法,只不過這時的引數會是一個 nil。
提供一個例子來說明:一個選擇 UIView 型別實現的自定義 actionSheet 的出入動畫,互動基本和微信一致。
#pragma mark - show & dismiss
- (void)didMoveToSuperview {
if (self.superview) {
[UIView animateWithDuration:0.35 delay:0 usingSpringWithDamping:0.9 initialSpringVelocity:10 options:UIViewAnimationOptionCurveEaseIn animations:^{
_backgroundControl.alpha = 1;
self.actionSheetTable.frame = CGRectMake(0, SCREEN_HEIGHT - _sheetHeight, SCREEN_WIDTH, _sheetHeight);
} completion:^(BOOL finished) {
[super didMoveToSuperview];
}];
}
}
- (void)hideSelf {
[UIView animateWithDuration:0.35 delay:0 usingSpringWithDamping:0.9 initialSpringVelocity:10 options:UIViewAnimationOptionCurveEaseIn animations:^{
_backgroundControl.alpha = 0;
self.actionSheetTable.frame = CGRectMake(0, SCREEN_HEIGHT, SCREEN_WIDTH, _sheetHeight);
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
}
複製程式碼
UIView 的事件透傳
作者: Vong_HUST
通常我們會遇到這種需求,一個檢視除了需要響應子檢視的點選事件,其它空白地方希望能將點選事件透傳到,比如自定義了一個“導航欄”,除了左右兩邊按鈕,希望其它部分點選能夠透傳到底下的檢視。這個時候我們可以通過複寫 hitTest
方法,具體實現如下。
@implementation PassthroughView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (self.hidden || self.alpha < FLT_EPSILON || self.userInteractionEnabled) {
return [super hitTest:point withEvent:event];
}
UIView *targetView = nil;
for (UIView *subview in [[self subviews] reverseObjectEnumerator]) {
if ((targetView = [subview hitTest:[subview convertPoint:point fromView:self] withEvent:event])) {
break;
}
}
return targetView;
}
@end
複製程式碼
以上程式碼即可實現,只響應子檢視的事件,而非子檢視區域部分的互動事件則透傳到響應鏈中的下一個響應者。
如果你有其它更好方式,也可以分享出來,一起交流下。
iOS 如何除錯 WebView (二)
作者: Lefe_x
上次的小集中,我主要討論瞭如何除錯 WebView ,小集發出後 @折騰範兒_味精
提供了另一種方法來除錯 WebView。我覺得有必要再擴充套件一下,原話是這樣的:
真說方便還是植入一個 webview console 在 debug 環境,可以在黑盒下不連電腦不連 safari 調 dom,調 js,另外在開發期間 Xcode 斷點 run 的時候,js hook console.log console.alert,接管 window.onerror 全都改 bridge NSLog 輸出,也會方便點。
短短几句話,資訊量很大,私下向味精學習了下,這裡總結一下。寫完這個小集特意讓味精看了下,覺得有必要再補充下第二種除錯技巧,但中途踩了幾個坑,一直到23:30左右才搞定。
第一,把 WebView 用來除錯的 log、alert、error 顯示到 NA ,在除錯時會方便不少。做 WebView 與端互動的時候,主要用 window.webkit.messageHandlers.xxx.postMessage(params);
來給端發訊息,也就是說 WebView 想給端發訊息的時候直接呼叫這個方法即可,端會通過 WKScriptMessageHandler
的代理方法來接收訊息,而此時端根據和 WebView 約定的規則進行通訊即可。
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
複製程式碼
而新增除錯資訊,無非就是給 WebView 新增了 log、alert、error 這些訊息的 bridge,這樣當 WebView 給端傳送訊息後,端根據和 WebView 約定的規則解析 log、alert、error 為端對應的事件,比如 log 直接呼叫端的 NSLog
,alert 呼叫端的 UIAlertController
。
第二,黑盒下除錯 WebView,無需連線電腦和 safari 即可除錯 DOM,這個可以參考小程式的 vConsole 或者 eruda 。可以直接在 WebView 中接入,或者在端中接入。這裡以在端中接入 eruda 為例,這裡踩到幾個坑:
1.有些頁面顯示不出來,估計是故意遮蔽掉的,味精特意使用 JSBox 試了下其它頁面,發現百度等都不可以顯示除錯按鈕,而掘金是可以的;
2.使用本地的頁面也顯示不出來,這是 webview 跨域安全方面的考慮,file 協議下會禁止 js css html 以部分 file,部分網路的方式載入。
下面這段程式碼直接在 webview 載入完成後執行即可。
NSString *js = @"(function() {var script = document.createElement('script');script.type = 'text/javascript';script.src = 'https://xteko.blob.core.windows.net/neo/eruda-loader.js';document.body.appendChild(script);})();";
[self.webView evaluateJavaScript:js completionHandler: nil];
複製程式碼
一個結構較為合理的下載模組該怎麼設計
作者: halohily
最近負責下載元件的開發,對於如何設計一個下載模組有一些粗淺體會,今天分享一下我採用的方案,希望能夠拋磚引玉。另外,最近會出兩篇主題為“下載元件的設計”和“與 Hybrid 相關的下載方案”的長文,歡迎關注 「 知識小集 」公眾號。
“下載”作為一個需要本地結構化、持久化儲存的場景,使用資料庫是比較自然的選擇。所以,我們首先拆分出一個資料庫模組,用來儲存下載記錄。主要欄位為下載任務的資訊,如 url、檔案大小、時間戳等,以及最重要的檔案本地儲存路徑。這一層可以在介面設計上認真思慮,比如僅涉及當前業務邏輯,而不涉及具體的資料庫操作,相當於是較 FMDB 等資料庫元件來說更高層的抽象。後期需要更換底層資料庫引擎時,本層封裝無需改動,是比較理想的實現。
資料庫是用來儲存下載記錄的,那麼所下載的具體檔案呢?自然就需要一個檔案管理模組,在這個模組裡,負責根據檔案 url 生成本地的儲存路徑,以及進行檔案校驗、儲存、移除等操作。
所要下載的檔案,我們可以按體積、型別等進行區分。對於網路請求的結果這類簡短內容,我抽象出了一個快取管理器,用來完成網路請求、圖片等內容的快取。網路請求的 JSON 格式結果,可以選擇 YYCache
、EGOCache
等快取框架。而圖片的快取,則可以選擇專注圖片快取的 YYWebImage
、SDWebImage
等框架。
對於體積較大的檔案,自然需要一個專注大檔案下載的模組。這個模組不關注具體的檔案型別,不關注具體的業務場景,它只需要檔案 url 、檔案管理模組生成的本地目標路徑,完成下載任務即可。
在以上通用模組的基礎上,有一個業務層的封裝,它負責根據提交的下載任務,協調呼叫各基礎元件。舉個例子,一個下載任務包括一個視訊檔案、一個網路請求結果、三張圖片。本模組在收到任務後,首先解析出以上的任務具體結構。使用檔案管理模組,根據視訊檔案 url 生成本地儲存目標路徑,呼叫大檔案下載器完成下載,此為一個子任務。對於網路請求結果,呼叫快取模組,進行快取,此為一個子任務。對於三張圖片,使用圖片快取器完成快取,此為一個子任務。三個子任務均完成,使用資料庫模組,對下載記錄、媒體檔案記錄等進行儲存。除此之外,本模組還負責對外提供下載中任務、已下載任務等資料。
再談 iOS 輸入框的字數統計/最大長度限制
作者: KANGZUBIN
前兩週我們發了一個小集「iOS 自帶九宮格拼音鍵盤與 Emoji 表情之間的坑」,介紹瞭如何解決由於輸入框限制 Emoji 表情的輸入導致中文拼音也無法輸入的問題。
後面我們又有了新需求:對輸入框已輸入的文字字數進行實時統計,並在介面上顯示剩餘字數,且不能讓所輸入的文字超過最大限制長度。但這個簡單的功能仍然有不少小坑。
在上一個小集中,我們講到,對於 iOS 系統自帶的鍵盤,有時候它在輸入框中填入的是佔位字元(已被高亮選中起來),等使用者選中鍵盤上的候選詞時,再替換為真正輸入的字元,如下:
這會帶來一個問題:比如輸入框限定最多隻能輸入 10 位,當已經輸入 9 個漢字的時候,使用系統拼音鍵盤則第 10 個字的拼音就打不了(因為剩餘的 1 位無法輸入完整的拼音)。
怎麼辦呢?上面提到,輸入框中的拼音會被高亮選中起來,所以我們可以根據 UITextField
的 markedTextRange
屬性判斷是否存在高亮字元,如果有則不進行字數統計和字串截斷操作。我們通過監聽 UIControlEventEditingChanged
事件來對輸入框內容的變化進行相應處理,如下:
[self.textField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
複製程式碼
- (void)textFieldDidChange:(UITextField *)textField {
// 判斷是否存在高亮字元,如果有,則不進行字數統計和字串截斷
UITextRange *selectedRange = textField.markedTextRange;
UITextPosition *position = [textField positionFromPosition:selectedRange.start offset:0];
if (position) {
return;
}
// maxWowdLimit 為 0,不限制字數
if (self.maxWowdLimit == 0) {
return;
}
// 判斷是否超過最大字數限制,如果超過就截斷
if (textField.text.length > self.maxWowdLimit) {
textField.text = [textField.text substringToIndex:self.maxWowdLimit];
}
// 剩餘字數顯示 UI 更新
}
複製程式碼
對於 UITextView
的處理也是類似的。
另外,對於“字數”的定義是很多種理解:在 Objective-C 中字串 NSString
的長度 length
,對於一箇中文漢字和一個英文字母都是 1;但如果我們要按位元組來統計和限制,同一字元在不同編碼下所佔的位元組數也是不同的;另外有時我們要統計的是所輸入文字的單詞個數,而不是字串的長度,所以我們需要根據不同的使用場景進行分析。
關注我們
- 「 知識小集 · 2號群 」差13個就滿,可以先加微信
coldlight_hh
或wsy9871
,請註明iOS 入群
; - 「 知識小集 · Flutter 自習室 」人數到 200,可以先加微信
coldlight_hh
或者bob5201215
,請註明 Flutter 入群; - 「 知識小集 · 前端修行室 」,可以先加微信
wsy9871
或coldlight_hh
,請註明前端入群
- 「 知識小集 · PWA 實驗室 」,可以先加微信
wsy9871
或coldlight_hh
,請註明PWA入群
- 「 知識小集 · 小程式交流群 」,**可以先加微信
kangzubin
**,請註明小程式入群
上面技術群群規比較嚴,主要以討論技術為主,發廣告等行為都一律踢出群。另外,我們的微信群不是真正的“討論群”,為了不浪費大家看訊息的時間,除技術問題或很小白的問題,大家不會討論。
某天「 知識小集 · 前端修行室 」進來個妹子,然後群就炸了,控制不住大家的情緒,所以專門開了個「 知識小集 · 吐槽吹水群 」
,這個群暫時不對外開放,可以加入技術群后再拉群。