You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于MVVM-C的TabBarController:跨标签页跳转同ViewController的问题

问题描述

我有一个包含两个标签页的TabBarCoordinator:

  • 第一个标签页由FirstTabCoordinator管理,对应电影列表页(MoviesListViewController)
  • 第二个标签页由SecondTabCoordinator管理,对应收藏电影页(FavoritesViewController)
    两个页面都带有tableView,且各自拥有独立的UINavigationController。

从电影列表页或收藏页点击电影时,均可正常推送MovieDetailsViewController。但MovieDetailsViewController中设有相似电影集合,当前问题:在MovieDetailsViewController的didSelectItemAt indexPath方法中点击相似电影时,如何确定应使用哪个协调器来推送下一个MovieDetailsViewController?因为该详情页可从两个不同协调器跳转进入。

我认为需要在两个协调器之间添加某种机制,但不确定具体方案。


相关代码

MainCoordinator

protocol Coordinator {
  func startCoordinator()
}
    
class MainCoordinator: Coordinator {
  var tabBarController = UITabBarController()
  
  static func getCoordinator() -> MainCoordinator? {
    if let scene = UIApplication.shared.connectedScenes.first, let delegate = scene.delegate as? SceneDelegate {
      return delegate.coordinator
    }
    return nil
  }
  
  func startCoordinator() {
    FirstTabCoordinator.shared.startCoordinator()
    let moviesListVC = FirstTabCoordinator.shared.moviesListViewController
    self.setupTabBarItem(vc: moviesListVC,
                     title: "All Movies",
                     imageName: "film",
                     selectedImageName: "film.fill")
    
    SecondTabCoordinator.shared.startCoordinator()
    let favoritesVC = SecondTabCoordinator.shared.favoritesViewController
    self.setupTabBarItem(vc: favoritesVC,
                     title: "Favorites",
                     imageName: "heart",
                     selectedImageName: "heart.fill")
    
    self.tabBarController.tabBar.barStyle = .black
    self.tabBarController.tabBar.isTranslucent = false
    self.tabBarController.tabBar.tintColor = .white
    
    self.tabBarController.viewControllers = [FirstTabCoordinator.shared.navigationController,
                                             SecondTabCoordinator.shared.navigationController]
  }
 
  func setupTabBarItem(vc: UIViewController, title: String, imageName: String, selectedImageName: String) {
    let defaultImage = UIImage(systemName: imageName)
    let selectedImage = UIImage(systemName: selectedImageName)
    let tabBarItem = UITabBarItem(title: title, image: defaultImage, selectedImage: selectedImage)
    vc.tabBarItem = tabBarItem
  }
}

FirstTabCoordinator

class FirstTabCoordinator: Coordinator {
  var navigationController: UINavigationController! {
    didSet {
      navigationController?.navigationBar.tintColor = .white
      navigationController?.navigationBar.barStyle = .black
    }
  }
  let storyboard = UIStoryboard(name: "Main", bundle: .main)
  private lazy var navigationManager = NavigationManager(self.navigationController)
  
  static let shared = FirstTabCoordinator()
  private init() {}
  
  lazy var moviesListViewController: MoviesTableViewController = {
    let viewController = self.storyboard.instantiateViewController(identifier: "MoviesTableViewController") as! MoviesTableViewController
    viewController.title = "All Movies"
    return viewController
  }()
  
  func startCoordinator() {
    self.navigationController = UINavigationController()
    self.navigationController.viewControllers = [self.moviesListViewController]
  }
  
  func goToMovieDetails(movie: Movie) {
    self.navigationManager.goToMovieDetails(movie: movie)
  }
}

SecondTabCoordinator

class SecondTabCoordinator: Coordinator {
  var navigationController: UINavigationController! {
    didSet {
      navigationController?.navigationBar.tintColor = .white
      navigationController?.navigationBar.barStyle = .black
    }
  }
  let storyboard = UIStoryboard(name: "Main", bundle: .main)
  private lazy var navigationManager = NavigationManager(self.navigationController)

  static let shared = SecondTabCoordinator()
  private init() {}
  
  lazy var favoritesViewController: FavoritesViewController = {
    let viewController = self.storyboard.instantiateViewController(identifier: "FavoritesViewController") as FavoritesViewController
    viewController.title = "Favorites"
    return viewController
  }()
  
  func goToMovieDetails(movie: Movie) {
    self.navigationManager.goToMovieDetails(movie: movie)
  }
  
