RxSwiftCommunity / RxFlow

RxFlow is a navigation framework for iOS applications based on a Reactive Flow Coordinator pattern
MIT License
1.88k stars 118 forks source link

TabBarController #106

Closed seyhagithub closed 1 year ago

seyhagithub commented 5 years ago

I got problem with tab bar controller. when i use only one tab it works fine but when i use two tabs the object viewmodel in viewcontroller is nil. The problem happens when i migrate from 1.x.x to 2.0.0. Here my code:

AppDelegate

let dashboardFlow = DashboardFlow(dependency: DependencyRegistry())

    guard let window = self.window else { return true }

    Flows.whenReady(flow1: dashboardFlow, block: { [unowned window] (flowRoot) in
      window.rootViewController = flowRoot
      window.makeKeyAndVisible()
    })
    self.coordinator.coordinate(flow: dashboardFlow, with: OneStepper(withSingleStep: AppStep.dashboard))

DashboardFlow

fileprivate func navigateToDashboard() -> FlowContributors {
    let homeFlow = HomeFlow(dependency: dependency)
    let secondFlow = SecondFlow(dependency: dependency)

    Flows.whenReady(flow1: homeFlow, flow2: secondFlow) { [unowned self] (root1: UINavigationController, root2: UINavigationController) in

      let homeItem = UITabBarItem(title: "Home", image: UIImage(named: "home"), selectedImage: nil)

      let homeItem2 = UITabBarItem(title: "Second", image: UIImage(named: "home"), selectedImage: nil)

      root1.tabBarItem = homeItem
      root2.tabBarItem = homeItem2

      self.rootViewController.setViewControllers([root1, root2], animated: false)
    }

    return .multiple(flowContributors: [
      .contribute(withNextPresentable: homeFlow, withNextStepper:OneStepper(withSingleStep: Homestep.home)),
      .contribute(withNextPresentable: secondFlow, withNextStepper: OneStepper(withSingleStep: Homestep.home))
      ])
  }

View Controller

class ViewController: UIViewController {

  var viewModel: ViewModel!

  override func viewDidLoad() {
    super.viewDidLoad()

    print("ViewModel \(viewModel.title)")

  }
}

viewModel.title is nil

vvghost1 commented 5 years ago
.contribute(withNextPresentable: secondFlow, withNextStepper: OneStepper(withSingleStep: Homestep.home))

Is Homestep.home in that line mistake? Looks strange. If not, can you share code of SecondFlow?

twittemb commented 5 years ago

Hi @seyhagithub

Can you share the code of your first and second flows because I can't figure out how the ViewModels are created with your example ?

Thanks.

seyhagithub commented 5 years ago

Hi @twittemb, @vvghost1

class SecondFlow: Flow {

  var root: Presentable {
    return self.rootViewController
  }

  let rootViewController = UINavigationController()

  let dependency: DependencyRegistry

  init(dependency: DependencyRegistry) {
    self.dependency = dependency
  }

  func navigate(to step: Step) -> FlowContributors {
    guard let step = step as? Homestep else { return .none }
    switch step {
    case .home:
      return navigateToHome()
    }
  }

    fileprivate func navigateToHome() -> FlowContributors {
     let viewController = dependency.resolver.resolve(SecondViewController.self)!
      self.rootViewController.pushViewController(viewController, animated: true)
     return .one(flowContributor: .contribute(withNextPresentable: viewController, withNextStepper: viewController.viewModel))
  }
}

Thank in advance.

vvghost1 commented 5 years ago

@seyhagithub you need to look inside DependencyRegistry and search if you forgot to setup ViewController's viewModel property after creation. As we can see you are not setting up it here, and looks like your problem is not related to RxFlow

seyhagithub commented 5 years ago

@vvghost1 thank for this comment. My DependencyRegistry is swinject. It will resolve related object. First thing first, i think like this too.

class MyAssembly: Assembly {
  func assemble(container: Container) {

    container.register(ViewModel.self) { _  in
      return ViewModel()
    }.inObjectScope(.graph)

    container.register(ViewModel2.self) { _  in
      return ViewModel2()
      }.inObjectScope(.graph)

container.register(ViewController.self) { resolver  in
      let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
      let temp = storyboard.instantiateViewController(withIdentifier: "ViewController")
      let viewController = temp as!  ViewController
      viewController.viewModel = resolver.resolve(ViewModel.self)!
      return viewController
    }

    container.register(SecondViewController.self) { resolver  in
      let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
      let temp = storyboard.instantiateViewController(withIdentifier: "SecondViewController")
      let viewController = temp as!  SecondViewController
      viewController.viewModel = resolver.resolve(ViewModel2.self)!
      return viewController
    }
  }
}

