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