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

必讀

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

這周公衆號發佈的文章:css

本期知識小集:html

  • 如何定製一個 UIView 類型控件的出入動畫
  • UIView 的事件透傳
  • iOS 如何調試 WebView (二)
  • 一個結構較爲合理的下載模塊該怎麼設計
  • 再談 iOS 輸入框的字數統計/最大長度限制

截止目前爲止iOS知識小集已經發布183條,我想看看前端

如何定製一個 UIView 類型控件的出入動畫

做者: halohilyjava

在 iOS 開發中,自定義的彈層組件很是常見,好比分享框、自定義的 actionSheet 組件等。有的場景下,會選擇使用 UIViewController 類型來實現,這時定製這個視圖的出現、隱藏動畫很是方便。然而,有時候須要選擇輕量級的 UIView 類型來實現。這時該怎麼定製它的出現、隱藏動畫呢?這裏提供一個思路:git

使用 UIView 的 willMoveToSuperview:didMoveToSuperview這組方法,它們會在 UIView 做爲subView 被添加到其餘 UIView 中時調用。這裏須要注意,自身調用 removeFromSuperview 方法時,一樣會觸發這組方法,只不過這時的參數會是一個 nil。github

提供一個例子來講明:一個選擇 UIView 類型實現的自定義 actionSheet 的出入動畫,交互基本和微信一致。web

#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 格式結果,能夠選擇 YYCacheEGOCache 等緩存框架。而圖片的緩存,則能夠選擇專一圖片緩存的 YYWebImageSDWebImage 等框架。

對於體積較大的文件,天然須要一個專一大文件下載的模塊。這個模塊不關注具體的文件類型,不關注具體的業務場景,它只須要文件 url 、文件管理模塊生成的本地目標路徑,完成下載任務便可。

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

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

做者: KANGZUBIN

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

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

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

這會帶來一個問題:好比輸入框限定最多隻能輸入 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 **,請註明 小程序入羣

上面技術羣羣規比較嚴,主要以討論技術爲主,發廣告等行爲都一概踢出羣。另外,咱們的微信羣不是真正的「討論羣」,爲了避免浪費你們看消息的時間,除技術問題或很小白的問題,你們不會討論。

某天「 知識小集 · 前端修行室 」進來個妹子,而後羣就炸了,控制不住你們的情緒,因此專門開了個「 知識小集 · 吐槽吹水羣 」,這個羣暫時不對外開放,能夠加入技術羣后再拉羣。

相關文章
相關標籤/搜索