-
Notifications
You must be signed in to change notification settings - Fork 81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Interop with UINavigationController user instantiated popping #74
Interop with UINavigationController user instantiated popping #74
Conversation
…ed added for PopWasIgnored
Codecov Report
@@ Coverage Diff @@
## master #74 +/- ##
===========================================
- Coverage 80.28% 64.98% -15.31%
===========================================
Files 5 6 +1
Lines 208 257 +49
===========================================
Hits 167 167
- Misses 41 90 +49
Continue to review full report at Codecov.
|
@Ben-G: what do you think about it? |
Hi @ambientlight! Thanks a lot for the PR, it's great that you're trying to solve this problem!
Once again, thx for the PR 🎉 |
Let me make a sample project for it.
Makes sense. Should we continue this conversation here or close and inside #17? |
@ambientlight Happy to keep the conversation here, until we've decided where the API should live! Thanks a lot for looking into a sample project. And to clarify: how did you come up with this solution? Because you saw the open issue here or because you solved the issue in your own project? |
@Ben-G I didn't like it this way, I thought it is possible to do it transparently - incorporate pop action inside navigationController, so I went to see how you guys do it, and since this wasn't yet resolved, I basically propose such implementation here. My point to provide it within |
@Ben-G: Brief set of changes:
class RootNavigationController: NavigationController {
override func popRoute() {
if let popRouteAction = popRouteAction(){
store.dispatch(popRouteAction)
}
}
} where popRouteAction is: func popRouteAction() -> SetRouteAction? {
let currentRoute = store.state.navigationState.route
guard currentRoute.startIndex != currentRoute.endIndex else {
return nil
}
return SetRouteAction([RouteElementIdentifier](currentRoute[currentRoute.startIndex ..< currentRoute.endIndex.advanced(by: -1)]))
}
(self.viewController as? UINavigationController)?.popViewController(true, completion: completionHandler) Please take a look and let me know if you have any questions. |
@Ben-G What do you think? 🙂 |
Hey @ambientlight! Thanks a lot for putting this example together. It seems like you didn't add the new files to the Xcode project in the commit that you are pointing to? I have the source files in the repo, but they are not included in the project. Overall I put more thought into this; I think this is a great starting point for a third-party library, but it is definitely outside of the scope of the core of It would be really cool if you could build this as a standalone library (the code already has no real dependencies on ReSwift/ReSwift-Router), and I'd be more than happy to reference it. I'd also be curious to see if there's a solution that doesn't require subclassing I will close this issue for now; but please let me know if you happen to build a separate library that I can reference in future. Thanks a lot for the work on this 😃 (I'll also make sure to take some ideas from this approach if I update the GitHub sample project in future). |
@Ben-G Will definitely do so. Let me get back to it and then I will probably open the issue for it once it is done. Solution with Did I forget something? that's weird, will check. Thanks a lot for the review and suggestions! |
@ambientlight have you created a third-party library for this? |
@hlineholm-flir: Thanks for reminding me, I will do my best to publish it soon (next week hopefully).
import UIKit
// dummy view controller returned when popViewController is canceled
public final class PopWasIgnored: UIViewController {}
open class ReNavigationController: UINavigationController {
fileprivate var isSwipping: Bool = false
// indicates that backButton was pressed when set to false
fileprivate var isPerformingPop: Bool = false
override open func viewDidLoad() {
super.viewDidLoad()
self.interactivePopGestureRecognizer?.addTarget(self, action: #selector(NavigationController.handlePopSwipe))
self.delegate = self
}
override open func popViewController(animated: Bool) -> UIViewController? {
// when swipping we are discarding all subsequent popViewController calls
guard !self.isSwipping else {
return PopWasIgnored()
}
self.isPerformingPop = true
return super.popViewController(animated: animated)
}
// will be called after popViewController call
func handlePopSwipe(){
self.isSwipping = true
}
/// should be overriden
/// normally you should dispatch SetRouteAction here
open func popRoute(){
print("WARNING: \(#function) should to be overriden")
}
}
extension NavigationController: UINavigationControllerDelegate {
public func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
self.isSwipping = false
}
}
extension NavigationController: UINavigationBarDelegate {
// if overriden navigationController popViewController won't be called before this method
// on back button press
// isPerformingPop will be false here in this case
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
defer { isPerformingPop = false }
// changeRoute is performed:
// 1. back button was pressed
// 2. pop swipe was triggerred
if !isPerformingPop || self.isSwipping {
self.popRoute()
}
// don't remove the navigationItem if navigationController
// is not going to be popped
return isPerformingPop
}
}
extension UINavigationController {
open func pushViewController(_ viewController: UIViewController, animated: Bool, completion: @escaping () -> Void) {
self.pushViewController(viewController, animated: animated)
guard animated, let coordinator = self.transitionCoordinator else {
completion()
return
}
coordinator.animate(alongsideTransition: nil) { _ in completion() }
}
open func popViewController(animated: Bool, completion: @escaping () -> Void) -> UIViewController? {
let popped = self.popViewController(animated: animated)
guard animated, let coordinator = self.transitionCoordinator else {
completion()
return popped
}
coordinator.animate(alongsideTransition: nil) { _ in completion() }
return popped
}
} |
Ah! I went ahead and made RoutableUIKit, before reading your comment @ambientlight ... |
@hlineholm-flir: that's great! I am not sure if RoutableUIKIt is a the most intuitive name for it, but let's now add cocoapods and carthage support to it! |
Not sure either about the name there @ambientlight. I've created issues for both creating a CocoaPod and supporting Carthage. Think I can fix it during this week. |
Motivation:
ReSwiftRouter
should provide the optional mechanism that assists in triggering of route pop corresponding actions to the store when:API Proposal:
Users needs to subclass the ReSwiftInit.NavigationController and override popRoute() methods where they specify the dispatch logic to their store. Like:
Implementation details:
When user hits the back button
popViewController()
can be canceled ifnavigationBar(:shouldPop item:)
is implemented. In such casepopRoute()
will be called that should result inSetRouteAction
dispatched. Our routable can then subsequently call the actualpopViewController()
as desired.However, when swiping
popViewContoller()
call cannot be canceled. When swipe is recognized,popViewController()
will be called beforepopRoute()
callback. In order to avoid the redundantpopViewController()
from our routable,popViewController()
semantics was changed to ignore allpopViewController()
calls when swipe is still in progress. This semantic change is reasonable since nothing should be callingpopViewController()
while user is swiping anyway. Also it allows us having our routable transparent of the swiping state. It can instead (if needed) check if pop was canceled since the instance ofPopWasCancelled
dummy viewController would be returned. Like:Additionally the utility navigationController's push/pop with completion were provided.
The sample hypothetical routable can use it like: