CoolDocMan
CoolDocMan

Reputation: 638

IOS: calling framework’s View Controller. causes 'Could not load NIB in bundle‘ crash

I want to distribute a single View Controllers as a framework (called LoginFrameWork.framework) that can be imported and used in any App project. So in XCode I created an iOS Swift Framework project, in which I added a view controller instantiated with a XIB to display a login view (with username, password and button). This LoginViewController is coded with a public interface so that it can included and used from any App.

The framework View Controller is as follows:

 public class LoginViewController: UIViewController {

    @IBOutlet weak var emailTextField: UITextField!
    
    @IBOutlet weak var passwordTextField: UITextField!
    
    @IBAction func signInButton(_ sender: UIButton) {
        print("username \(emailTextField.text) password \(passwordTextField.text)")
    }
    
    public override func viewDidLoad() {
        super.viewDidLoad()
      
    }

}

Next I create an App project MyApp.xcodeproj with a single view controller MyAppVC and a button. When the button is clicked I'd like to push the LoginViewController from the framework created above.

So I create a Workspace called MyApp.xcworkspace. in XCode and add to it LoginFrameWork.framework and MyApp.xcodeproj created above. I hookup the single button on MyAppVC to open LoginViewController.

import UIKit

import LoginFrameWork

class ViewController: UIViewController {

  
  @IBAction func launchLogin(_ sender: Any) {
    //LoginSDK.start(presentingVC: self)
    var loginVC = LoginViewController(nibName: "LoginViewController", bundle: nil)

     // Present
     self.present(loginVC, animated: true)
    
  }
  override func viewDidLoad() {
        super.viewDidLoad()
    } 

}

However this crashes with the error below, which indicates that it cannot find LoginViewController. This could be because either (a)it has the wrong path to LoginViewController in the Library folder OR (b) the framework did not get installed in the devices folder (c) something else ...

(I did include LoginFrameWork.framework in MyApp project the General tab and under the Embedded binaries section)

 libc++abi: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Could not load NIB in bundle: 'NSBundle </Users/ramanmtx/Library/Developer/CoreSimulator/Devices/5C88CAFD-85B0-49E2-8641-362D6097DEA6/data/Containers/Bundle/Application/CF14B060-A662-44B7-9CC3-3C7EBD968D21/McApp.app> (loaded)' with name 'LoginViewController''
terminating with uncaught exception of type NSException
CoreSimulator 757.5 - Device: iPhone 12 Pro Max (5C88CAFD-85B0-49E2-8641-362D6097DEA6) - Runtime: iOS 14.5 (18E182) - DeviceType: iPhone 12 Pro Max

Upvotes: 0

Views: 661

Answers (1)

user7014451
user7014451

Reputation:

Posting this as an answer, more than happy to delete if it doesn't help.

I don't work with NIB/XIB files, so I'm having difficulty duplicating what you are trying, but I still believe I understand the actual issue. It really doesn't have to do with that, but with bundles, and where you are trying to pull things from.

Here's what I do. For example, let's say you have three things:

  • MyFramework, which contains
  • MyFile, and is being used by
  • MyProject

This seems to me what you want. Now, in your case MyProject imports MyFramework which contains MyFile. BUT...

In MyProject you are trying to access a file that's in the MyFramework bundle:

var loginVC = LoginViewController(nibName: "LoginViewController", bundle: nil)

Here's what works for me. First, I'm accessing something very different - CoreImage kernel files inside MyFramework, instantiating things there as CIFilters. Keep that in mind, but I think it's the same thing you want.

In MyFramework I open the bundle file:

func openBundleFile(_ named:String) -> String {
    let myBundle = Bundle.init(identifier: "com.MyCompany.MyFramework")
    let kernelPath = (myBundle?.path(forResource: "cikernels", ofType: "bundle"))! + "/" + named + ".cikernel"
    do {
        return try String(contentsOfFile: kernelPath)
    }
    catch let error as NSError {
        return error.description
    }
}

The main thing is the bundle identifier. I don't think this will work inside MyProject for you, but it may.

But I think the best route to go is to:

  • Create an initializer in LoginViewController that will
  • Access the NIB/XIB inside your framework with the appropriate bundle name
  • And remove the `init(nibName:bundle:) from your view controller.

Of course, it my be possible that simply putting in the bundle name in your app will work. :-) But it still feels wrong - frameworks should not require hard-coding of this sort!

Upvotes: 1

Related Questions