美文网首页Swift基础
iOS(Swift) 防重复跳转页面策略

iOS(Swift) 防重复跳转页面策略

作者: 简单coder | 来源:发表于2021-06-27 18:12 被阅读0次

    前提

    业务中有时会有需求,同一个页面我们只希望它在navigationController 的栈内只会出现一次,比如有时候的误触2次点击,比如个人信息页面我们不希望有多个重复 id用户页面叠加等等,在这样的需求,我们可以制定一个策略---防重复跳转.
    下面是简单的效果实现展示,效果虽简陋,但是能实现效果就是我们的目的


    处理前.gif
    处理后.gif

    前置需求

    不管公司的架构如何设计,是否使用路由啥的,最后我们肯定是要接管 push 的

    设计

    打开脑洞,我们可以设计跳转的策略了.
    判定重复前->
    防重复跳转,主要是判断如果才算页面重复,跟重复网络请求一样,我们需要判断页面的初始化参数是否是相同的值,进而判断页面是否重复.
    比如个人中心设置页面,没有必要的参数,我们认为跳转两次属于页面重复.
    比如他人控件,如果参数 userId 不同,我们认为这是不同的页面,从而不需要"干掉"之前的页面

    判定重复后->
    这是我司使用判定重复页面后的跳转策略,然后,闭上眼睛想几分钟,自己思考下如何相对应的设计各个策略的实现 逻辑.


    实现

    我这里实现了两种 大家可以自己可以看看自己喜欢哪种,或者可以找更加好的实现
    1是字符串数组设定重复参数判定

    拖展UIViewController参数

    extension UIViewController {
        @objc var intent: Any? { nil }
    }
    

    举例实现某页面

    class Home2Controller: UIViewController {
        override var intent: Any? {
    //        Intent.pushReplace(["index"])
    //        Intent.popToExisted(["index"])
            Intent.pushExisted(["index", "scrollContainer"])
    //        Intent.pushReplace()
        }
    

    2是 propertyWrapper 包装需要的参数

    @IntentProperty var aaa = "asdasdasd"
    
    protocol Intentable {
        func intentValue() -> Any
    }
    
    @propertyWrapper
    class IntentProperty<T>:Intentable {
        
        private var value: T
        
        init(wrappedValue: T) {
            value = wrappedValue
        }
        
        var wrappedValue: T {
            get {
                value
            }
            set {
                value = newValue
            }
        }
        
        func intentValue() -> Any {
            value
        }
    }
    

    接下来回到路由流程,我设定Home2Controller在点击时重复跳转了两次

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            Home2Controller().push()
            Home2Controller().push()
        }
    

    获取 intent 参数类型

    private static func doPush(_ viewController: UIViewController, nav: UINavigationController, animated: Bool) {
            guard let intent = viewController.intent as? Intent else {
                nav.pushViewController(viewController, animated: animated)
                return
            }
            switch intent {
            case let .popToExisted(intents):
                doPopToExisted(viewController,
                               nav: nav,
                               intents: intents,
                               animated: animated)
            case let .pushReplace(intents):
                doPushReplace(viewController,
                              nav: nav,
                              intents: intents,
                              animated: animated)
            case let .pushExisted(intents):
                doPushExisted(viewController,
                           nav: nav,
                           intents: intents,
                           animated: animated)
            }
        }
    

    这里举例讲一个pushExisted
    遍历nav 的栈控制器,拿到同类控制器,判断是否重复

    for vc in nav.viewControllers {// 遍历栈控制器
        if let containerVC = vc as? RTContainerController,
            let contentVC = containerVC.contentViewController,// RT 包装的话就取contentViewController
            contentVC.classForCoder == viewController.classForCoder,
            contentVC.intentValue(for: intents).equalTo(viewController.intentValue(for: intents)) {// 核心判断代码
            existed = contentVC
            break
        } else if vc.classForCoder == viewController.classForCoder,
                  vc.intentValue(for: intents).equalTo(viewController.intentValue(for: intents)) {
            existed = vc
            break
        }
    }
    

    核心判断是否重复
    contentVC.intentValue(for: intents).equalTo(viewController.intentValue(for: intents))
    利用 Mirror 取 intent 的 Value 组装出Dict,这里注意一点,普通的属性 children.label 是正常的key 值,但是用propertyWrapper 包装后的值是 "_key"

    func intentValue(for intents: [String]? = nil) -> Dict {
        if let intents = intents {// 手动传值
            let mirror = Mirror(reflecting: self)
            
            var intentDict = Dict()
            for child in mirror.children {
                let label = child.label ?? ""
                guard intents.contains(label) else { continue }
                intentDict[label] = child.value
            }
            log(intentDict)
            return intentDict
        } else { // propertyWrapper
            let mirror = Mirror(reflecting: self)
            
            var intentDict = Dict()
            for child in mirror.children {
                guard let intent = child.value as? Intentable else { continue }
                let label = child.label?.substring(fromIndex: 1) ?? ""
                let intentValue = intent.intentValue()
                intentDict[label] = intentValue
            }
            log(intentDict)
            return intentDict
        }
    }
    

    判断出是重复页面后,最后是跳转策略

    PopToExisted

    if let popTo = popTo {//如果存在就 popTo
        nav.popToViewController(popTo, animated: animated)
    } else {
        nav.pushViewController(viewController, animated: animated)
    }
    

    PushReplace

    if let existed = existed {// 如果存在就将之前的页面干掉
        if let nav = nav as? RTRootNavigationController {
            nav.removeViewController(existed, animated: false)
        } else {
            nav.setViewControllers(nav.viewControllers.filter { $0 != existed }, animated: false)
        }
    }
    nav.pushViewController(viewController, animated: animated)// 执行跳转策略
    

    PushExisted

    if let existed = existed {
        if let nav = nav as? RTRootNavigationController {
            if nav.rt_visibleViewController == existed {//如果在栈顶则不操作
                return
            }
            nav.removeViewController(existed, animated: false)
        } else {
            nav.setViewControllers(nav.viewControllers.filter { $0 != existed }, animated: false)
        }
    }
    if let existed = existed {//重新跳转一次
        nav.pushViewController(existed, animated: true)
    } else {// 正常跳转
        nav.pushViewController(viewController, animated: true)
    }
    

    propertyWrapper 修饰intent会占用掉这次珍贵的修饰机会, swift 现在目前不能像 java 那样可以重复修饰.但是用[String]需要注意写对属性名,所以这个使用啥就见仁见智.

    PS: 补充下 wildog 大神的策略
    他使用的反射 mirror,先是拿到控制器,使用 mirror 对路由解析出来的 query转成的Dict进行反射赋值到控制器,省去了手动赋值的过程,最后拿到了 intent 参数进行比较判断.我由于主要想讲防重复跳转,所以并不想将路由参数赋值拿到代码中,就省去了这一步骤.还有一点,我个人觉得远程路由最后还是解析成本地路由,真真切切地调用一次本地的路由才比较安心,可能是我境界比较低.不过这并不影响我对 wildog 的崇拜
    wildog~~永远滴神 O(∩_∩)O~

    近半年一直在写业务,而且事情太忙了,加班时间也需要去写业务,而且成为了常态,导致我脑子越来越愚钝,只想用最普通最快捷的方式去实现.其实我不反对加班,在我心里,可以在某一阶段或者某段时间,为了冲击一些公司需求而加班写业务,但是我很不喜欢这种加班写业务成为常态,每天的加班都只是为了完成业务,我也想,加班的时间,自己也能慢慢优化框架,提升框架响应,优化项目架构,毕竟框架的构建是能真实地提升coding能力.这种加班会让我十分的快乐,说真的,写业务写到十点,再去搞技术学技术,真没那个精力,唉~,希望后面能好点吧

    相关文章

      网友评论

        本文标题:iOS(Swift) 防重复跳转页面策略

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