用系统方法自定义滑动Cell,自定义UITableview左滑

2020-03-24 11:11 来源:未知

实现效果

美高梅网投平台 1

美高梅网投平台 2

发布微博

UITableview滑动菜单从iOS8开始就已经推出,方便的接口和良好的用户体验,成为了iOS区别于安卓的又一个特性,很多App中都使用到了这个特性。不过,系统默认的样式太过简陋,而Apple至今都没有给出友好的自定义方法。查看了许多教程,往往都需要遍历整个tableview,尤其是iOS11后对View的层级进行了调整,使得遍历查找更加麻烦。下面,我将提供一个更取巧的方法给大家。

实现系统原生的删除方法

  • 必须要实现系统原生的两个方法
    1. 方法editingStyleForRowAtIndexPath:为设置表格的样式为删除样式;
    2. 方法titleForDeleteConfirmationButtonForRowAtIndexPath:为编辑时显示的文字,这里讲的是自定义,所以这里的文字可以传空字符串,避免自定义后文字重叠异常显示, 系统默认显示的文字为 "删除";

注意: 这里的传的空字符串的长度决定你自定义后显示多少个按钮的总宽度。

#pragma mark - <UITableViewDelegate>/** * 实现表格系统方法的编辑模式为删除模式 */- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath { return UITableViewCellEditingStyleDelete;}/** * 设置滑出cell后的文字为空字符串 */- (NSString*)tableView:(UITableView*)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath*)indexpath { return @" ";}

#pragma mark - <UITableViewDataSource>- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ static NSString *identifie = @"UITableViewCell"; GJEmployeeCell *cell = [tableView dequeueReusableCellWithIdentifier:identifie]; if  { cell = [[GJEmployeeCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifie]; } //选择的cell WEAKSELF cell.slectedActionBlock = ^(SwipeCellActionType cellActionType, UITableViewCell *cell){ //防止cell出现选中效果 NSIndexPath *editIndex = [tableView indexPathForCell:cell]; [tableView reloadRowsAtIndexPaths:@[editIndex] withRowAnimation:UITableViewRowAnimationNone]; GJEmployeeModel *editEmployee = weakSelf.dataArr[editIndex.row]; if (cellActionType == EditCellType) { //处理编辑按钮事件... } else { //处理删除按钮事件... } }; return cell;}

示例图.jpg

课程目标

  1. 界面搭建
  2. 自定义文字输入框
  3. 自定义显示照片的View
  4. 底部 toolBar 自定义(UIStackView)
  5. 表情键盘(一个复杂的自定义 View 如何一步一步实现出来的)

美高梅网投平台 3

自定义Cell文件的逻辑

  • 关键点:重写cell的 layoutSubviews方法
  • layoutSubviews方法中遍历寻找到滑动完Cell后底部显示的视图, 然后再添加需要自定义的按钮的到底部的视图上;

注意: 这里要根据当前的手机系统版本来区分底部的视图是属于哪种类型的视图:1.在iOS8以下的系统经过亲自测试遍历到底部的视图属于:UITableViewCellDeleteConfirmationView这种类型的视图;2.在iOS8以上的系统经过亲自测试遍历到底部的视图属于:_UITableViewCellActionButton这种类型的视图;

