- When using
UINavigationController, we typically invokepopViewControllerthrough an action event or simply by swiping back from left to right. This process automatically deallocates thetopViewControllerof theUINavigationController, eliminating the need for manual memory management. - When I attempted this with the RIBs framework, I encountered some issues. The
detachChildmethod of the router cannot be called directly because the framework does not capture the pop swipe gesture. This limitation leads to a memory leak bug. - Unfortunately, the RIBs framework's examples do not include any navigation examples or solutions. As a result, some developers typically resort to using the
viewDidAppearmethod ofUIViewControllerfor supporting pop swipe gesture. - However, I believe that such implementations indicate a lack of proper understanding of the method's intended purpose. This approach can increase complexity and lead to unexpected errors.
- Let’s solve the problem with UIViewController’s life cycle.
- Let’s handle all navigation cases.
- Simply call the
popViewControllermethod. - Support swipe-back gesture.
- Support multiple view controllers deallocation case when either the
UITabbarbutton is clicked or the navigation back button’s long click occured.
- Simply call the
- Minimize the implementation requirements to the end users.
-
First, We have to know the basic parts of a RIBs.
- Router: A Router listens to the Interactor and translates its outputs into attaching and detaching child RIBs.
- View(View Controller): Views build and update the UI. Views are designed to be as “dumb” as possible. They just display information.
-
Second, We have to know the life cycle of
UINavigationController.- When UINavigationController pops the UIViewController-C, the delegate method
navigationController(_:didShow:animated:)is called, with thedidShowparameter specifically indicating the UIViewController that was shown.
- When UINavigationController pops the UIViewController-C, the delegate method
-
As a result,
- When the
navigationController(_:didShow:animated:)is called, it is necessary to locate the RIB family(especially the Router) associated with thedidShowUIViewController. Subsequently, we should invoke thedetachmethod to remove child elements from the memory.
- When the
-
As a code
-
Create a common
UINavigationControllerand set it to delegate to itself. We define acachedViewControllerproperty to calldetachmethod. -
When the
navigationController(_:didShow:animated:)is called, we handle it based on whether it confirms to the RIBs framework.public class CommonRIBNavigationController: UINavigationController, UINavigationControllerDelegate { private var cachedViewController: [UIViewController] = [] public override func viewDidLoad() { super.viewDidLoad() self.delegate = self } public override func pushViewController(_ viewController: UIViewController, animated: Bool) { super.pushViewController(viewController, animated: animated) if cachedViewController.contains(viewController) == false { cachedViewController.append(viewController) } } public func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { guard cachedViewController.count > children.count else { return } guard let currentIndex = cachedViewController.firstIndex(of: viewController), cachedViewController.count > currentIndex + 1 else { return } // When the last dismissed view controller is kind of a RIBs and CommonRIBsViewController. guard cachedViewController[currentIndex + 1] is ViewControllable, let current = cachedViewController[currentIndex] as? CommonRIBsViewController else { cachedViewController = Array(cachedViewController[0...currentIndex]) return } current.resourceFlusher?.flushRIBsResources() cachedViewController = Array(cachedViewController[0...currentIndex]) }
-
Create a common
UIViewControllerandRouter(RIBs).- CommonRIBsViewController: In this scenario, it must identify the appropriate router to release the resources of the child RIBs.
- CommonRIBsRouter: Using common router is mandatory to find and release RIBs resources.
public protocol CommonRIBsInteractable: Interactable { var resourceFlusher: CommonRIBsResourceFlush? { get } } public protocol CommonRIBsResourceFlush { func flushRIBsResources() } public class CommonRIBsViewController: UIViewController, ViewControllable { public var resourceFlusher: CommonRIBsResourceFlush? { nil } } public class CommonRIBsRouter<InteractorType, ViewControllerType>: ViewableRouter<InteractorType, ViewControllerType>, CommonRIBsResourceFlush { public var nextScreenRouter: ViewableRouting? /** Easy short-cut push method to use same router. */ @discardableResult public func push(nextRouter: ViewableRouting?, animated: Bool) -> Bool { if nextScreenRouter != nil { flushRIBsResources() } guard let next = nextRouter else { return false } nextScreenRouter = next nextScreenRouter?.interactable.activate() nextScreenRouter?.load() viewControllable.uiviewController.navigationController?.pushViewController(next.viewControllable.uiviewController, animated: animated) return true } /** Deactivate RIBs resource and set nil */ public func flushRIBsResources() { nextScreenRouter?.interactable.deactivate() nextScreenRouter = nil } }
-
Example
final class MasterViewController: CommonRIBsViewController, MasterPresentable { override var resourceFlusher: CommonRIBsResourceFlush? { guard let listener = listener as? CommonRIBsInteractable else { return nil } return listener.resourceFlusher } // ... Do other things... } protocol MasterInteractable: CommonRIBsInteractable { // ... Do other things... } final class MasterInteractor: PresentableInteractor<MasterPresentable>, MasterInteractable { public var resourceFlusher: CommonRIBsResourceFlush? { router as? CommonRIBsResourceFlush } // ... Do other things... } final class MasterRouter: CommonRIBsRouter <MasterInteractable, ViewControllable>, MasterRouting, LaunchRouting { // ... Do other things... func routeToDetail(at case: MasterExampleCase) { let detail = DetailBuilder(dependency: EmptyComponent()).build(`case`) // Use common push method.` push(nextRouter: detail, animated: true) } }
-


