optimize UIKit such as UITableview, UIButton, UITextField, UISwitch with Blocks to make it easier to write

Overview

HWBlocksUI

A set of utilities to make UIKit Easier to write

使用

CocoaPods

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'

target 'TestApp' do
  pod 'HWBlocksUI', '~>0.0.2'
end

背景

UIKit中的许多常用控件通过Delegate方式或者指定target+selector来实现事件回调,例如UITableViewUITextFieldUIButton等。这种方式的优点是代码规整,在代码量大的时候更容易维护。但是当回调逻辑不是特别复杂时,使用Block回调会比Delegatetarget+selector更加有优势,具体体现在:

  • 代码紧凑,无需声明协议,可以将相关代码逻辑集中在一起,降低开发调试成本;
  • 允许访问上下文变量,无需再专门抽出实例变量供不同代理方法共享。

苹果自身也曾经调整过部分API,专门支持Block回调方式,例如NSTimer,在iOS 10后新增了方法:

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats Block:(void (^)(NSTimer *timer))Block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

用来取代之前指定target+selector的方法:

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

又例如iOS 9之前常用的UIAlertViewController,要通过UIAlertViewDelegate实现点击回调,苹果干脆废弃重写了一个类UIAlertController,抽出UIAlertAction类,完全通过Block方式实现,代码写起来简洁明了很多:

+ (instancetype)actionWithTitle:(nullable NSString *)title style:(UIAlertActionStyle)style handler:(void (^ __nullable)(UIAlertAction *action))handler;

优化思路

鉴于上述分析,对UITableViewUITextFieldUIButton等常用的UIKit类进行Block改写,同时希望做到以下几点:

  • Delegate的基础上增加对应的Block方式,原有Delegate方式不受影响,调用方可根据实际场景自行选择合适的回调方式;
  • Block的方法与原Delegate方法名字尽量保持一致,降低迁移成本;
  • 赋值Block回调时,Xcode要能自动代码填充,因为手写Block入参回参容易出错;
  • 尽量不使用method swizzling等黑魔法,对安全性与稳定性的影响降到最小。

HWBlocksUI

基于上述目的,笔者封装了HWBlocksUI库,对UITableViewUITextFieldUIButton常用UI组件做了Block改造,使用示例如下:

UITableView实现一个简单列表:

    UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
    [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:reuseId];
    [self.view addSubview:tableView];

    NSArray *titles = @[@"北京", @"上海", @"深圳", @"广州", @"成都", @"雄安", @"苏州"];
    
    tableView.numberOfRowsHandler = ^NSInteger(UITableView *__weak  _Nonnull tableView, NSInteger section) {
        return titles.count;
    };
    
    tableView.cellForRowHandler = ^UITableViewCell * _Nonnull(UITableView *__weak  _Nonnull tableView, NSIndexPath * _Nonnull indexPath) {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId forIndexPath:indexPath];
        cell.textLabel.text = titles[indexPath.row];
        return cell;
    };
    
    tableView.didSelectRowHandler = ^(UITableView *__weak  _Nonnull tableView, NSIndexPath * _Nonnull indexPath) {
        NSString *title = titles[indexPath.row];
        NSLog(title);
    };

UITextField实现一个最多允许输入6个字符的输入框:

    UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(20, 100, self.view.frame.size.width - 40, 30)];
    textField.borderStyle = UITextBorderStyleRoundedRect;
    textField.clearButtonMode = UITextFieldViewModeAlways;
    [self.view addSubview:textField];

    textField.shouldChangeCharactersHandler = ^BOOL(UITextField *__weak  _Nonnull textField, NSRange range, NSString * _Nonnull replacementString) {
        NSString *str = [textField.text stringByReplacingCharactersInRange:range withString:replacementString];
        if (str.length > 6) {
            return NO;
        }
        return YES;
    };

    textField.shouldReturnHandler = ^BOOL(UITextField *__weak  _Nonnull textField) {
        [textField resignFirstResponder];
        return YES;
    };

