Runtime learning and usage (1): Add a category for UITextField and move it to the image after it is hidden by the keyboard. Click the blank recycle keyboard and view it after uitextfield.
You cannot directly add attributes to categories in OC. You can use runtime to add attributes to categories.
In the course of learning, I tried to add a category for UITextField to implement the function of moving the image when the TextField is covered by the keyboard. By the way, I also added the function of clicking blank to reclaim the keyboard.
Effect Preview
The above functions can be implemented without a code statement.
[Github link] (https://github.com/a1419430265/CHTTextFieldHealper)
. H file
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 * Whether to support view up
15 * /
16 @property (nonatomic, assign) BOOL canMove;
17 / **
18 * Click to recycle the keyboard, move the view, the default is the view of the current controller
19 * /
20 @property (nonatomic, strong) UIView * moveView;
twenty one /**
22 * distance from bottom of textfield to top of keyboard
twenty three */
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
After declaring an attribute in the. h file, you must re-write the setter and getter methods in. m.
First, define the global key as the unique identifier of the association.
1 static char canMoveKey;2 static char moveViewKey;
@implementation UITextField (CHTHealper)@dynamic canMove;@dynamic moveView;
Implementation
1-(void) setCanMove: (BOOL) canMove {
2 // Parameter meaning: association object, association identifier, association attribute value, association strategy
3 objc_setAssociatedObject (self, & canMoveKey, @ (canMove), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
4}
5
6-(BOOL) canMove {
7 // The associated attribute value is an object type and needs to be converted
8 return [objc_getAssociatedObject (self, & canMoveKey) boolValue];
9 }
To move the TextField behind the screen, you must first determine whether the TextField is covered by the keyboard. You need to know the position of the TextField on the screen.
// This method can obtain the coordinates in the current window in the upper left corner of TextField [self convertPoint: self. bounds. origin toView: [UIApplication sharedApplication]. keyWindow]
You also need to know the keyboard height. In this case, you need to receive system notifications, but when will you receive notifications or deregister notifications?
My idea is to add notifications to TextField when TextField becomes the first responder. However, if the becomeFirstResponder method is directly rewritten, The UITextField method will be overwritten, the most obvious consequence is that there is no cursor ...... To avoid this problem, I used another powerful runtime function to exchange methods.
To ensure that the method exchange is performed only once, use dispatch_once
In order to ensure that the method exchange is executed as soon as possible, it is written in the load method
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 }
Procedure
1 // exchange method
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 // First add the method dynamically. The implementation is the method being exchanged. The return value indicates whether the addition was successful or failed.
6 BOOL isAdd = class_addMethod (self, systemSel, method_getImplementation (myMethod), method_getTypeEncoding (myMethod));
7 if (isAdd) {
8 // If successful, the implementation of this method does not exist in the class
9 // Replace the implementation of the swapped method with this non-existent implementation
10 class_replaceMethod (self, mySel, method_getImplementation (systemMethod), method_getTypeEncoding (systemMethod));
11} else {
12 // Otherwise, swap the implementation of the two methods
13 method_exchangeImplementations (systemMethod, myMethod);
14}
15}
I exchanged four sets of methods above. The two sets of init methods are used to ensure that both the code creation and the TextField dragged by xib are initialized.
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 }
When TextField becomes the first responder, add notification receiving for self and add click events for moveView (Click blank recycle keyboard). When the first responder is logged out, cancel the notification and remove the click event.
1-(BOOL) newBecomeFirstResponder {
2 // If moveView is not set, it defaults to the view of the current controller
3 if (self.moveView == nil) {
4 self.moveView = [self viewController] .view;
5}
6 // Ensure that moveView has only one click event for this TextField
7 if (! [Self.moveView.gestureRecognizers containsObject: self.tapGesture]) {
8 [self.moveView addGestureRecognizer: self.tapGesture];
9 }
10 // When the current TextField is repeatedly clicked (repeatedly becomes the first responder) or set to non-movable, no more notifications are added
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 // Make sure current moveView has current click event, remove
21 if ([self.moveView.gestureRecognizers containsObject: self.tapGesture]) {
22 [self.moveView removeGestureRecognizer: self.tapGesture];
twenty three }
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 // When another TextField becomes the first responder, the keyboard will not be recycled when the current TextField logs out the first responder, and the moveView method is manually called
31 [self hideKeyBoard: 0];
32 return result;
33}
34 // Get the controller where the current TextField is located
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}
Method called after receiving the pop-up keyboard
1-(void) showAction: (NSNotification *) sender {
2 if (! Self.canMove) {
3 return;
4}
5 // Get the keyboard height and the Y coordinate of the keyboard
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;
twenty one }
22 // Get the Y coordinate of the TextField in the window
23 CGFloat fieldYInWindow = [self convertPoint: self.bounds.origin toView: [UIApplication sharedApplication] .keyWindow] .y;
24 // Determine if the view needs to be moved up, and the distance moved
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 // determine if it is scrollView and move accordingly
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 // Record the distance moved by the current TextField
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 sets the move distance to 0 after the status is restored
55 self.totalHeight = 0;
56}];
57}
Click the event currently controllerview endediting
- (void)tapAction { [[self viewController].view endEditing:YES];}