/** * 根据ios系统版本自定义滑动删除按钮 */- layoutSubviews{ [super layoutSubviews]; for (UIView *subview in self.subviews) { for (UIView *subview2 in subview.subviews) { //根据ios系统版本判断滑动删除视图View,  NSString *systemDeleteViewName = nil; if ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) { systemDeleteViewName = @"UITableViewCellDeleteConfirmationView"; } else { systemDeleteViewName = @"_UITableViewCellActionButton"; } if ([NSStringFromClass([subview2 class]) isEqualToString:systemDeleteViewName]) { NSLog(@"subview2 属于什么View====%@",subview2); //防止多次添加自定义按钮 [subview2.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; //subview2的宽度由控制器里面的代理方法(tableView:titleForDeleteConfirmationButtonForRowAtIndexPath:)返回的文字宽度决定 [subview2 addSubview:self.editButton]; self.editButton.width = subview2.width/2; [subview2 addSubview:self.deleteButton]; self.deleteButton.x = CGRectGetMaxX(self.editButton.frame); self.deleteButton.width = self.editButton.width; NSLog(@"subview2====%zd",subview2.subviews.count); } } }}- (UIButton *)editButton{ if (!_editButton) { _editButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 80, self.height)]; _editButton.backgroundColor = UIColorFromRGB; [_editButton setImage:ImageNamed(@"editButton_icon") forState:0]; _editButton.tag = EditCellType; [_editButton addTarget:self action:@selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside]; } return _editButton;}- (UIButton *)deleteButton{ if (!_deleteButton) { _deleteButton = [[UIButton alloc] initWithFrame:CGRectMake(80, 0, 80, self.height)]; _deleteButton.backgroundColor = UIColorFromRGB; [_deleteButton setImage:ImageNamed(@"deleteButton_icon") forState:0]; _deleteButton.tag = DeleteCellType; [_deleteButton addTarget:self action:@selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside]; } return _deleteButton;}- buttonAction:(UIButton *)button{ if (self.slectedActionBlock) { self.slectedActionBlock(button.tag, self); }}

以上实现自定义的方法的关键点在于遍历到Cell底部的视图, 这种遍历的方法可能会随着iOS手机系统的版本不同而Cell层次结构也不同, 因此在最新的iOS10.2以后的版本中可能存在遍历不到的情况, 经过亲自测试,目前最新的iOS10.2以下的版本可用上面的方法亲测有效;

在iOS11之前,如果需要自定义左滑删除的显示样式,可以通过遍历cell的子视图,找到左滑显示出来的视图,直接更改该视图即可

界面搭建

屏幕快照 2018-01-12 10.06.03.png

重写自定义CelllayoutSubviews方法

导航栏内容

  • 标题视图懒加载
// MARK: - 懒加载

/// 顶部标题视图
private lazy var titleView: UILabel = {
    let label = UILabel()
    // 设置多行
    label.numberOfLines = 0
    // 字体大小
    label.font = UIFont.systemFontOfSize(14)
    // 文字居中
    label.textAlignment = NSTextAlignment.Center
    // 如果有用户昵称
    if let name = HMUserAccountViewModel.sharedInstance.userAccount?.name {
        // 初始化一个带有属性的文字
        var attr = NSMutableAttributedString(string: "发微博n(name)")
        // 获取到要添加的属性的范围
        let range = (attr.string as NSString).rangeOfString(name)
        // 添加属性
        attr.addAttribute(NSFontAttributeName, value: UIFont.systemFontOfSize(12), range: range)
        attr.addAttribute(NSForegroundColorAttributeName, value: UIColor.lightGrayColor() ,range: range)
        label.attributedText = attr
    }else{
        label.text = "发微博"
    }
    label.sizeToFit()
    return label
}()
  • 右边按钮懒加载
/// 右边按钮
private lazy var rightButton: UIButton = {
    let button = UIButton()

    // 添加点击事件
    button.addTarget(self, action: "send", forControlEvents: UIControlEvents.TouchUpInside)

    // 设置文字属性
    button.titleLabel?.font = UIFont.systemFontOfSize(13)
    button.setTitle("发送", forState: UIControlState.Normal)

    // 设置不同状态的文字
    button.setTitleColor(UIColor.grayColor(), forState: UIControlState.Disabled)
    button.setTitleColor(UIColor.whiteColor(), forState: UIControlState.Normal)

    // 设置不同状态的背景图片
    button.setBackgroundImage(UIImage(named: "common_button_white_disable"), forState: UIControlState.Disabled)
    button.setBackgroundImage(UIImage(named: "common_button_orange"), forState: UIControlState.Normal)
    button.setBackgroundImage(UIImage(named: "common_button_orange_highlighted"), forState: UIControlState.Highlighted)

    // 设置宽高
    button.height = 30
    button.width = 44

    return button
}()
  • 实现 send 方法
@objc private func send(){
    printLog("发送")
}
  • 设置导航栏内容
// 设置导航栏内容
private func setupNav(){
    // 设置左边 Item
    navigationItem.leftBarButtonItem = UIBarButtonItem.item(title: "返回", target: self, action: "back")
    // 设置中间 titleView
    navigationItem.titleView = titleView
    // 设置右边 Item
    navigationItem.rightBarButtonItem = UIBarButtonItem(customView: rightButton)
    // 默认为不可用状态
    navigationItem.rightBarButtonItem?.enabled = false
}

运行测试

方法其实很简单,我们在StoryBoard或XIB中画控件的时候,将我们的自定义View添加到Cell的右侧,给定约束,其中最重要的是该自定义View到Cell右侧的距离。根据AutoLayout的设定,无论Cell在屏幕的哪里,右侧的自定义View都会保持在Cell右侧与其保持一个固定的距离。这样,当我们向左拖动Cell的时候,右侧的View也会被一并拖过来。就是这!么!简!单!
剩下的工作比较简单,我们需要给UITableview添加editActions事件,否则Cell是无法拖动的。创建的UITableViewRowAction需要将backgroundColor设置为UIColor(red: 0, green: 0, blue: 0, alpha: 0)(不能设置为Clear,会显示灰色)。

// TableViewCell.m

- (void)layoutSubviews {
    [super layoutSubviews];
    for (UIView *subView in self.subviews) {
        if ([subView isKindOfClass:NSClassFromString(@"UITableViewCellDeleteConfirmationView")]) {
            subView.backgroundColor = [UIColor colorWithHEXString:@"#F2F2F4"];

            for (UIView *btnView in subView.subviews) {
                if ([btnView isKindOfClass:NSClassFromString(@"_UITableViewCellActionButton")]) {
                    UIButton *btn = (UIButton *)btnView;
                    btn.bounds = CGRectMake(0, 0, 50, 50);
                    btn.backgroundColor = [UIColor whiteColor];
                    btn.layer.cornerRadius = 25;
                    [btn setBackgroundImage:[UIImage imageNamed:@"collection_item_delete"] forState:UIControlStateNormal];

                    // 移除标题
                    for (UIView *view in btn.subviews) {
                        if ([view isKindOfClass:NSClassFromString(@"UIButtonLabel")]) {
                            [view removeFromSuperview];
                            break;
                        }
                    }
                }
            }
        }
    }
}

文字输入框

  1. 带有占位文字
  2. 可以像 UITextView 一样输入多行
  3. 自定义一个输入框继承于 UITextView,向里面添加一个 label
  • 代码实现
class HMTextView: UITextView {

    /// 重写的是指定构造函数
    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)

        // 添加占位控件
        addSubview(placeholderLabel)

        // 添加约束
        placeholderLabel.snp_makeConstraints { (make) -> Void in
            make.width.lessThanOrEqualTo(self.snp_width).offset(-10)
            make.leading.equalTo(self.snp_leading).offset(5)
            make.top.equalTo(self.snp_top).offset(8)
        }
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // 占位文字控件
    private lazy var placeholderLabel: UILabel = {
        let label = UILabel()
        // 设置文字颜色以及大小
        label.font = UIFont.systemFontOfSize(12)
        label.textColor = UIColor.lightGrayColor()
        label.text = "请输入文字"

        // 多行
        label.numberOfLines = 0
        return label
    }()
}
  • 添加到 controller 中使用
// 懒加载控件
private lazy var textView: HMTextView = {
    let textView = HMTextView()
    return textView
}()

// setupUI 方法中添加子控件并设置约束

view.addSubview(textView)
textView.snp_makeConstraints { (make) -> Void in
    make.edges.equalTo(self.view.snp_edges)
}

运行测试

  • HMTextView 中提供给外界设置占位文字的属性
// 添加 placeholder 属性,代外界设置值
var placeholder: String? {
    didSet{
        placeholderLabel.text = placeholder
    }
}
  • 重写 font 属性,以让占位文字与输入的文字字体大小一样
override var font: UIFont? {
    didSet{
        placeholderLabel.font = font
    }
}
  • 外界设置文字大小
textView.font = UIFont.systemFontOfSize(16)

运行测试:占位文字与输入的文字一样大

  • 监听文字改变的时候,去执行占位控件的隐藏与显示逻辑
// 监听文字改变的通知
NSNotificationCenter.defaultCenter().addObserver(self, selector: "textDidChange", name: UITextViewTextDidChangeNotification, object: self)
  • 文字改变之后调用的方法
/// 文字改变的时候会调用这个方法,当前如果有文字的话就隐藏占位 label
@objc private func textDidChange(){
    placeholderLabel.hidden = hasText()
}

运行测试。注:监听文字改变在这个地方不要使用代理,因为自己一般不成为自己的代理。

func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
        let action = UITableViewRowAction(style: .normal, title: nil) { (action, index) in
            self.delete(indexPath.row)
        }
        action.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0)
        return [action]
    }