  func startCoordinator() {
    self.navigationController = UINavigationController()
    self.navigationController.viewControllers = [self.favoritesViewController]
  }
}

解决方案

方案一:给MovieDetailsViewController持有来源协调器引用

直接在创建详情页时传递来源协调器,点击相似电影时复用该协调器推送新页面。

  1. 修改MovieDetailsViewController添加协调器属性:
class MovieDetailsViewController: UIViewController {
    weak var coordinator: Coordinator?
    var movie: Movie!
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let similarMovie = similarMovies[indexPath.item]
        switch coordinator {
        case let tabCoordinator as FirstTabCoordinator:
            tabCoordinator.goToMovieDetails(movie: similarMovie)
        case let tabCoordinator as SecondTabCoordinator:
            tabCoordinator.goToMovieDetails(movie: similarMovie)
        default: break
        }
    }
}
  1. 更新NavigationManager的初始化和跳转方法,传递协调器:
class NavigationManager {
    private weak var navigationController: UINavigationController?
    weak var coordinator: Coordinator?
    
    init(navigationController: UINavigationController, coordinator: Coordinator) {
        self.navigationController = navigationController
        self.coordinator = coordinator
    }
    
    func goToMovieDetails(movie: Movie) {
        let storyboard = UIStoryboard(name: "Main", bundle: .main)
        let detailsVC = storyboard.instantiateViewController(identifier: "MovieDetailsViewController") as! MovieDetailsViewController
        detailsVC.movie = movie
        detailsVC.coordinator = coordinator
        navigationController?.pushViewController(detailsVC, animated: true)
    }
}
  1. 修改两个TabCoordinator中navigationManager的初始化:
// FirstTabCoordinator
private lazy var navigationManager = NavigationManager(self.navigationController, coordinator: self)

// SecondTabCoordinator
private lazy var navigationManager = NavigationManager(self.navigationController, coordinator: self)

方案二:通过NavigationManager反向匹配协调器

利用每个Tab协调器持有独立navigationController的特点,通过当前详情页所在的导航控制器匹配对应的协调器。

修改MovieDetailsViewController的点击方法:

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    let similarMovie = similarMovies[indexPath.item]
    guard let currentNav = navigationController else { return }
    
    if FirstTabCoordinator.shared.navigationController === currentNav {
        FirstTabCoordinator.shared.goToMovieDetails(movie: similarMovie)
    } else if SecondTabCoordinator.shared.navigationController === currentNav {
        SecondTabCoordinator.shared.goToMovieDetails(movie: similarMovie)
    }
}

该方案无需大量修改现有代码,但依赖协调器单例的正确性。

方案三:使用闭包回调解耦

通过闭包将点击事件回调给来源协调器,避免详情页直接持有协调器引用。

  1. 修改MovieDetailsViewController添加回调闭包:
class MovieDetailsViewController: UIViewController {
    var movie: Movie!
    var onSimilarMovieSelected: ((Movie) -> Void)?
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let similarMovie = similarMovies[indexPath.item]
        onSimilarMovieSelected?(similarMovie)
    }
}
  1. 更新NavigationManager的跳转方法,接收闭包参数:
func goToMovieDetails(movie: Movie, onSimilarSelected: @escaping (Movie) -> Void) {
    let storyboard = UIStoryboard(name: "Main", bundle: .main)
    let detailsVC = storyboard.instantiateViewController(identifier: "MovieDetailsViewController") as! MovieDetailsViewController
    detailsVC.movie = movie
    detailsVC.onSimilarMovieSelected = onSimilarSelected
    navigationController?.pushViewController(detailsVC, animated: true)
}
  1. 修改两个TabCoordinator的goToMovieDetails方法,设置闭包逻辑:
// FirstTabCoordinator
func goToMovieDetails(movie: Movie) {
    navigationManager.goToMovieDetails(movie: movie) { [weak self] similarMovie in
        self?.goToMovieDetails(movie: similarMovie)
    }
}

// SecondTabCoordinator
func goToMovieDetails(movie: Movie) {
    navigationManager.goToMovieDetails(movie: movie) { [weak self] similarMovie in
        self?.goToMovieDetails(movie: similarMovie)
    }
}

该方案解耦了详情页与协调器,符合依赖倒置原则。


内容的提问来源于stack exchange,提问作者alexionut01

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 00:31:19