UIButton,考虑到对UIControlEventsTouchUpInside事件响应最多,所以专门封了一个clickHandler,对其他事件响应可以使用setHandler:forControlEvents:

    UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
    [btn setFrame:CGRectMake(24, 200, self.view.frame.size.width - 48, 20)];
    [btn setTitle:@"OK" forState:UIControlStateNormal];
    btn.clickHandler = ^{
        NSLog(@"OK");
    };
    [self.view addSubview:btn];

实现原理

UIKit进行Block改造的核心点在于:

  • 为要改造的UIKit类,添加每个Delegate方法对应的Block属性;
  • 由于无法改造UIKit源码,所以仍然需要有一个Delegate对象,实现对应的代理方法;
  • Delegate对象在执行代理方法时,找到对应的Block执行实际回调方法;
  • 对调用方隐藏这个Delegate对象;

下面以UITextField为例看下改造的主要过程:

添加Block属性

定义相应CategoryUITextField+HWBlocksUI用来绑定Block;梳理UITextFieldDelegate的方法,定义对应的BlockBlock属性名采用Delegate的方法主体名+Handler的形式,入参和回参与Delegate方法保持一致,通过runtime将该Block属性添加到该分类。示例代码如下:

头文件中定义Block属性:

typedef BOOL(^HWShouldBeginEditingBlock)(UITextField *__weak textField);
@property (nonatomic, copy) HWShouldBeginEditingBlock shouldBeginEditingHandler;

实现文件中,实现其对应的settergetter

