Runtime學習與使用(一):為UITextField添加類目實現被鍵盤遮住後視圖上移,點擊空白回收鍵盤

来源:http://www.cnblogs.com/chtBlog/archive/2016/08/19/5787648.html
-Advertisement-
Play Games

OC中類目無法直接添加屬性,可以通過runtime實現在類目中添加屬性。 在學習的過程中,試著為UITextField添加了一個類目,實現了當TextField被鍵盤遮住時視圖上移的功能,順便也添加了點擊空白回收鍵盤功能。效果預覽使用時不需要一句代碼就可以實現上述功能[github鏈接](https ...


OC中類目無法直接添加屬性,可以通過runtime實現在類目中添加屬性。

在學習的過程中,試著為UITextField添加了一個類目,實現了當TextField被鍵盤遮住時視圖上移的功能,順便也添加了點擊空白回收鍵盤功能。
效果預覽
使用時不需要一句代碼就可以實現上述功能

[github鏈接](https://github.com/a1419430265/CHTTextFieldHealper)

.h文件

 1 //
 2 //  UITextField+CHTPositionChange.h
 3 //  CHTTextFieldHealper
 4 //
 5 //  Created by risenb_mac on 16/8/17.
 6 //  Copyright © 2016年 risenb_mac. All rights reserved.
 7 //
 8 
 9 #import <UIKit/UIKit.h>
10 
11 @interface UITextField (CHTHealper)
12 
13 /**
14  *  是否支持視圖上移
15  */
16 @property (nonatomic, assign) BOOL canMove;
17 /**
18  *  點擊回收鍵盤、移動的視圖,預設是當前控制器的view
19  */
20 @property (nonatomic, strong) UIView *moveView;
21 /**
22  *  textfield底部距離鍵盤頂部的距離
23  */
24 @property (nonatomic, assign) CGFloat heightToKeyboard;
25 
26 @property (nonatomic, assign, readonly) CGFloat keyboardY;
27 @property (nonatomic, assign, readonly) CGFloat keyboardHeight;
28 @property (nonatomic, assign, readonly) CGFloat initialY;
29 @property (nonatomic, assign, readonly) CGFloat totalHeight;
30 @property (nonatomic, strong, readonly) UITapGestureRecognizer *tapGesture;
31 @property (nonatomic, assign, readonly) BOOL hasContentOffset;
32 
33 @end

 

在.h文件中聲明屬性之後需要在.m中重寫setter,getter方法
首先定義全局key用作關聯唯一標識符

1 static char canMoveKey;
2 static char moveViewKey;
@implementation UITextField (CHTHealper)
@dynamic canMove;
@dynamic moveView;

 

具體實現

1 - (void)setCanMove:(BOOL)canMove {
2 // 參數意義:關聯對象 ,關聯標識符,關聯屬性值,關聯策略
3     objc_setAssociatedObject(self, &canMoveKey, @(canMove), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
4 }
5 
6 - (BOOL)canMove {
7 // 關聯屬性值為對象類型,需要轉換
8     return [objc_getAssociatedObject(self, &canMoveKey) boolValue];
9 }

 

想要實現鍵盤遮住TextField後視圖上移,首先應確定TextField是否被鍵盤遮住,需要知道TextField在整個屏幕中的位置

// 此方法可以獲得TextField左上角在當前window中的坐標
[self convertPoint:self.bounds.origin toView:[UIApplication sharedApplication].keyWindow]


還需要知道鍵盤高度,這點需要接受系統通知,但是什麼時候接受通知、註銷通知?
我的思路是在TextField成為第一響應者的時候,為TextField添加通知,但是如果直接重寫becomeFirstResponder方法會覆蓋掉UITextField本身的方法,造成的最明顯的後果就是沒有游標了……為了避免這個問題,我用了runtime另外一個強大的功能,方法交換
為了保證方法交換隻進行一次,使用dispatch_once
為了保證方法交換儘早執行,寫在了load方法中

 1 + (void)load {
 2     static dispatch_once_t onceToken;
 3     dispatch_once(&onceToken, ^{
 4         SEL systemSel = @selector(initWithFrame:);
 5         SEL mySel = @selector(setupInitWithFrame:);
 6         [self exchangeSystemSel:systemSel bySel:mySel];
 7         
 8         SEL systemSel2 = @selector(becomeFirstResponder);
 9         SEL mySel2 = @selector(newBecomeFirstResponder);
10         [self exchangeSystemSel:systemSel2 bySel:mySel2];
11         
12         SEL systemSel3 = @selector(resignFirstResponder);
13         SEL mySel3 = @selector(newResignFirstResponder);
14         [self exchangeSystemSel:systemSel3 bySel:mySel3];
15         
16         SEL systemSel4 = @selector(initWithCoder:);
17         SEL mySel4 = @selector(setupInitWithCoder:);
18         [self exchangeSystemSel:systemSel4 bySel:mySel4];
19     });
20     [super load];
21 }


具體交換步驟

 1 // 交換方法
 2 + (void)exchangeSystemSel:(SEL)systemSel bySel:(SEL)mySel {
 3     Method systemMethod = class_getInstanceMethod([self class], systemSel);
 4     Method myMethod = class_getInstanceMethod([self class], mySel);
 5     //首先動態添加方法,實現是被交換的方法,返回值表示添加成功還是失敗
 6     BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(myMethod), method_getTypeEncoding(myMethod));
 7     if (isAdd) {
 8         //如果成功,說明類中不存在這個方法的實現
 9         //將被交換方法的實現替換到這個並不存在的實現
10         class_replaceMethod(self, mySel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
11     }else{
12         //否則,交換兩個方法的實現
13         method_exchangeImplementations(systemMethod, myMethod);
14     }
15 }


在上面我交換了四組方法,兩組init方法,是為了保證無論是代碼創建的還是xib拖得TextField都進行初始化

 1 - (instancetype)setupInitWithCoder:(NSCoder *)aDecoder {
 2     [self setup];
 3     return [self setupInitWithCoder:aDecoder];
 4 }
 5 
 6 - (instancetype)setupInitWithFrame:(CGRect)frame {
 7     [self setup];
 8     return [self setupInitWithFrame:frame];
 9 }
10 
11 - (void)setup {
12     self.heightToKeyboard = 10;
13     self.canMove = YES;
14     self.keyboardY = 0;
15     self.totalHeight = 0;
16     self.tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction)];
17 }

 

在TextField成為第一響應者時,為self添加通知接收,為moveView添加點擊事件(實現點擊空白回收鍵盤),註銷第一響應者時,註銷通知,移除點擊事件

 1 - (BOOL)newBecomeFirstResponder {
 2 // 如果沒有設置moveView 預設為當前控制器的view
 3     if (self.moveView == nil) {
 4         self.moveView = [self viewController].view;
 5     }
 6 // 保證moveView只有一個本TextField的點擊事件
 7     if (![self.moveView.gestureRecognizers containsObject:self.tapGesture]) {
 8         [self.moveView addGestureRecognizer:self.tapGesture];
 9     }
10 // 當重覆點擊當前TextField時(重覆成為第一響應者)或設置為不可移動 不再添加通知
11     if ([self isFirstResponder] || !self.canMove) {
12         return [self newBecomeFirstResponder];
13     }
14     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(showAction:) name:UIKeyboardWillShowNotification object:nil];
15     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hideAction:) name:UIKeyboardWillHideNotification object:nil];
16     return [self newBecomeFirstResponder];
17 }
18 
19 - (BOOL)newResignFirstResponder {
20 // 確保當前moveView有當前點擊事件,移除
21     if ([self.moveView.gestureRecognizers containsObject:self.tapGesture]) {
22         [self.moveView removeGestureRecognizer:self.tapGesture];
23     }
24     if (!self.canMove) {
25         return [self newResignFirstResponder];
26     }
27     BOOL result = [self newResignFirstResponder];
28     [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
29     [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
30 // 當另外一個TextField成為第一響應者,當前TextField註銷第一響應者時不會回收鍵盤,手動調用moveView改變方法
31     [self hideKeyBoard:0];
32     return result;
33 }
34 //獲取當前TextField所在controller
35 - (UIViewController *)viewController {
36     UIView *next = self;
37     while (1) {
38         UIResponder *nextResponder = [next nextResponder];
39         if ([nextResponder isKindOfClass:[UIViewController class]]) {
40             return (UIViewController *)nextResponder;
41         }
42         next = next.superview;
43     }
44     return nil;
45 }

 

接收到彈出鍵盤後調用的方法

 1 - (void)showAction:(NSNotification *)sender {
 2     if (!self.canMove) {
 3         return;
 4     }
 5 // 獲取鍵盤高度以及鍵盤的Y坐標
 6     self.keyboardY = [sender.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].origin.y;
 7     self.keyboardHeight = [sender.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
 8     [self keyboardDidShow];
 9 }
10 
11 - (void)hideAction:(NSNotification *)sender {
12     if (!self.canMove || self.keyboardY == 0) {
13         return;
14     }
15     [self hideKeyBoard:0.25];
16 }
17 
18 - (void)keyboardDidShow {
19     if (self.keyboardHeight == 0) {
20         return;
21     }
22 // 獲取TextField在window中的Y坐標
23     CGFloat fieldYInWindow = [self convertPoint:self.bounds.origin toView:[UIApplication sharedApplication].keyWindow].y;
24 // 確定是否需要視圖上移,以及移動的距離
25     CGFloat height = (fieldYInWindow + self.heightToKeyboard + self.frame.size.height) - self.keyboardY;
26     CGFloat moveHeight = height > 0 ? height : 0;
27     
28     [UIView animateWithDuration:0.25 animations:^{
29 // 判斷是否是scrollView併進行相應移動
30         if (self.hasContentOffset) {
31             UIScrollView *scrollView = (UIScrollView *)self.moveView;
32             scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, scrollView.contentOffset.y + moveHeight);
33         } else {
34             CGRect rect = self.moveView.frame;
35             self.initialY = rect.origin.y;
36             rect.origin.y -= moveHeight;
37             self.moveView.frame = rect;
38         }
39 // 記錄當前TextField使得moveView移動的距離
40         self.totalHeight += moveHeight;
41     }];
42 }
43 
44 - (void)hideKeyBoard:(CGFloat)duration {
45     [UIView animateWithDuration:duration animations:^{
46         if (self.hasContentOffset) {
47             UIScrollView *scrollView = (UIScrollView *)self.moveView;
48             scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, scrollView.contentOffset.y - self.totalHeight);
49         } else {
50             CGRect rect = self.moveView.frame;
51             rect.origin.y += self.totalHeight;
52             self.moveView.frame = rect;
53         }
54 // moveView回覆狀態後將移動距離置0
55         self.totalHeight = 0;
56     }];
57 }

 

點擊事件當前controllerview endediting

- (void)tapAction {
    [[self viewController].view endEditing:YES];
}

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 該系列教程概述與目錄:http://www.cnblogs.com/chengyujia/p/5787111.html 一、自定義控制項簡介 在本項目中,無論是游戲主區域還是虛擬方向鍵都是通過自定義控制項來實現的,我們有必要先對自定義控制項有個簡單的瞭解。而且通過自定義控制項的學習能更好的理解系統自帶控制項的 ...
  • 什麼是Dagger2 Dagger是為Android和Java平臺提供的一個完全靜態的,在編譯時進行依賴註入的框架,原來是由Square公司維護,現在由Google維護。 我們知道Dagger是一個依賴註入的框架,那麼什麼是依賴註入呢? 我們在activity中有可能會用到很多很多的類,這些類要在a ...
  • 1.參考文獻 http://hi.baidu.com/accpzhangbo/blog/item/52aeffc683ee6ec238db4965.html 2.解析 查看java.lang.System的源代碼,我們可以找到System.exit(status)這個方法的說明,代碼如下: /** ...
  • 一般2D游戲使用SurfaceView足夠,所以不要認為什麼都要使用GLSurfaceView(openGL),而且 GLSurfaceView的弊端在於適配能力差,因為很多機型中是沒有GPU加速的。 ...
  • 1. 下列哪些語句關於記憶體回收的說明是正確的? (b) A、 程式員必須創建一個線程來釋放記憶體 B、 記憶體回收程式負責釋放無用記憶體 C、 記憶體回收程式允許程式員直接釋放記憶體 D、 記憶體回收程式可以在指定的時間釋放記憶體對象 2. 下麵異常是屬於Runtime Exception 的是(abcd)(多選 ...
  • Analyze提示:Value stored to "xxx"is never read 分析:即當前變數沒有被使用,在當前類中搜索該變數發現只是被賦值並沒有被使用。 解決:解除這個提示:刪除或者註視這行代碼OK; Analyze提示:the left operand of ** is a garb ...
  • 一個類可以繼承另一個類的方法,屬性和其他特性。當一個類繼承其他類時,繼承類叫子類,被繼承類叫超類(或父類)。在Swift中,繼承具有單繼承的特點,每個子類只有一個直接父類,繼承是區分類與其他類型的一個基本特征。 在Swift中,類可以調用和訪問父類的方法,屬性和下標腳本,並且可以重寫這些方法,屬性和 ...
  • Tinker 是微信官方的 Android 熱補丁解決方案,它支持動態下發代碼、So庫以及資源,讓應用能夠在不需要重新安裝的情況下實現更新。這裡大致介紹 Tinker 的實現原理,當時遇到的各種坑以及對它各個方面性能的優化工作。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...