Search code examples
iosswiftxib

How can I open xib(ViewController) in xib(ViewController) when I tap Button without Navigation


I am not using storyboard.

I made customTabBarView. (I added 2 button on View).

I connected the buttons with the same function and gave the buttons a tag value.

How can I open the ViewController when I press the first button or press any button?

When I tap first Button I got this error.

Error:

*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<NSObject 0x28184c020> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key countryListTableView.'
terminating with uncaught exception of type NSException

SceneDelegate:

guard let windowScene = (scene as? UIWindowScene) else { return }
let countryRouter = TabBarViewController()

let window = UIWindow(windowScene: windowScene)
window.rootViewController = countryRouter
self.window = window
window.makeKeyAndVisible()

ViewControllerCountryList:

class ViewControllerCountryList: UIViewController, CountryListModule.View { 
.
.
.
}

TabBarButton:

class TabBarViewController: UIViewController {

  @IBOutlet weak var contentView: UIView!
  @IBOutlet weak var tabBarView: UIView!
  
  override func viewDidLoad() {
        super.viewDidLoad()

        designableTabBarView()
    }
  
  private func designableTabBarView() {
    tabBarView.layer.cornerRadius = tabBarView.frame.size.height / 3
    tabBarView.clipsToBounds = true
  }
  @IBAction func onClickTabBarButton(_ sender: UIButton) {
    switch sender.tag {
    case 1:
      let nib = UINib(nibName: "ViewControllerCountryList", bundle: nil)
      guard let countryListVC = nib.instantiate(withOwner: nil, options: nil).first as? ViewControllerCountryList else { return }
      self.addChild(countryListVC)
      countryList.didMove(toParent: self)
    default:
      break
    }
  }
}

enter image description here


Solution

  • Using instantiate(withOwner:options:) is not really suitable for your task.

    A better method is to use init(nibName:bundle:).

    This extension wraps it into an easy one-line call:

    extension UIViewController {
        static func loadFromNib() -> Self {
            func instantiateFromNib<T: UIViewController>() -> T {
                return T.init(nibName: String(describing: T.self), bundle: nil)
            }
            return instantiateFromNib()
        }
    }
    

    With that, you can now do:

    let countryListVC = ViewControllerCountryList.loadFromNib()
    

    For your "custom tab" layout, take a look at this:

    class TabBarViewController: UIViewController {
    
        @IBOutlet weak var contentView: UIView!
        @IBOutlet weak var tabBarView: UIView!
        
        // keep references to the loaded view controllers
        var countryListVC: ViewControllerCountryList!
        var someOtherVC: SomeOtherViewController!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            designableTabBarView()
        }
        
        private func designableTabBarView() {
            tabBarView.layer.cornerRadius = tabBarView.frame.size.height / 3
            tabBarView.clipsToBounds = true
        }
        
        @IBAction func onClickTabBarButton(_ sender: UIButton) {
            switch sender.tag {
            case 1:
                
                // remove other VC view from content view
                if someOtherVC != nil {
                    someOtherVC.view.removeFromSuperview()
                }
    
                // if we haven't loaded ViewControllerCountryList yet
                if countryListVC == nil {
                    countryListVC = ViewControllerCountryList.loadFromNib()
                    self.addChild(countryListVC)
                    contentView.addSubview(countryListVC.view)
                    countryListVC.didMove(toParent: self)
                }
    
                // add ViewControllerCountryList view to contentView
                contentView.addSubview(countryListVC.view)
                countryListVC.view.frame = contentView.bounds
                countryListVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    
            case 2:
                
                // remove country list VC view from content view
                if countryListVC != nil {
                    countryListVC.view.removeFromSuperview()
                }
                
                // if we haven't loaded SomeOtherViewController yet
                if someOtherVC == nil {
                    someOtherVC = SomeOtherViewController.loadFromNib()
                    self.addChild(someOtherVC)
                    contentView.addSubview(someOtherVC.view)
                    someOtherVC.didMove(toParent: self)
                }
    
                // add SomeOtherViewController view to contentView
                contentView.addSubview(someOtherVC.view)
                someOtherVC.view.frame = contentView.bounds
                someOtherVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    
            default:
                break
            }
        }
    
    }