美文网首页Swift见解有些文章不一定是为了上首页投稿iOS学习笔记
Swift中优雅的为UIButton添加链式的Block点击事件

Swift中优雅的为UIButton添加链式的Block点击事件

作者: BennyLoo | 来源:发表于2018-04-14 14:15 被阅读80次

    UIButton是基于 action - target 的事件机制处理点击事件的。通常,如果我们需要添加一个 UIButton 的点击事件的时候,一般会这么做:

      btn.addTarget(self, action: #selector(touchUpInSideBtnAction), for: .touchUpInside)
    

    当然,可能对于同一个 button 我们可能会添加不止一个状态的 action ,比如再添加一个状态事件:

     btn.addTarget(self, action: #selector(touchUpOutsideBtnAction), for: .touchUpOutside)
    

    除此之外,我们还需要给 button 添加两个接受事件:

        @objc func touchUpInSideBtnAction(btn: UIButton) {
                print("你好呀","addTouchUpInSideBtnAction")
        }
    
        @objc func touchUpOutsideBtnAction(btn: UIButton) {
                print("addTouchUpOutsideBtnAction")
        }
    

    这种方式将事件相关的分离到另一个地方,虽然高度解耦, 但是,有时候我们只需要点击按钮,然后打印一段小小的内容,并不希望将代码做的这么复杂。聪明的你一下子想到了,将 UIButton 的点击事件做成 Block 不就行了。封装一个 UIButton 的子类,给它添添加一个回调事件的 Block ,再给子类写一个方法,参数带有一个闭包,这个闭包就是 UIButton 被点击时执行的内容。大概就像这样:

    typealias BtnAction = (UIButton)->()
    
    class SubButton{
        var action : ((UIButton)->())? = nil
        
       // 添加 事件
        func DIY_button_add(action:@escaping  BtnAction ,for controlEvents: UIControlEvents){
           self.action = action
            btn.addTarget(self, action: #selector(touchUpInSideBtnAction), for: .touchUpInside)
        }
    
        //事件处理
        @objc func touchUpInSideBtnAction(btn: UIButton) {
            if let act = self.action {
                act()
            }
        }
    }
    
    

    这个办法确实可以,但是稍微有点经验的开发者就会说:如果想给已经存在的 UIButton 添加这个方法该怎么办? 总不可能一个一个的去替换类吧,不仅耗时耗力,关键是容易遗漏。被老大知道要被打的。
    你:额....
    然后你突然灵光一闪,如果放在 extension 中实现这个方法就行了,这样就不需要创建什么子类了。因为在 extension 并不能添加存储属性,所以你想到了在 extension 中使用 runtime 关联一个属性,这个属性为一个闭包。然后你呼哧呼哧写出了下面的代码:

    typealias BtnAction = (UIButton)->()
    
    extension UIButton{
        private struct AssociatedKeys{
            static var actionKey = "actionKey"
        }
        
        @objc dynamic var action: BtnAction? {
            set{
                objc_setAssociatedObject(self,&AssociatedKeys.actionKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_COPY)
            }
            get{
                if let action = objc_getAssociatedObject(self, &AssociatedKeys.actionKey) as? BtnAction{
                    return action
                }
                return nil
            }
        }
    
        func DIY_button_add(action:@escaping  BtnAction) {
            self.action = action
            self.addTarget(self, action: #selector(touchUpInSideBtnAction), for: .touchUpInside)
        }
    
        @objc func touchUpInSideBtnAction(btn: UIButton) {
             if let action = self.action {
                 action()
             }
        }
    }
    

    这时候,你可能有点沾沾自喜了,这简直就是完美嘛! 哈哈。 当然,其实到了这里,你确实已经做完了关于 UIButton 添加 Block 点击事件的大部分工作。

    然而,有一天,PM跑过来跟你说,除了 touchUpInside 点击事件,他还想要增加其他的事件状态方法。你也不知道他是不是拆台,他觉得他可能会使用到 UIButton 各个状态的点击状态,比如touchUpOutsidetouchDragOutsidetouchDragInside等。因为,我们只关联了一个属性,如果是这样,我们可能需要关联很多属性,这不是最麻烦的,更为尴尬的是,有可能我们只需要使用到其中一两个属性,但是却要事先关联好所有的属性。这时候,面对 PM 你有种“大刀饥渴难耐”的感觉了。

    最后,你忍住了抽出你80米大刀的冲动。因为你突然想到一个好办法可以很轻松的应付。对,将 extension 中关联的属性设置成一个字典,然后将每个按钮的状态作为 Key ,将每一个状态对应的的动作作为 value 保存起来。每次添加事件,会将对应的事件保存进这个字典中。你的代码改进之后就像这样:

    typealias BtnAction = (UIButton)->()
    
    extension UIButton{
    
    ///  gei button 添加一个属性 用于记录点击tag
       private struct AssociatedKeys{
          static var actionKey = "actionKey"
       }
        
        @objc dynamic var actionDic: NSMutableDictionary? {
            set{
                objc_setAssociatedObject(self,&AssociatedKeys.actionKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_COPY)
            }
            get{
                if let dic = objc_getAssociatedObject(self, &AssociatedKeys.actionKey) as? NSDictionary{
                    return NSMutableDictionary.init(dictionary: dic)
                }
                return nil
            }
        }
    
         @objc dynamic fileprivate func DIY_button_add(action:@escaping  BtnAction ,for controlEvents: UIControlEvents) {
            let eventStr = NSString.init(string: String.init(describing: controlEvents.rawValue))
            if let actions = self.actionDic {
                actions.setObject(action, forKey: eventStr)
                self.actionDic = actions
            }else{
                self.actionDic = NSMutableDictionary.init(object: action, forKey: eventStr)
            }
            
            switch controlEvents {
                case .touchUpInside:
                    self.addTarget(self, action: #selector(touchUpInSideBtnAction), for: .touchUpInside)
                case .touchUpOutside:
                    self.addTarget(self, action: #selector(touchUpOutsideBtnAction), for: .touchUpOutside)
               .
               .
               .
             }
          }
    
          @objc fileprivate func touchUpInSideBtnAction(btn: UIButton) {
              if let actionDic = self.actionDic  {
                   if let touchUpInSideAction = actionDic.object(forKey: String.init(describing: UIControlEvents.touchUpInside.rawValue)) as? BtnAction{
                      touchUpInSideAction(self)
                   }
              }
          }
    
          @objc fileprivate func touchUpOutsideBtnAction(btn: UIButton) {
             if let actionDic = self.actionDic  {
                if let touchUpOutsideBtnAction = actionDic.object(forKey:   String.init(describing: UIControlEvents.touchUpOutside.rawValue)) as? BtnAction{
                    touchUpOutsideBtnAction(self)
                }
             }
          }
       }
    

    嗯,虽然代码多了点,但是目前已经很容易就能将所有的状态的点击事件都加入进来了。只要想加入更多的状态的时候,添加对应状态的实现就好了。

    最后,为了代码更加的优雅一点,你对上面的代码做了一点点的改变,让每一个点击事件都返回自身,这样就为链式调用形成了可能,这里注意使用 @discardableResult 关键字为函数消除警告:

        @discardableResult
        func addTouchUpInSideBtnAction(_ action:@escaping BtnAction) -> UIButton{
            self.DIY_button_add(action: action, for: .touchUpInside)
            return self 
        }
        @discardableResult
        func addTouchUpOutSideBtnAction(_ action:@escaping BtnAction) -> UIButton{
                self.DIY_button_add(action: action, for: .touchUpOutside)
               return self 
        }
    

    最后,你在项目中使用起来就像这样:


    按钮点击事件添加

    //测试结果


    打印结果

    对于如何给 UIView 添加 Block 手势事件也是这样思路。

    相关文章

      网友评论

        本文标题:Swift中优雅的为UIButton添加链式的Block点击事件

        本文链接:https://www.haomeiwen.com/subject/fhnkkftx.html