Any more suggestion.

seyhagithub commented 5 years ago

@vvghost1 ViewModel / ViewModel2 is nil not title

class ViewModel: Stepper {
  var steps = PublishRelay<Step>()
  let title = "Hello"
}
vvghost1 commented 5 years ago

@seyhagithub I dont use storyboard for a while for now, but as I remember instantiateViewController method forced viewDidLoad to execute, and at that step viewModel property didn't setted up yet. If so, you can move your work with viewModel to viewWillAppear, for example

seyhagithub commented 5 years ago

@vvghost1 My problem is that viewModel is nil even we move this property to another event in ViewController

vvghost1 commented 5 years ago

@seyhagithub can you share sample project with this behavior?

beretis commented 5 years ago

I had similar problem, I use my own custom implementation of tab bar using ContainerViewController, and I had problem that after I changed tabs, when I came back to the previous tab, FlowCoordinator didn't respond to my Stepper (it got dealocated). I spent few hours debuging and found out that this code is the reason:

    /// Rx observable, triggered when the view is being dismissed
    public var dismissed: ControlEvent<Bool> {

        let dismissedSource = self.sentMessage(#selector(Base.viewDidDisappear))
            .filter { [base] _ in base.isBeingDismissed }
            .map { _ in false }

        let movedToParentSource = self.sentMessage(#selector(Base.didMove))
            .filter({!($0.first is UIViewController)})
            .map { _ in false }

        return ControlEvent(events: Observable.merge(dismissedSource, movedToParentSource))
    }

specifically movedToParentSource. I implemented containerViewController according apple documentation / Listing 5-3Transitioning between two child view controllers.

Maybe even system TabBar has similar issue. I wanted to ask, will I brake something if I comment out this movedToParentSource code?

@twittemb ?

seyhagithub commented 5 years ago

@vvghost1 here is my sample project . thank for your time

vvghost1 commented 5 years ago

@seyhagithub the issue is in entry point of your app, it is ViewController in Main.storyboard. Your app trying to instantiate ViewController as described by entry point just after didFinishLaunchingWithOptions, this is common process, and FlowCoordinator just don't have chance to take control of window. The solution is easy, just make any safe placeholder entry point. I love solution in RxFlow sample app: in info.plist change value for key UIMainStoryboardFile from Main to LaunchScreen.

seyhagithub commented 5 years ago

@vvghost1 wow that's is really useful answer. Thank @vvghost1 it works now.

snowtema commented 5 years ago

Hello! How I can create two flows with tab bar which has three tabBarButtoItem's? Thanks!

twittemb commented 5 years ago

Hi @snowtema

Can you elaborate a little bit please ?

thanks

snowtema commented 5 years ago

Hi @twittemb, thanks for the quick response!

I want to create something like these:

Снимок экрана 2019-03-20 в 1 34 06

But a Plus button hasn't their own flow, because of its simple modal view controller.

snowtema commented 5 years ago

I mean, can I create these button from standard RxFlow mechanism without any magic code, f.e. adding UIButton above tabBarController.tabBar?

Plus button shouldn't have an active state in tabBar. If I will click by its then my active tab must be still the last tab before I clicked plus, f.e Index tab (first tab).

supervtb commented 4 years ago

@twittemb Hi, I have some issue with tabbar, deep link and presented view controller, maybe you could explain right way for flow? case: User open main screen, choose 2 tab bar item (for eg), then in this place open next view controller (with present function), then user go to the safari and open deeplink (after it app have to switched to the 1 tab and push view controller) But instead. App switching to the 1 tab in tabbar, pushing VC but I see latest presented screen still and when I try to close it - nothing happening because RxFlow lost link to presented vc (link to vc is nil).

Or maybe you could share more detailed instruction about deep linking and right way for implementation deep links?

twittemb commented 4 years ago

Hi @supervtb

Here is an attempt to handle deep links with RxFlow -> https://github.com/RxSwiftCommunity/RxFlow/pull/147

I would be interested in your feedback if you could try this branch against your code base.

Thanks.

wintermoon9 commented 4 years ago

@beretis How did you end up fixing this ? Having the same problem with a custom tab bar. Commenting out the movedToParentSource observable ended up fixing it for me but I want to know if there's any unintended side effects this could have @twittemb

wintermoon9 commented 4 years ago

Does anyone know about the above ^ is this project alive ?

beretis commented 4 years ago

@wintermoon9 I did fix it, let me find the solution and help you.

github-actions[bot] commented 1 year ago

This issue has not received any recent updates. We encourage you to check if this is still an issue after the latest release and if you find that this is still a problem, please leave a comment below and auto-close will be canceled.

github-actions[bot] commented 1 year ago

This issue has automatically been closed due to inactivity.