iOS:轻量可定制的防键盘遮挡textField实现总结

本文介绍了一个自定义UITextField子类,用于解决键盘弹出时遮挡textField的问题。通过监听键盘通知,计算并调整view的位置,确保textField始终可见。文章还讨论了实现细节和设计模式的应用。

背景

  这是个常见场景:textField或者包含textField的控件需要在键盘弹出的时候随之上移,不然就会被键盘遮挡。

  既然是常见的,为了提高开发效率,也为了遵循DRY原则,我们就有必要实现一个公共控件。实现这个功能并不复杂,更有意义的是在这个实现过程中的一些总结和思考。下面首先讲一下实现过程,之后再附上总结。

实现

  在键盘弹出和收起的时候,会收到两个全局的系统通知:UIKeyboardWillShowNotification和UIKeyboardWillHideNotification,并且通知的userInfo中包含有键盘高度和键盘展开及收起的动画时间。键盘高度可以推算出上移的高度,而上移下移动画时间与键盘展开收起动画时间保持一致可以使得动画更加流畅。

  一般来说,需要上移的高度就是textField底部和键盘顶部的距离,不过也有一些场景需要上移更多的距离,比如,textField下方还有个确认按钮,那这种情况可能需要把确认按钮也移到键盘的上方,此时一共需要上移的高度,就应该是键盘顶部与textField底部之间的距离,加上textField底部与确认按钮底部的距离。

  一般情况下直接上移整个keyWindow即可,不过也有一些场景是需要移动一个特定的view,比如承载textField的容器。

  考虑到以上因素,我们来做一个比较灵活的可定制的防止键盘遮挡textField,通过UITextField子类来实现。代码如下:

#import 

@interface LHWAutoAdjustKeyboardTextField : UITextField

//上移后,textField需要额外高于键盘顶部的距离,默认为0
@property (nonatomic, assign) CGFloat offset;

//需要向上移动的view,默认为keyWindow
@property (nonatomic, weak) UIView *movingView;

@end

 

#import "LHWAutoAdjustKeyboardTextField.h"

@interface LHWAutoAdjustKeyboardTextField()

@end

@implementation LHWAutoAdjustKeyboardTextField

#import "LHWAutoAdjustKeyboardTextField.h"

@interface LHWAutoAdjustKeyboardTextField()

@property (nonatomic, assign) CGRect originalFrame;

@end

@implementation LHWAutoAdjustKeyboardTextField

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self addKeyboardNotifications];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self addKeyboardNotifications];
    }
    return self;
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}

- (void)addKeyboardNotifications {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}

- (void)keyboardWillShow: (NSNotification *)notification {
    if (self.isFirstResponder) {
        CGPoint relativePoint = [self convertPoint: CGPointZero toView: [UIApplication sharedApplication].keyWindow];

        CGFloat keyboardHeight = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
        CGFloat overstep = CGRectGetHeight(self.frame) + relativePoint.y + keyboardHeight - CGRectGetHeight([UIScreen mainScreen].bounds);
        overstep += self.offset;
        self.originalFrame = self.movingView.frame;
        if (overstep > 0) {
            CGFloat duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
            CGRect frame = self.originalFrame;
            frame.origin.y -= overstep;
            [UIView animateWithDuration: duration delay: 0 options: UIViewAnimationOptionCurveLinear animations: ^{
                self.movingView.frame = frame;
            } completion: nil];
        }
    }
}

- (void)keyboardWillHide: (NSNotification *)notification {
    if (self.isFirstResponder) {
        CGFloat duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
        [UIView animateWithDuration: duration delay: 0 options: UIViewAnimationOptionCurveLinear animations: ^{
            self.movingView.frame = self.originalFrame;
        } completion: nil];
    }
}

@end

