iOS 知識小集第 15 期 · 掘金首發

知識小集發表於2019-03-01

必讀

iOS 知識小集不知不覺已經更新了 15 期,也許有些同學會問,前 14 期在哪裡能找到呢?每天一條iOS知識小集在哪裡能第一時間看到呢?我有必要說明一下,我們的文章會在公眾號上首發,其它平臺只會發部分文章,而且更新都會比公眾號晚。每天早晨 7:30左右,會在小集微信群和微博發出每天一條iOS知識小集和公眾號文章。如果你想關注我們的公眾號或加小集微信群(2號群還差13個名額就滿了),請看文章結尾。

這周公眾號釋出的文章:

本期知識小集:

  • 如何定製一個 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];
複製程式碼
iOS 知識小集第 15 期 · 掘金首發

一個結構較為合理的下載模組該怎麼設計

作者: halohily

最近負責下載元件的開發,對於如何設計一個下載模組有一些粗淺體會,今天分享一下我採用的方案,希望能夠拋磚引玉。另外,最近會出兩篇主題為“下載元件的設計”和“與 Hybrid 相關的下載方案”的長文,歡迎關注 「 知識小集 」公眾號。

“下載”作為一個需要本地結構化、持久化儲存的場景,使用資料庫是比較自然的選擇。所以,我們首先拆分出一個資料庫模組,用來儲存下載記錄。主要欄位為下載任務的資訊,如 url、檔案大小、時間戳等,以及最重要的檔案本地儲存路徑。這一層可以在介面設計上認真思慮,比如僅涉及當前業務邏輯,而不涉及具體的資料庫操作,相當於是較 FMDB 等資料庫元件來說更高層的抽象。後期需要更換底層資料庫引擎時,本層封裝無需改動,是比較理想的實現。

資料庫是用來儲存下載記錄的,那麼所下載的具體檔案呢?自然就需要一個檔案管理模組,在這個模組裡,負責根據檔案 url 生成本地的儲存路徑,以及進行檔案校驗、儲存、移除等操作。

所要下載的檔案,我們可以按體積、型別等進行區分。對於網路請求的結果這類簡短內容,我抽象出了一個快取管理器,用來完成網路請求、圖片等內容的快取。網路請求的 JSON 格式結果,可以選擇 YYCacheEGOCache 等快取框架。而圖片的快取,則可以選擇專注圖片快取的 YYWebImageSDWebImage 等框架。

對於體積較大的檔案,自然需要一個專注大檔案下載的模組。這個模組不關注具體的檔案型別,不關注具體的業務場景,它只需要檔案 url 、檔案管理模組生成的本地目標路徑,完成下載任務即可。

在以上通用模組的基礎上,有一個業務層的封裝,它負責根據提交的下載任務,協調呼叫各基礎元件。舉個例子,一個下載任務包括一個視訊檔案、一個網路請求結果、三張圖片。本模組在收到任務後,首先解析出以上的任務具體結構。使用檔案管理模組,根據視訊檔案 url 生成本地儲存目標路徑,呼叫大檔案下載器完成下載,此為一個子任務。對於網路請求結果,呼叫快取模組,進行快取,此為一個子任務。對於三張圖片,使用圖片快取器完成快取,此為一個子任務。三個子任務均完成,使用資料庫模組,對下載記錄、媒體檔案記錄等進行儲存。除此之外,本模組還負責對外提供下載中任務、已下載任務等資料。

再談 iOS 輸入框的字數統計/最大長度限制

作者: KANGZUBIN

前兩週我們發了一個小集「iOS 自帶九宮格拼音鍵盤與 Emoji 表情之間的坑」,介紹瞭如何解決由於輸入框限制 Emoji 表情的輸入導致中文拼音也無法輸入的問題。

後面我們又有了新需求:對輸入框已輸入的文字字數進行實時統計,並在介面上顯示剩餘字數,且不能讓所輸入的文字超過最大限制長度。但這個簡單的功能仍然有不少小坑。

在上一個小集中,我們講到,對於 iOS 系統自帶的鍵盤,有時候它在輸入框中填入的是佔位字元(已被高亮選中起來),等使用者選中鍵盤上的候選詞時,再替換為真正輸入的字元,如下:

iOS 知識小集第 15 期 · 掘金首發

這會帶來一個問題:比如輸入框限定最多隻能輸入 10 位,當已經輸入 9 個漢字的時候,使用系統拼音鍵盤則第 10 個字的拼音就打不了(因為剩餘的 1 位無法輸入完整的拼音)。

怎麼辦呢?上面提到,輸入框中的拼音會被高亮選中起來,所以我們可以根據 UITextFieldmarkedTextRange 屬性判斷是否存在高亮字元,如果有則不進行字數統計和字串截斷操作。我們通過監聽 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_hhwsy9871,請註明 iOS 入群
  • 「 知識小集 · Flutter 自習室 」人數到 200,可以先加微信 coldlight_hh 或者 bob5201215,請註明 Flutter 入群
  • 「 知識小集 · 前端修行室 」,可以先加微信 wsy9871coldlight_hh,請註明 前端入群
  • 「 知識小集 · PWA 實驗室 」,可以先加微信 wsy9871coldlight_hh,請註明 PWA入群
  • 「 知識小集 · 小程式交流群 」,**可以先加微信 kangzubin **,請註明 小程式入群

上面技術群群規比較嚴,主要以討論技術為主,發廣告等行為都一律踢出群。另外,我們的微信群不是真正的“討論群”,為了不浪費大家看訊息的時間,除技術問題或很小白的問題,大家不會討論。

某天「 知識小集 · 前端修行室 」進來個妹子,然後群就炸了,控制不住大家的情緒,所以專門開了個「 知識小集 · 吐槽吹水群 」,這個群暫時不對外開放,可以加入技術群后再拉群。

iOS 知識小集第 15 期 · 掘金首發

相關文章