I have two view controller . In first view controller I have table view with cell and search bar on top . The second view controller I have back button to go back to first view controller. The problem is when I enter the text into search bar and select the table view cell and then navigate to second view controller then when I click the back button at second view controller , the layout of the first view controller like each bar , navigation title is top of each other.
Here is the code for First view controller..
final class FirstViewController: UITableViewController {
// MARK: - UI Search Components
private let searchController = UISearchController(searchResultsController: nil)
private let viewModel: ViewModel
init(viewModel: MoviesViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
title = LocalizedString(key: "title")
setupSearchController()
configureTableView()
updateFromViewModel()
bindViewModel()
viewModel.fetchData()
}
private func configureTableView() {
tableView.dm_registerClassWithDefaultIdentifier(cellClass: MovieCell.self)
tableView.rowHeight = UITableView.automaticDimension
refreshControl = UIRefreshControl()
refreshControl?.addTarget(self, action: #selector(refreshData), for: .valueChanged)
}
private func bindViewModel() {
viewModel.updatedState = { [weak self] in
guard let self else { return }
DispatchQueue.main.async {
self.updateFromViewModel()
}
}
}
private func updateFromViewModel() {
switch viewModel.state {
case .loading, .loaded:
tableView.reloadData()
case .error:
showError()
}
refreshControl?.endRefreshing()
}
private func showError() {
let alertController = UIAlertController(title: "", message: LocalizedString(key: "movies.load.error.body"), preferredStyle: .alert)
let alertAction = UIAlertAction(title: LocalizedString(key: "movies.load.error.actionButton"), style: .default, handler: nil)
alertController.addAction(alertAction)
present(alertController, animated: true, completion: nil)
}
@objc private func refreshData() {
viewModel.fetchData()
}
@objc private func textSizeChanged() {
tableView.reloadData()
}
// MARK: Search Property.
private func setupSearchController() {
self.searchController.searchResultsUpdater = self
self.searchController.obscuresBackgroundDuringPresentation = false
self.searchController.hidesNavigationBarDuringPresentation = false
self.searchController.searchBar.placeholder = "Search Movie"
self.navigationItem.searchController = searchController
self.definesPresentationContext = false
self.navigationItem.hidesSearchBarWhenScrolling = false
searchController.delegate = self
searchController.searchBar.delegate = self
searchController.searchBar.showsBookmarkButton = true
searchController.searchBar.tintColor = .purple
searchController.searchBar.setImage(UIImage(named: "Filter"), for: .bookmark, state: .normal)
searchController.searchBar.setLeftImage(UIImage(named: "Search"))
searchController.searchBar.showsBookmarkButton = true
}
}
// MARK: - UITableViewDataSource
extension FirstViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let inSearchMode = self.viewModel.inSearchMode(searchController)
return inSearchMode ? self.viewModel.filteredData.count : self.viewModel.state.data.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: DataCell = tableView.dm_dequeueReusableCellWithDefaultIdentifier()
let inSearchMode = self.viewModel.inSearchMode(searchController)
let data = inSearchMode ? self.viewModel.filteredData[indexPath.row] : self.viewModel.state.data[indexPath.row]
cell.configure(movie)
return cell
}
}
// MARK: - UITableViewControllerDelegate
extension FirstViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let inSearchMode = self.viewModel.inSearchMode(searchController)
let movie = inSearchMode ? self.viewModel.filteredData[indexPath.row] : self.viewModel.state.data[indexPath.row]
let viewModel = DetailsViewModel(data: data, apiManager: APIManager())
let viewController = SecondDetailsViewController(viewModel: viewModel)
self.navigationController?.pushViewController(viewController, animated: true)
}
}
// MARK: - Search Controller Delegate Functions.
extension FirstViewController: UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate {
func updateSearchResults(for searchController: UISearchController) {
self.viewModel.updateSearchController(searchBarText: searchController.searchBar.text)
tableView.reloadData()
}
}
Here is the back button code ..
extension UIBarButtonItem {
static func backButton(target: Any?, action: Selector?) -> UIBarButtonItem {
let backButton = UIBarButtonItem(image: UIImage(named: "ArrowLeft"), style: .plain, target: target, action: action)
backButton.tintColor = UIColor.Brand.popsicle40
return backButton
}
}
Here is the code for second view controller.
final class SceondViewController: UIViewController {
private let viewModel: DetailsViewModel
private var currentViewController: UIViewController!
init(viewModel: ViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
navigationItem.largeTitleDisplayMode = .never
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = UIBarButtonItem.backButton(target: self, action: #selector(didTapBack(_:)))
updateFromViewModel()
bindViewModel()
viewModel.fetchData()
}
private func bindViewModel() {
viewModel.updatedState = { [weak self] in
guard let self else { return }
DispatchQueue.main.async {
self.updateFromViewModel()
}
}
}
private func updateFromViewModel() {
let state = viewModel.state
title = LocalizedString(key: "title")
switch state {
case .loading(let data):
self.showLoading(data)
case .loaded(let details):
self.showDetails(details)
case .error:
self.showError()
case .pageLoaded(let page):
self.showSimilarDetails(page)
}
}
private func showLoading(_ data: data) {
// show loading view controller content
}
private func showDetails(_ Details: Details) {
let displayViewController = DetailsDisplayViewController(Details: Details, viewModel: viewModel)
addChild(displayViewController)
displayViewController.view.frame = view.bounds
displayViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
currentViewController?.willMove(toParent: nil)
transition(
from: currentViewController,
to: displayViewController,
duration: 0.25,
options: [.transitionCrossDissolve],
animations: nil
) { (_) in
self.currentViewController.removeFromParent()
self.currentViewController = displayViewController
self.currentViewController.didMove(toParent: self)
}
}
private func showSimilarDetails(_ similarDetails: Page<Details>) {
let similarViewController = SimilariewController(viewModel: viewModel)
addChild(similarViewController)
similarViewController.view.frame = view.bounds
similarViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
currentViewController?.willMove(toParent: nil)
transition(
from: currentViewController,
to: similarViewController,
duration: 0.25,
options: [.transitionCrossDissolve],
animations: nil
) { (_) in
self.currentViewController.removeFromParent()
self.currentViewController = similarViewController
self.currentViewController.didMove(toParent: self)
}
}
private func showError() {
// code for error halding.
}
@objc private func didTapBack(_ sender: UIBarButtonItem) {
navigationController?.popViewController(animated: true)
}
}
I have tried that giving the constrains into table view controller but it did not fix the problem.
Here is the screenshot when I click the back button at second view controller and go back to first view controller , the layout of the view top of each other.
first view content layout error
here is the screenshot of the back button.