总结

  总结下实现过程中值得注意的几个细节:

  (1)为什么选择用继承而不是用分类?首先我们要明白分类的主要目标在于扩展功能,而非数据。本例中除了需要拓展UITextField的功能,还需要保存额外的数据(offset,movingView以及originalFrame),因此更适合用继承。

  有同学可能会有疑问了,用runtime的关联对象可以为分类添加属性啊扩展数据啊,嗯,确实可以,但是关联对象不到不得已或者是调试场景下,尽量不要使用,因为很容易引发奇怪的内存管理问题。

  (2)在dealloc函数里要移除对键盘事件的通知,不然在iOS8系统会crash,这也是不考虑用分类实现的另一个原因,在分类中override已有方法是非常危险的,尤其是dealloc这种控制生命周期的函数

  分类的方法加入原有类这一操作是在运行期系统加载分类时完成的,所以很可能会覆盖原有类的实现,如果有多个分类同时实现了名字一样的方法,结果就是以最后一次的覆盖为准。因此,在实现分类方法时,仅仅避免覆写已有方法还不够,最好还要加上前缀,来避免工程中其他地方的某个分类和你的分类起了一样的名字,不然出现bug后会很难定位;

  (3)本自定义类的前缀是LHW,三个字母开头,因为苹果宣称保留所有两个字母前缀的权利,所以AFNetworking、SDWebImage等等这些著名开源库严格来说命名是不符合苹果规范的;

  (4)上移的view,这个属性要定义成weak的,因为很可能这个view就是textField的superView,如果不声明成weak,将会导致循环引用。

  很多人对weak的理解仅仅局限在防止循环引用的层面上,其实weak有更深层次的含义。在本例中,即便不会引发循环引用,上移的view也更适合于声明成weak的,因为这个类对于上移view是仅仅知道就可以的弱关联关系,而不是一种拥有或者持有的强关联关系。考虑另外一个相似的场景:在可以方便使用block回调的UIAlertController出现以前,当一个VC实现多个alertView的代理回调时,我们常常通过属性保存这些alertView来区分(用tag区分是很不优雅的做法)。

#import "FooVC.h"

@interface FooVC() <UIAlertViewDelegate>

@property(nonatomic, weak) UIAlertView *alertViewA;
@property(nonatomic, weak) UIAlertView *alertViewB;
@property(nonatomic, weak) UIAlertView *alertViewC;

@end

@implementation FooVC

- (void)showAlertABC {
    UIAlertView *alertViewA = [[UIAlertView alloc] initWithTitle:@"" message:@"我是弹窗A" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"doAThing", nil];
    self.alertViewA = alertViewA;
    UIAlertView *alertViewB = [[UIAlertView alloc] initWithTitle:@"" message:@"我是弹窗B" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"doBThing", nil];
    self.alertViewB = alertViewB;
    UIAlertView *alertViewC = [[UIAlertView alloc] initWithTitle:@"" message:@"我是弹窗C" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"doCThing", nil];
    self.alertViewC = alertViewC;
    [alertViewA show];
    [alertViewB show];
    [alertViewC show];
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    if (alertView == self.alertViewA) {
        [self doAThing];
    } else if (alertView == self.alertViewB) {
        [self doBThing];
    } else  if (alertView == self.alertViewC) {
        [self doCThing];
    }
}

@end

  此时就应该声明成weak而非strong,声明成weak的好处是VC不会干扰这些alertView原本的生命周期,如果声明成strong的,相当于强行延长了这些alertView的声明周期,直到VC释放时,他们才能释放,这样做显然是不合理的。

  (5)设计公用控件时,要尽可能多考虑各种使用场景,抽象出可定制部分,如本例中的offset和movingView,如果一开始没考虑这些,把原本需要定制的元素在代码中写死,等到未来需要时,就不得不改动原有的实现,违背了设计模式的开闭原则,非常不好。

作者:Eternal_Love

