-
Notifications
You must be signed in to change notification settings - Fork 179
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
TabBarNavigator - present ViewController #192
Comments
Hey @tonyskansf, a coordinator is an object controlling a certain view controller. In the case of NavigationCoordinator, this is a viewcontroller of type "UINavigationController". If you use a TabBarCoordinator, the rootViewController (i.e. the one being coordinated) is a UITabBarCoordinator. When you specify these routers in the initializer of a TabBarCoordinator, it will call the UITabBarController's setViewControllers method. So, when a tab is selected, the UITabBarController will switch the tab to that NavigationCoordinator's rootViewController, which is an empty UINavigationController (hence, the screen is black). What should actually happen is, that whenever you create the TabBarCoordinator, it should try to present the sheet and fail, since the SpecialCoordinator's rootViewController is not yet in the view hierarchy. You can probably see something similar in the console. A possible solution to this would be a custom UITabBarControllerDelegate implementation overriding the I would probably do something like this: class TabSheetViewController: UIViewController {} // This is the type of viewController to present a sheet for, instead of selecting it.
class SheetTabBarDelegate: NSObject, UITabBarControllerDelegate {
var didSelectSheet: (UIViewController) -> Void
init(didSelectSheet: @escaping (UIViewController) -> Void) {
self.didSelectSheet = didSelectSheet
}
open func tabBarController(_ tabBarController: UITabBarController,
shouldSelect viewController: UIViewController) -> Bool {
if viewController is TabSheetViewController {
didSelectSheet()
return false
} else {
return true
}
}
}
class AppCoordinator: TabBarCoordinator<AppRoute> {
var sheetDelegateObject: SheetTabBarDelegate?
init() {
let normalCoordinator = NormalCoordinator()
normalCoordinator.rootViewController.tabBarItem = UITabBarItem(tabBarSystemItem: .recents, tag: 0)
let sheetVC = TabSheetViewController()
sheetVC.tabBarItem = UITabBarItem(tabBarSystemItem: .more, tag: 1)
super.init(tabs: [normalCoordinator, sheetVC], select: normalCoordinator)
sheetDelegateObject = SheetTabBarDelegate { [weak self] in self?.trigger(.presentSheet) }
delegate = sheetDelegateObject
}
} |
👋 Hi @pauljohanneskraft, thanks for your response. Your suggestion put me on a right track. So intially I've tried to alter the code a bit. (For readability I've cut out some part of the code) class SheetTabBarDelegate: NSObject, UITabBarControllerDelegate {
open func tabBarController(...) -> Bool {
if viewController is TabSheetViewController {
didSelectSheet(viewController) // pass viewController to be presented to the delegate
return false
} else {
return true
}
}
}
class AppCoordinator: TabBarCoordinator<AppRoute> {
init() {
// ...
sheetDelegateObject = SheetTabBarDelegate { [weak self] in self?.trigger(.presentSheet($0) }
}
override func prepareTransition(for route: AppRoute) -> TabBarTransition {
switch route {
case let .presentSheet(viewController):
return .present(viewController)
}
}
} However, the application crashed whenever I tapped the bar that was supposed to present a sheet due to:
The workaround I've tried and seemed to work, but I'm not sure if it is a good practice or if the library should be used like this. So instead of calling
I will keep this solution for now as this feature is not fundamental for my application. Although, if you know why this is, I'd highly appreciate your help as I already do. 🙏 |
Hi. Is there any progress on this issue? |
I found a simple solution. The reason for the error is to use the same I create a new coordinator and import UIKit
import XCoordinator
enum EmptyRoute: Route {
case empty
}
class EmptyViewController: UIViewController {
// MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
}
}
class EmptyCoordinator: NavigationCoordinator<EmptyRoute> {
// MARK: - Initialization
init() {
super.init(initialRoute: .empty)
}
// MARK: - Overrides
override func prepareTransition(for route: EmptyRoute) -> NavigationTransition {
let viewController = EmptyViewController()
return .push(viewController)
}
} Then I brought this coordinator instead of the coordinator you want to present: convenience init() {
let firstCoordinator = FirstCoordinator()
firstCoordinator.rootViewController.tabBarItem = .init(title: "First", image: nil, tag: 0)
let emptyCoordinator = EmptyCoordinator()
emptyCoordinator.rootViewController.tabBarItem = .init(title: "Second", image: nil, tag: 0)
let thirdCoordinator = ThirdCoordinator()
thirdCoordinator.rootViewController.tabBarItem = .init(title: "Third", image: nil, tag: 0)
self.init(
firstRouter: firstCoordinator.strongRouter,
emptyRouter: emptyCoordinator.strongRouter,
secondRouter: thirdCoordinator.strongRouter
)
} I use custom tab bar delegate like this: super.init(
tabs: [
firstRouter,
emptyRouter,
thirdRouter
],
select: firstRouter
)
sheetDelegateObject = SheetTabBarDelegate { [unowned self] _ in
self.trigger(.second)
}
delegate = sheetDelegateObject SheetTabBarDelegate itself: open class SheetTabBarDelegate: NSObject, UITabBarControllerDelegate {
// MARK: - Properties
public var didSelectSheet: (UIViewController) -> Void
// MARK: - Initialization
public init(didSelectSheet: @escaping (UIViewController) -> Void) {
self.didSelectSheet = didSelectSheet
}
// MARK: - Methods
open func tabBarController(
_ tabBarController: UITabBarController,
shouldSelect viewController: UIViewController
) -> Bool {
if viewController.children.first is EmptyViewController {
didSelectSheet(viewController)
return false
} else {
return true
}
}
} Finally, I use SecondCoordinator (which is the coordinator we want to present) at override func prepareTransition(for route: MainRoute) -> TabBarTransition {
switch route {
case .first:
return .select(firstRouter)
case .second:
return .presentFullScreen(secondRouter)
case .third:
return .select(thirdRouter)
}
} |
Hi, I am trying out the XCoordinator and got this problem, which I do not know the solution to. Not sure if this is the right place to ask such question so I am sorry if this does not belong here.
I want to implement a tab bar that has one specific tab, which only presents a view controller on tap.
Code
For the sake of example the code is simplified to two tabs --
SpecialCoordinator
is the one I want to present a view controller.I have created an extension to present a
Presentable
modally.Both Normal and Special Coordinators are
NavigationCoordinator<...>
and the implementation of SpecialCoordinator looks like this:If I use
.push(vc)
the view controller gets pushed; however, if.presentAsSheet(vc)
is used tabs are switched but the screen is black (nothing gets presented).Can you please help me?
Thanks.
The text was updated successfully, but these errors were encountered: