Swift基础

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

2021-06-27  本文已影响0人  简单coder

前提

业务中有时会有需求,同一个页面我们只希望它在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能力.这种加班会让我十分的快乐,说真的,写业务写到十点,再去搞技术学技术,真没那个精力,唉~,希望后面能好点吧

上一篇 下一篇

猜你喜欢

热点阅读