这里是m文件 应该是这里面具体写了吧 #import "TPBCommonTableController.h" #import "TPBTableTextSectionHeader.h" #import "TPBTableTextSectionFooter.h" #import "TPBDesignKit.h" #import "UIView+TPBExpanded.h" #import "MASConstraint+TPBExpanded.h" #import "TPBTableSectionModel.h" #import "TPBCommonInputView.h" #import "TPBCustomTableCellBaseModel.h" #import "TPBCustomViewTableCell.h" #import "TPBTitleSubtitleTableCell.h" #import "TPBSwitchTableCell.h" #import "TPBListButtonTableCell.h" #import "TPBProgressTableCell.h" #import "TPBCommonInputTableCell.h" #import "TPBCheckTableCell.h" #import "TPBJumpSelectTableCell.h" #import "TPBMenuSelectTableCell.h" #import "TPBListInputTableCell.h" #import "TPBTitleActionTableCell.h" @interface TPBCommonTableController () < TPBTableCommonInputDelegate, TPBTableListInputDelegate > @property (nonatomic, assign) UITableViewStyle tableViewStyle; @property (nonatomic, strong) UIView *tpbSearchBarPlaceholderView; @property (nonatomic, strong) TPBTableSearchHeader *tpbInnerSearchBar; @property (nonatomic, strong) TPBEmptyView *emptyView; @property (nonatomic, strong) TPKeyboardAvoidingTableView *tableView; @property (nonatomic, strong) MASConstraint *emptyCenterYConstraint; @property (nonatomic, strong) MASConstraint *searchBarTopConstraint; @property (nonatomic, strong) MASConstraint *searchBarHeightConstraint; @end @implementation TPBCommonTableController - (instancetype)init { if (@available(iOS 13.0, *)) { return [self initWithTableViewStyle:UITableViewStyleInsetGrouped]; } else { return [self initWithTableViewStyle:UITableViewStyleGrouped]; } } - (instancetype)initWithTableViewStyle:(UITableViewStyle)style { if (self = [super initWithNibName:nil bundle:nil]) { _tableViewStyle = style; _hideCellSeparator = NO; } return self; } - (instancetype)initWithCoder:(NSCoder *)coder { if (self = [super initWithCoder:coder]) { UITableViewStyle style = UITableViewStyleGrouped; if (@available(iOS 13.0, *)) { style = UITableViewStyleInsetGrouped; } _tableViewStyle = style; _hideCellSeparator = NO; } return self; } - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { UITableViewStyle style = UITableViewStyleGrouped; if (@available(iOS 13.0, *)) { style = UITableViewStyleInsetGrouped; } _tableViewStyle = style; _hideCellSeparator = NO; } return self; } - (void)dealloc { [self.view tpbRemoveForKeyboardEvent]; } - (void)tpbSetupInitialData { [super tpbSetupInitialData]; self.keyboardBehavior = TPBCommonTableKeyboardBehaviorDismissOnly; } - (void)tpbSetupSubviews { [super tpbSetupSubviews]; [self.tableView insertSubview:self.emptyView atIndex:0]; [self.view addSubview:self.tableView]; [self.view addSubview:self.tpbInnerSearchBar]; } - (void)tpbMakeConstraint { [super tpbMakeConstraint]; [self.emptyView mas_remakeConstraints:^(MASConstraintMaker *make) { make.leading.trailing.equalTo(self.mas_tpSafeAreaLayoutGuide); self.emptyCenterYConstraint = make.centerY.equalTo(self.mas_tpSafeAreaLayoutGuide); }]; [self.tableView mas_remakeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.mas_tpSafeAreaLayoutGuide); make.leading.trailing.equalTo(self.mas_tpSafeAreaLayoutGuide); make.bottom.equalTo(self.view); }]; CGFloat searchHeight = [self.tpbInnerSearchBar preferredHeight]; [self.tpbInnerSearchBar mas_remakeConstraints:^(MASConstraintMaker *make) { self.searchBarTopConstraint = make.top.equalTo(self.mas_tpSafeAreaLayoutGuide).offset(0); make.leading.trailing.equalTo(self.mas_tpSafeAreaLayoutGuide); self.searchBarHeightConstraint = make.height.equalTo(@(searchHeight)); }]; } - (void)tpbBindActions { [super tpbBindActions]; TPBWeakSelf [self tpbAddContentSizeDidChangeConfig:^(id _Nonnull object, TPBDynamicContentManager * _Nonnull manager) { TPBStrongSelf dispatch_async(dispatch_get_main_queue(), ^{ [_self updateTableHeader]; [_self.tableView reloadData]; }); } notifyWhenRegister:NO]; } - (void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; [self.view bringSubviewToFront:self.tpbInnerSearchBar]; [self updateSearchBarPosition]; } #pragma mark - Public - (void)setSectionArray:(NSArray<TPBTableSectionModel *> *)sectionArray { _sectionArray = sectionArray; [self.tableView reloadData]; } - (void)setSearchBarShow:(BOOL)searchBarShow { _searchBarShow = searchBarShow; self.tpbInnerSearchBar.hidden = !searchBarShow; [self updateTableHeader]; } - (NSString *)searchPlaceholder { return self.tpbInnerSearchBar.searchPlaceholder; } - (void)setSearchPlaceholder:(NSString *)searchPlaceholder { self.tpbInnerSearchBar.searchPlaceholder = searchPlaceholder; } - (void)updateSearchKey:(NSString *)searchKey { [self.tpbInnerSearchBar updateSearchKey:searchKey]; } - (void)setCustomTableHeaderView:(UIView *)customTableHeaderView { _customTableHeaderView = customTableHeaderView; [self updateTableHeader]; } - (void)setKeyboardBehavior:(TPBCommonTableKeyboardBehavior)keyboardBehavior { _keyboardBehavior = keyboardBehavior; switch (keyboardBehavior) { case TPBCommonTableKeyboardBehaviorDismissOnly: [self.view tpbRegisterForKeyboardEvent]; break; case TPBCommonTableKeyboardBehaviorDismissAndRespondClick: [self.view tpbRemoveForKeyboardEvent]; break; default: break; } } - (void)focusToFirstEligibleElement { NSIndexPath *indexPath = nil; for (NSUInteger section = 0; section < self.sectionArray.count; section++) { TPBTableSectionModel *sectionModel = self.sectionArray[section]; for (NSUInteger row = 0; row < sectionModel.cellModelArray.count; row++) { TPBBaseTableCellModel *cellModel = sectionModel.cellModelArray[row]; if (cellModel.isAutoFocusEnabled) { indexPath = [NSIndexPath indexPathForRow:row inSection:section]; break; } } if (indexPath != nil) break; } if (indexPath == nil) return; UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; if (cell == nil) return; if ([TPBA11yHelper isVoiceOverOn]) { UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, cell); } else { [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; } } #pragma mark - UITableViewDataSource & UITableViewDelegate // 注册自定义Cell - (void)registerCustomCellWithCellModel:(TPBCustomTableCellBaseModel *)customCellModel tableView:(UITableView *)tableView cellIdentifier:(NSString *)cellIdentifier { if (customCellModel.cellClass != nil) { BOOL isCellClassValid = [customCellModel.cellClass isSubclassOfClass:[UITableViewCell class]]; BOOL isValid = isCellClassValid; NSAssert(isCellClassValid, @"TPBCustomBaseTableCellModel's cellClass「%@」is not Subclass of UITableViewCell!", NSStringFromClass(customCellModel.cellClass)); if (!isValid) { return; } // 注册Cell Class [tableView registerClass:customCellModel.cellClass forCellReuseIdentifier:cellIdentifier]; } else if (customCellModel.cellNib != nil) { // 注册Cell Nib [tableView registerNib:customCellModel.cellNib forCellReuseIdentifier:cellIdentifier]; } } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return self.sectionArray.count; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { if (section < 0 || self.sectionArray.count <= section) { return 0; } TPBTableSectionModel *sectionModel = self.sectionArray[section]; return sectionModel.cellModelArray.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section < 0 || self.sectionArray.count <= indexPath.section) { return [[UITableViewCell alloc] init]; } TPBTableSectionModel *sectionModel = self.sectionArray[indexPath.section]; if (indexPath.row < 0 || sectionModel.cellModelArray.count <= indexPath.row) { return [[UITableViewCell alloc] init]; } BOOL isRTL = self.view.effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft; BOOL isLast = indexPath.row == sectionModel.cellModelArray.count - 1; TPBBaseTableCellModel *model = sectionModel.cellModelArray[indexPath.row]; switch (model.cellType) { case TPBTableCellTypeCustomCell: { if ([model isKindOfClass:[TPBCustomTableCellBaseModel class]]) { TPBCustomTableCellBaseModel *cellModel = (TPBCustomTableCellBaseModel *)model; NSString *cellIdentifier = [cellModel effectiveCellIdentifier:isRTL]; UITableViewCell *cell; if (TPBA11yHelper.isVoiceOverOn && cellModel.cellClass != nil) { cell = [[cellModel.cellClass alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } if (cell == nil) { // 若无法获取Cell,说明可能是未注册自定义Cell,则注册自定义Cell之后尝试重新取值 [self registerCustomCellWithCellModel:cellModel tableView:tableView cellIdentifier:cellIdentifier]; cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } if (cellModel.cellConfigCallback) { cellModel.cellConfigCallback(tableView, indexPath, cell, cellModel); } if ([cell isKindOfClass:[TPBBaseTableViewCell class]]) { TPBBaseTableViewCell *baseCell = (TPBBaseTableViewCell *)cell; [baseCell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; } return cell; } } break; case TPBTableCellTypeCustomView: { if ([model isKindOfClass:[TPBCustomViewTableCellModel class]]) { TPBCustomViewTableCellModel *cellModel = (TPBCustomViewTableCellModel *)model; NSString *cellIdentifier = [TPBCustomViewTableCell cellIdentifier:isRTL]; TPBCustomViewTableCell *cell; cell = [[TPBCustomViewTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } [cell updateCellModel:cellModel]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; return cell; } } break; case TPBTableCellTypeTitleSubtitle: { if ([model isKindOfClass:[TPBTitleSubtitleTableCellModel class]]) { TPBTitleSubtitleTableCellModel *cellModel = (TPBTitleSubtitleTableCellModel *)model; NSString *cellIdentifier = [TPBTitleSubtitleTableCell cellIdentifier:isRTL]; TPBTitleSubtitleTableCell *cell; if (TPBA11yHelper.isVoiceOverOn) { cell = [[TPBTitleSubtitleTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } [cell updateCellModel:cellModel]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; return cell; } } break; case TPBTableCellTypeSwitch: { if ([model isKindOfClass:[TPBSwitchTableCellModel class]]) { TPBSwitchTableCellModel *cellModel = (TPBSwitchTableCellModel *)model; NSString *cellIdentifier = [TPBSwitchTableCell cellIdentifier:isRTL]; TPBSwitchTableCell *cell; if (TPBA11yHelper.isVoiceOverOn) { cell = [[TPBSwitchTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } [cell updateCellModel:cellModel]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; return cell; } } break; case TPBTableCellTypeListButton: { if ([model isKindOfClass:[TPBListButtonTableCellModel class]]) { TPBListButtonTableCellModel *cellModel = (TPBListButtonTableCellModel *)model; NSString *cellIdentifier = [TPBListButtonTableCell cellIdentifier:isRTL]; TPBListButtonTableCell *cell; if (TPBA11yHelper.isVoiceOverOn) { cell = [[TPBListButtonTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } [cell updateCellModel:cellModel]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; return cell; } } break; case TPBTableCellTypeProgress: { if ([model isKindOfClass:[TPBProgressTableCellModel class]]) { TPBProgressTableCellModel *cellModel = (TPBProgressTableCellModel *)model; NSString *cellIdentifier = [TPBProgressTableCell cellIdentifier:isRTL]; TPBProgressTableCell *cell; if (TPBA11yHelper.isVoiceOverOn) { cell = [[TPBProgressTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } [cell updateCellModel:cellModel]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; return cell; } } break; case TPBTableCellTypeCommonInput: { if ([model isKindOfClass:[TPBCommonInputTableCellModel class]]) { TPBCommonInputTableCellModel *cellModel = (TPBCommonInputTableCellModel *)model; NSString *cellIdentifier = [TPBCommonInputTableCell cellIdentifier:isRTL]; TPBCommonInputTableCell *cell; if (TPBA11yHelper.isVoiceOverOn) { cell = [[TPBCommonInputTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } cell.inputDelegate = self; [cell updateCellModel:cellModel]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; return cell; } } break; case TPBTableCellTypeCheck: { if ([model isKindOfClass:[TPBCheckTableCellModel class]]) { TPBCheckTableCellModel *cellModel = (TPBCheckTableCellModel *)model; BOOL isCustomizedCellHeight = cellModel.height.isCustomHeight; NSString *cellIdentifier = [TPBCheckTableCell cellIdentifier:isRTL]; TPBCheckTableCell *cell; if (TPBA11yHelper.isVoiceOverOn) { cell = [[TPBCheckTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } [cell updateCellModel:cellModel isCustomizedCellHeight:isCustomizedCellHeight]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; return cell; } } break; case TPBTableCellTypeJumpSelect: { if ([model isKindOfClass:[TPBJumpSelectTableCellModel class]]) { TPBJumpSelectTableCellModel *cellModel = (TPBJumpSelectTableCellModel *)model; NSString *cellIdentifier = [TPBJumpSelectTableCell cellIdentifier:isRTL]; TPBJumpSelectTableCell *cell; if (TPBA11yHelper.isVoiceOverOn) { cell = [[TPBJumpSelectTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } [cell updateCellModel:cellModel]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && TPBIsEmptyString(cellModel.title) && !cellModel.hideCellSeparator]; return cell; } } break; case TPBTableCellTypeMenuSelect: { if ([model isKindOfClass:[TPBMenuSelectTableCellModel class]]) { TPBMenuSelectTableCellModel *cellModel = (TPBMenuSelectTableCellModel *)model; NSString *cellIdentifier = [TPBMenuSelectTableCell cellIdentifier:isRTL]; TPBMenuSelectTableCell *cell; if (TPBA11yHelper.isVoiceOverOn) { cell = [[TPBMenuSelectTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } [cell updateCellModel:cellModel]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; return cell; } } break; case TPBTableCellTypeListInput: { if ([model isKindOfClass:[TPBListInputTableCellModel class]]) { TPBListInputTableCellModel *cellModel = (TPBListInputTableCellModel *)model; NSString *cellIdentifier = [TPBListInputTableCell cellIdentifier:isRTL]; TPBListInputTableCell *cell; if (TPBA11yHelper.isVoiceOverOn) { cell = [[TPBListInputTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } cell.inputDelegate = self; [cell updateCellModel:cellModel]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; return cell; } } break; case TPBTableCellTypeTitleAction: { if ([model isKindOfClass:[TPBTitleActionTableCellModel class]]) { TPBTitleActionTableCellModel *cellModel = (TPBTitleActionTableCellModel *)model; NSString *cellIdentifier = [TPBTitleActionTableCell cellIdentifier:isRTL]; TPBTitleActionTableCell *cell; if (TPBA11yHelper.isVoiceOverOn) { cell = [[TPBTitleActionTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } [cell updateCellModel:cellModel]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; return cell; } } break; } return [[UITableViewCell alloc] init]; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section < 0 || self.sectionArray.count <= indexPath.section) { return UITableViewAutomaticDimension; } TPBTableSectionModel *sectionModel = self.sectionArray[indexPath.section]; if (indexPath.row < 0 || sectionModel.cellModelArray.count <= indexPath.row) { return UITableViewAutomaticDimension; } TPBBaseTableCellModel *cellModel = sectionModel.cellModelArray[indexPath.row]; if (cellModel.height.isCustomHeight) { return cellModel.height.customHeight; } return UITableViewAutomaticDimension; } // Header - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { if (section < 0 || self.sectionArray.count <= section) { return [UIView new]; } TPBTableSectionModel *sectionModel = self.sectionArray[section]; if (sectionModel.customHeaderView != nil) { return sectionModel.customHeaderView; } if (TPBIsEmptyString(sectionModel.headerTitle) && sectionModel.headerAction == nil) { return [UIView new]; } TPBTableTextSectionHeader *headerView = [TPBTableTextSectionHeader new]; [headerView updateSectionModel:sectionModel]; return headerView; } - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { if (section < 0 || self.sectionArray.count <= section) { return TPBDesign.list.sectionHeaderHeight; } TPBTableSectionModel *sectionModel = self.sectionArray[section]; if (sectionModel.sectionHeaderHeight.isCustomHeight) { return sectionModel.sectionHeaderHeight.customHeight; } if (sectionModel.customHeaderView != nil) { return UITableViewAutomaticDimension; } if (TPBIsEmptyString(sectionModel.headerTitle) && sectionModel.headerAction == nil) { if (self.searchBarShow && section == 0) { return 10; } return TPBDesign.list.sectionHeaderHeight; } return UITableViewAutomaticDimension; } // Footer - (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { if (section < 0 || self.sectionArray.count <= section) { return [UIView new]; } TPBTableSectionModel *sectionModel = self.sectionArray[section]; if (sectionModel.customFooterView != nil) { return sectionModel.customFooterView; } if (TPBIsEmptyString(sectionModel.footerTitle) && sectionModel.footerAction == nil) { return [UIView new]; } TPBTableTextSectionFooter *footerView = [TPBTableTextSectionFooter new]; [footerView updateSectionModel:sectionModel]; return footerView; } - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { if (section < 0 || self.sectionArray.count <= section) { return TPBDesign.list.sectionFooterHeight; } TPBTableSectionModel *sectionModel = self.sectionArray[section]; if (sectionModel.sectionFooterHeight.isCustomHeight) { return sectionModel.sectionFooterHeight.customHeight; } if (sectionModel.customFooterView != nil) { return UITableViewAutomaticDimension; } if (TPBIsEmptyString(sectionModel.footerTitle) && sectionModel.footerAction == nil) { return TPBDesign.list.sectionFooterHeight; } return UITableViewAutomaticDimension; } // TableViewCell点击 - (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:NO]; if (indexPath.section < 0 || self.sectionArray.count <= indexPath.section) { return NO; } TPBTableSectionModel *sectionModel = self.sectionArray[indexPath.section]; if (indexPath.row < 0 || sectionModel.cellModelArray.count <= indexPath.row) { return NO; } TPBBaseTableCellModel *cellModel = sectionModel.cellModelArray[indexPath.row]; switch (cellModel.cellType) { case TPBTableCellTypeCustomCell: case TPBTableCellTypeCustomView: case TPBTableCellTypeTitleSubtitle: case TPBTableCellTypeSwitch: case TPBTableCellTypeCheck: case TPBTableCellTypeListButton: case TPBTableCellTypeJumpSelect: case TPBTableCellTypeMenuSelect: case TPBTableCellTypeProgress: case TPBTableCellTypeTitleAction: return YES; case TPBTableCellTypeCommonInput: case TPBTableCellTypeListInput: return NO; } } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:NO]; if (indexPath.section < 0 || self.sectionArray.count <= indexPath.section) { return; } TPBTableSectionModel *sectionModel = self.sectionArray[indexPath.section]; if (indexPath.row < 0 || sectionModel.cellModelArray.count <= indexPath.row) { return; } if (self.keyboardBehavior == TPBCommonTableKeyboardBehaviorDismissAndRespondClick) { [self tpbHideKeyboard]; } TPBBaseTableCellModel *model = sectionModel.cellModelArray[indexPath.row]; switch (model.cellType) { case TPBTableCellTypeCustomCell: { if ([model isKindOfClass:[TPBCustomTableCellBaseModel class]]) { TPBCustomTableCellBaseModel *cellModel = (TPBCustomTableCellBaseModel *)model; if (cellModel.didSelectCellCallback) { cellModel.didSelectCellCallback(cellModel, indexPath); } } } break; case TPBTableCellTypeCustomView: { if ([model isKindOfClass:[TPBCustomViewTableCellModel class]]) { TPBCustomViewTableCellModel *cellModel = (TPBCustomViewTableCellModel *)model; if (cellModel.didSelectCellCallback) { cellModel.didSelectCellCallback(cellModel, indexPath); } } } break; case TPBTableCellTypeTitleSubtitle: { if ([model isKindOfClass:[TPBTitleSubtitleTableCellModel class]]) { TPBTitleSubtitleTableCellModel *cellModel = (TPBTitleSubtitleTableCellModel *)model; if (cellModel.didSelectCellCallback) { cellModel.didSelectCellCallback(cellModel, indexPath); } } } break; case TPBTableCellTypeSwitch: { if ([model isKindOfClass:[TPBSwitchTableCellModel class]]) { TPBSwitchTableCellModel *cellModel = (TPBSwitchTableCellModel *)model; if (cellModel.switchDidClickHotZoneCallback) { cellModel.switchDidClickHotZoneCallback(); } } } break; case TPBTableCellTypeListButton: { if ([model isKindOfClass:[TPBListButtonTableCellModel class]]) { TPBListButtonTableCellModel *cellModel = (TPBListButtonTableCellModel *)model; if (cellModel.actionEnabled && cellModel.didSelectCellCallback) { cellModel.didSelectCellCallback(cellModel, indexPath); } } } break; case TPBTableCellTypeCheck: { if ([model isKindOfClass:[TPBCheckTableCellModel class]]) { TPBCheckTableCellModel *cellModel = (TPBCheckTableCellModel *)model; if (cellModel.checkEnabled && cellModel.didSelectCellCallback) { cellModel.didSelectCellCallback(cellModel, indexPath); } } } break; case TPBTableCellTypeJumpSelect: { if ([model isKindOfClass:[TPBJumpSelectTableCellModel class]]) { TPBJumpSelectTableCellModel *cellModel = (TPBJumpSelectTableCellModel *)model; if (cellModel.didSelectCellCallback) { cellModel.didSelectCellCallback(cellModel, indexPath); } } } case TPBTableCellTypeTitleAction: { if ([model isKindOfClass:[TPBTitleActionTableCellModel class]]) { TPBTitleActionTableCellModel *cellModel = (TPBTitleActionTableCellModel *)model; if (cellModel.didSelectCellCallback) { cellModel.didSelectCellCallback(cellModel, indexPath); } } } break; case TPBTableCellTypeMenuSelect: break; case TPBTableCellTypeProgress: case TPBTableCellTypeCommonInput: case TPBTableCellTypeListInput: break; } } //- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath{ // if (indexPath.row < 0 || indexPath.row >= self.cloudDeviceList.count) { // return NO; // } // TPBDMECDevice *ecDevice = self.cloudDeviceList[indexPath.row]; // return [self canForgetWithDevice:ecDevice]; //} //- (NSArray<UITableViewRowAction *> *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath { // if (indexPath.row < 0 || indexPath.row >= self.cloudDeviceList.count) { // return @[]; // } // TPBDMECDevice *ecDevice = self.cloudDeviceList[indexPath.row]; // TPBWeakSelf; // UITableViewRowAction *deleteAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:gControllerCloudAccess.controllerCloudAccessForget handler:^(UITableViewRowAction *action, NSIndexPath *indexPath){ // TPBStrongSelf; // [_self popAlertControlForUnbindActionWithECDevice:ecDevice]; // }]; // deleteAction.backgroundColor = [UIColor tpbRed]; // return @[deleteAction]; //} #pragma mark - UIScrollViewDelegate - (void)scrollViewDidScroll:(UIScrollView *)scrollView { [self updateSearchBarPosition]; } #pragma mark - TPBTableSearchHeaderDelegate - (void)tpbTableSearchHeaderHideKeyboard { [self tpbHideKeyboard]; } #pragma mark - TPBTableCommonInputDelegate - (void)tableCommonInputFieldDidClickReturn:(TPBCommonInputView *)inputView textField:(UITextField *)textField { [self handleUserClickReturnForTextField:textField]; } #pragma mark - TPBTableListInputDelegate - (void)tableListInputFieldDidClickReturn:(TPBListInputTableCell *)inputCell textField:(UITextField *)textField { [self handleUserClickReturnForTextField:textField]; } #pragma mark - Private - (void)updateSearchBarPosition { CGFloat tableY = self.tableView.frame.origin.y; CGRect convertedRect = [self.tpbSearchBarPlaceholderView convertRect:self.tpbSearchBarPlaceholderView.bounds toView:self.view]; CGFloat diffY = convertedRect.origin.y - tableY; CGFloat targetConstant = MAX(0, diffY); if (self.searchBarTopConstraint.tpbConstant != targetConstant) { self.searchBarTopConstraint.tpbConstant = targetConstant; } } // 用户点击UITextField键盘Return - (void)handleUserClickReturnForTextField:(UITextField *)textField { if (textField.returnKeyType != UIReturnKeyNext) { [textField resignFirstResponder]; return; } if ([self.tableView focusNextTextField]) { } else { [textField resignFirstResponder]; } } - (void)updateTableHeader { if (self.searchBarShow) { CGFloat searchBarHeight = [self.tpbInnerSearchBar preferredHeight]; self.tpbSearchBarPlaceholderView.frame = CGRectMake(0, 0, self.view.bounds.size.width, searchBarHeight); self.tableView.tableHeaderView = self.tpbSearchBarPlaceholderView; self.searchBarHeightConstraint.tpbConstant = searchBarHeight; } else if (self.customTableHeaderView) { self.tableView.tableHeaderView = self.customTableHeaderView; } else { UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, CGFLOAT_MIN)]; self.tableView.tableHeaderView = view; } } #pragma mark - Property - (TPBEmptyView *)emptyView { if (!_emptyView) { _emptyView = [TPBEmptyView new]; _emptyView.hidden = YES; } return _emptyView; } - (UIView *)tpbSearchBarPlaceholderView { if (!_tpbSearchBarPlaceholderView) { _tpbSearchBarPlaceholderView = [UIView new]; } return _tpbSearchBarPlaceholderView; } - (TPBTableSearchHeader *)tpbInnerSearchBar { if (!_tpbInnerSearchBar) { CGRect frame = CGRectMake(0, 0, self.view.bounds.size.width, 62); _tpbInnerSearchBar = [[TPBTableSearchHeader alloc] initWithFrame:frame]; _tpbInnerSearchBar.delegate = self; _tpbInnerSearchBar.hidden = YES; _tpbInnerSearchBar.backgroundColor = [UIColor tpbBackground]; } return _tpbInnerSearchBar; } - (UITableView *)tableView { if (!_tableView) { _tableView = [[TPKeyboardAvoidingTableView alloc] initWithFrame:CGRectZero style:self.tableViewStyle]; if (@available(iOS 13.0, *)) { _tableView.automaticallyAdjustsScrollIndicatorInsets = YES; } if (@available(iOS 15.0, *)) { UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, CGFLOAT_MIN)]; _tableView.tableHeaderView = view; _tableView.sectionHeaderTopPadding = 0; } _tableView.backgroundColor = [UIColor clearColor]; _tableView.estimatedRowHeight = 72; _tableView.estimatedSectionHeaderHeight = 68; _tableView.showsHorizontalScrollIndicator = NO; _tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag; _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; _tableView.sectionIndexColor = [UIColor tpbTableSectionIndexColor]; _tableView.delegate = self; _tableView.dataSource = self; NSArray<Class> *cellClassArray = @[ [TPBCustomViewTableCell class], [TPBTitleSubtitleTableCell class], [TPBSwitchTableCell class], [TPBProgressTableCell class], [TPBCommonInputTableCell class], [TPBCheckTableCell class], [TPBJumpSelectTableCell class], [TPBMenuSelectTableCell class], [TPBListInputTableCell class], [TPBListButtonTableCell class], [TPBTitleActionTableCell class] ]; for (Class cls in cellClassArray) { if ([cls isSubclassOfClass:[TPBBaseTableViewCell class]]) { NSString *cellIdentifier = [cls cellIdentifier]; [_tableView registerClass:cls forCellReuseIdentifier:cellIdentifier]; NSString *rtlCellIdentifier = [cls cellIdentifier:YES]; [_tableView registerClass:cls forCellReuseIdentifier:rtlCellIdentifier]; NSString *ltrCellIdentifier = [cls cellIdentifier:NO]; [_tableView registerClass:cls forCellReuseIdentifier:ltrCellIdentifier]; } } } return _tableView; } @end
11-29
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值