- (void)setShouldBeginEditingHandler:(HWShouldBeginEditingBlock)shouldBeginEditingHandler {
    NSAssert(shouldBeginEditingHandler, @"shouldBeginEditingHandler cannot be nil");
    [self configDelegate];
    objc_setAssociatedObject(self, HWBlocksUIShouldBeginEditingKey, shouldBeginEditingHandler, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (HWShouldBeginEditingBlock)shouldBeginEditingHandler {
    return objc_getAssociatedObject(self, &HWBlocksUIShouldBeginEditingKey);
}

这里setter中会同时执行[self configDelegate],接下来会讲到其目的。

配置Delegte

新增一个类HWBlocksUIProxy,遵循UITextFieldDelegate,在其代理方法中,实际执行的是该对象绑定的Block,如果没有找到对应的Block,则返回默认值:

- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
    if (textField.shouldBeginEditingHandler) {
        return textField.shouldBeginEditingHandler(textField);
    }
    return YES;
}

在上步设置Block属性时,会把Delegate设置为该HWBlocksUIProxy

- (void)configDelegate {
    HWBlocksUIProxy *Delegate = [HWBlocksUIProxy sharedInstance];
    if (self.Delegate != Delegate) {
        self.Delegate = Delegate;
    }
}

对调用方隐藏Delegate

由于在每一次设置Block时,都会去检查设置Delegate,所以达到了对调用方隐藏Delegate的目的。考虑到HWBlocksUIProxy的使用特征和频率,同时由于其不包含实例变量,只用来转发方法,资源占用很小,方便起见设为单例形式。

内存处理

typedef BOOL(^HWShouldChangeCharactersBlock)(UITextField *__weak textField, NSRange range, NSString *replacementString);

定义Block时,UIKit对象自身需要设置为__weak属性,以防出现UIKit对象与其持有Block之间的循环应用。

总结

HWBlocksUI的实现大部分是胶水代码,不过如果能让调用方更方便使用,维护代价更小,那这一切都是值得做的。欢迎各位大佬一起讨论、使用、改进。

You might also like...
Codeless drop-in universal library allows to prevent issues of keyboard sliding up and cover UITextField/UITextView. Neither need to write any code nor any setup required and much more.
Codeless drop-in universal library allows to prevent issues of keyboard sliding up and cover UITextField/UITextView. Neither need to write any code nor any setup required and much more.

IQKeyboardManager While developing iOS apps, we often run into issues where the iPhone keyboard slides up and covers the UITextField/UITextView. IQKey

🏄‍♂️ UITextField-Navigation makes it easier to navigate between UITextFields and UITextViews
🏄‍♂️ UITextField-Navigation makes it easier to navigate between UITextFields and UITextViews

' __________________ _______ _________ _______ _________ _______ _ ______ ' |\ /|\__ __/\__ __/( ____ \|\ /

This library allows you to make any UIView tap-able like a UIButton.

UIView-TapListnerCallback Example To run the example project, clone the repo, and run pod install from the Example directory first. Installation UIVie

HoverConversion realized vertical paging with UITableView. UIViewController will be paged when reaching top or bottom of UITableView contentOffset.
HoverConversion realized vertical paging with UITableView. UIViewController will be paged when reaching top or bottom of UITableView contentOffset.

HoverConversion ManiacDev.com referred. https://maniacdev.com/2016/09/hoverconversion-a-swift-ui-component-for-navigating-between-multiple-table-views

Advanced Natural Motion Animations, Simple Blocks Based Syntax
Advanced Natural Motion Animations, Simple Blocks Based Syntax

FlightAnimator Moved to Swift 3.1 Support: For Swift 3.1 - Use tag Version 0.9.9 See Installation Instructions for clarification Introduction FlightAn

Light and scrollable view controller for tvOS to present blocks of text
Light and scrollable view controller for tvOS to present blocks of text

TvOSTextViewer Light and scrollable view controller for tvOS to present blocks of text Description TvOSTextViewer is a view controller to present bloc

Building blocks to easily consume Swift's `AsyncSequence`.

AsyncSequenceReader AsyncSequenceReader provides building blocks to easily consume Swift's AsyncSequence. Installation Add AsyncSequenceReader as a de

Lockdown is an open source firewall that blocks trackers, ads, and badware in all apps

Lockdown Privacy (iOS) Lockdown is an open source firewall that blocks trackers, ads, and badware in all apps. Product details at lockdownprivacy.com.

Easier way to represent the structure of UITableView.

Shoyu Shoyu is a library written in Swift to represent UITableView data structures. Shoyu means Soy Sauce in Japanese. Usage Create single section and

An easy to use, customizable replacement for UISegmentedControl & UISwitch.
An easy to use, customizable replacement for UISegmentedControl & UISwitch.

BetterSegmentedControl BetterSegmentedControl is an easy to use, customizable replacement for UISegmentedControl and UISwitch written in Swift. Featur

UISwitch which paints over the parent view with the color in Swift.
UISwitch which paints over the parent view with the color in Swift.

AnimatedSwitch Swift subclass of the UISwitch which paints over the parent view with the color if switch is turned on and returns original superview b

A UISwitch that infects its superview with its tint color.
A UISwitch that infects its superview with its tint color.

UISwitch subclass that 'infects' the parent view with the onTintColor when the switch is turned on. Inspired by this Dribble by Ramotion. Screenshot I

iOS7 style drop in replacement for UISwitch
iOS7 style drop in replacement for UISwitch

SevenSwitch iOS7 style drop in replacement for UISwitch Usage Cocoapods pod 'SevenSwitch', '~ 2.1' Swift support was added in version 2.0. If your p

Nicely animated flat design switch alternative to UISwitch
Nicely animated flat design switch alternative to UISwitch

AIFlatSwitch A smooth, nice looking and IBDesignable flat design switch for iOS. Can be used instead of UISwitch. Inspired by Creativedash's Dribbble

A rapid prototype of UISwitch built with Swift and PaintCode.
A rapid prototype of UISwitch built with Swift and PaintCode.

LTJelloSwitch A cute UISwitch prototype built in about 8 hours. One of my co-worker gave me this concept last year. It was written in Objective-C and

🎸🎸🎸 Common categories for daily development. Such as  UIKit, Foundation, QuartzCore, Accelerate, OpenCV and more.
🎸🎸🎸 Common categories for daily development. Such as UIKit, Foundation, QuartzCore, Accelerate, OpenCV and more.

🎸🎸🎸 Common categories for daily development. Such as UIKit, Foundation, QuartzCore, Accelerate, OpenCV and more.

(Experimental libraries) Controls interrupt handling, such as alert views, and is compatible with UIKit and Swift UI.
(Experimental libraries) Controls interrupt handling, such as alert views, and is compatible with UIKit and Swift UI.

UIPresentCoordinator Controls interrupt handling, such as alert views, and is compatible with UIKit and Swift UI. This library manages items that are

SugarRecord is a persistence wrapper designed to make working with persistence solutions like CoreData in a much easier way.
SugarRecord is a persistence wrapper designed to make working with persistence solutions like CoreData in a much easier way.

What is SugarRecord? SugarRecord is a persistence wrapper designed to make working with persistence solutions like CoreData in a much easier way. Than

NVDate is an extension of NSDate class (Swift4), created to make date and time manipulation easier.

NVDate is an extension of NSDate class (Swift4), created to make date and time manipulation easier. NVDate is testable and robust, we wrote intensive test to make sure everything is safe.

Comments
  • setEventsHandler 覆盖失效

    setEventsHandler 覆盖失效

    多次调用setEventsHandler会覆盖失效

    [btn1 setEventsHandler:^{ NSLog(@"touch down"); } forControlEvents:(UIControlEventTouchDown)];

    [btn1 setEventsHandler:^{ NSLog(@"touch upinside"); } forControlEvents:(UIControlEventTouchUpInside)];

    opened by Bluter 1
Owner
We r infinite.
null
Enables developers to write code that interacts with CloudKit in targets that don't support the CloudKit Framework directly

CloudKit Web Services This package enables developers to write code that interac

Eric Dorphy 2 Dec 27, 2021
ReadWriteLock - Swift Implementation of a standard Read/Write lock.

ReadWriteLock A Swift implementation of a Read/Write lock. I'm really amazed that the Swift Standard Library (nor the Objective-C standard library) ha

Galen Rhodes 1 Mar 1, 2022
Swift library that makes easier to serialize the user's preferences (app's settings) with system User Defaults or Property List file on disk.

PersistentStorageSerializable PersistentStorageSerializable is a protocol for automatic serialization and deserialization of Swift class, struct or NS

Ivan Rublev 163 Jun 3, 2021
KeyPathKit is a library that provides the standard functions to manipulate data along with a call-syntax that relies on typed keypaths to make the call sites as short and clean as possible.

KeyPathKit Context Swift 4 has introduced a new type called KeyPath, with allows to access the properties of an object with a very nice syntax. For in

Vincent Pradeilles 406 Dec 25, 2022
🇨🇳 Learn how to make WeChat with SwiftUI. 微信 7.0 🟢

Overview Features Screenshots TODO Requirements License 中文 Overview I will continue to follow the development of technology, the goal is to bring Swif

Gesen 937 Dec 20, 2022
PJAlertView - This library is to make your own custom alert views to match your apps look and feel

PJAlertView - This library is to make your own custom alert views to match your apps look and feel

prajeet 6 Nov 10, 2017
This app is a sample app that recognizes specific voice commands such as "make it red", "make it blue", "make it green", and "make it black" and change the background color of the view in the frame.

VoiceOperationSample This app is a sample app that recognizes specific voice commands such as "make it red", "make it blue", "make it green", and "mak

Takuya Aso 3 Dec 3, 2021
Blocks Based Bluetooth LE Connectivity framework for iOS/watchOS/tvOS/OSX. Quickly configure centrals & peripherals, perform read/write operations, and respond characteristic updates.

ExtendaBLE Introduction ExtendaBLE provides a very flexible syntax for defining centrals and peripherals with ease. Following a blocks based builder a

Anton 94 Nov 29, 2022
Example project guide you schedules multiple thread for network requests in RxSwift, which is optimize your app's performance better.

RxSwift-Multi-Threading-Example Example project guide you schedules multiple thread for network requests in RxSwift, which is optimize your app's perf

Huy Trinh Duc 6 Nov 4, 2022
ClassicPhotos is a TableView App demos how to optimize image download and filter with operation queue.

ClassicPhotos ClassicPhotos is a TableView App demos how to optimize image download and filter with operation queue. With Operation and Operation Queu

Kushal Shingote 2 Dec 18, 2021