底部 ToolBar 初始化

//设置约束
        toolBar.snp_makeConstraints { (make) -> Void in
            make.left.right.bottom.equalTo(self.view)
        }
        var items = [UIBarButtonItem]()
        //添加 UIBarButtonItem类型的对象到数据源数组中
        let itemSettings = [["imageName": "compose_toolbar_picture","actionName": "selectPicture"],
            ["imageName": "compose_mentionbutton_background"],
            ["imageName": "compose_trendbutton_background"],
            ["imageName": "compose_emoticonbutton_background", "actionName": "selectEmoticon"],
            ["imageName": "compose_add_background"]]

        for item in itemSettings {
            let imageName = item["imageName"]
            let btn = UIButton()
            btn.setImage(UIImage(named: imageName!), forState: .Normal)
            btn.setImage(UIImage(named: imageName!   "_highlighted"), forState: .Highlighted)
            btn.sizeToFit()
            if let actionName = item["actionName"] {
                btn.addTarget(self, action: Selector(actionName), forControlEvents: .TouchUpInside)
            }

            let barItem = UIBarButtonItem(customView: btn)
            //添加到数组中
            items.append(barItem)
            //添加弹簧类型的item  FlexibleSpace: 可伸缩的弹簧
            let space = UIBarButtonItem(barButtonSystemItem: .FlexibleSpace, target: nil, action: nil)
            items.append(space)
        }

        items.removeLast()
        toolBar.items = items
  • HMComposeViewController 中懒加载控件
/// composeToolBar
private lazy var composeToolBar: HMComposeToolBar = HMComposeToolBar(frame: CGRectZero)
  • HMComposeViewControllersetupUI 方法中添加控件与约束
view.addSubview(composeToolBar)

// 添加约束
composeToolBar.snp_makeConstraints { (make) -> Void in
    make.bottom.equalTo(self.view.snp_bottom)
    make.width.equalTo(self.view.snp_width)
    make.height.equalTo(44)
}

这时候重新运行App,你可能看到的还是一片空白,并没有任何自定义View显示出来。要知道为什么,我们需要了解ClipToBounds属性。很多人在切圆角的时候用过它,他的功能就是将View边界之外的View裁切掉。我们回到StoryBorad或XIB,分别点击UITableViewCell和它的ContentView,将它们的ClipToBounds属性取消,这样,即便是在它们边界之外的View(例如我们的自定义View)也能被渲染出来。

但是在iOS11之后左滑删除的视图层级发生了变化。该视图不再属于Cell,而是属于tableView。
所以再更改该样式时需要在tableView上面来进行更改。

底部 ToolBar 跟随键盘移动

  • 监听键盘 frame 改变通知
// 监听键盘 frame 改变通知
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillChangeFrame:", name: UIKeyboardWillChangeFrameNotification, object: nil)
  • 注销通知
deinit{
    NSNotificationCenter.defaultCenter().removeObserver(self)
}
  • 在键盘 frame 改变做更新约束的逻辑
/// 键盘 frame 改变通知调用的方法
@objc private func keyboardWillChangeFrame(noti: NSNotification){

    let endFrame = (noti.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()

    // 更新约束
    composeToolBar.snp_updateConstraints { (make) -> Void in
        make.bottom.equalTo(self.view.snp_bottom).offset(endFrame.origin.y - self.view.height)
    }

    UIView.animateWithDuration(0.25) { () -> Void in
        self.composeToolBar.layoutIfNeeded()
    }
}
  • 拖动 textView 的时候退下键盘:打开 textView 垂直方向弹簧效果,并设置代理
textView.alwaysBounceVertical = true
textView.delegate = self
  • 实现协议,并实现协议方法
func scrollViewDidScroll(scrollView: UIScrollView) {
    self.view.endEditing(true)
}
  • 实现 textViewDidChange 的方法,当textView有文字输入的时候右边按钮可用
func textViewDidChange(textView: UITextView) {
    //设置占位的文本的隐藏或者显示
        placeholderLabel.hidden = textView.hasText()
        //设置 发布按钮的 交互 和不可交互状态
        //有文本就允许交互
        navigationItem.rightBarButtonItem?.enabled = textView.hasText()
}

美高梅网投平台 4

视图层级改变为 UITableView -> UISwipeActionPullView

选择照片

屏幕快照 2018-01-12 10.25.53.png

所以更改该界面的方法如下

目标

  • 在独立的项目中开发独立的功能,或者直接切换根控制器
  • 开发完毕后再整合到现有项目中
  • 提高工作效率,专注开发品质
    TAG标签:
版权声明:本文由美高梅网投平台发布于美高梅网投网址,转载请注明出处:用系统方法自定义滑动Cell,自定义UITableview左滑