Failed to render instance of ClassName: The agent threw an exception loading nib in bundle - interface-builder

When I include my custom IBDesignable view in a storyboard or another nib, the agent crashes and throws an exception because it can't load the nib.
error: IB Designables: Failed to update auto layout status: The agent raised a "NSInternalInconsistencyException" exception: Could not load NIB in bundle: 'NSBundle (loaded)' with name 'StripyView'
Here's the code I use to load the nib:
override init(frame: CGRect) {
super.init(frame: frame)
loadContentViewFromNib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadContentViewFromNib()
}
func loadContentViewFromNib() {
let nib = UINib(nibName: String(StripyView), bundle: nil)
let views = nib.instantiateWithOwner(self, options: nil)
if let view = views.last as? UIView {
view.frame = bounds
view.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
addSubview(view)
}
}
The view loads from the nib correctly when I run in the simulator, why won't it display in Interface Builder?

When Interface Builder renders your IBDesignable views, it uses a helper app to load everything. The upshot of this is that the mainBundle at design time is related to the helper app, and it's not your app's mainBundle. You can see that the path mentioned in the error has nothing to do with your app:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Overlays
When loading the nib, you're relying on the fact that passing bundle: nil defaults to your app's mainBundle at run time.
let nib = UINib(nibName: String(describing: StripyView.self), bundle: nil)
So instead, you need to pass the correct bundle here. Fix the above line with the following:
let bundle = Bundle(for: StripyView.self)
let nib = UINib(nibName: String(describing: StripyView.self), bundle: bundle)
That will make Interface Builder load your nib from the same bundle as your custom view class.
This applies to anything that's loaded from a bundle by your custom view. For example, localised strings, images, etc. If you're using these in your view, make sure you use the same approach and explicitly pass in the bundle for the custom view class.

The same viewpoint with "Josh Heald", We can't pass nil for bundle. And
this one for who in object - C:
- (UIView *) loadViewFromNib{
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
UINib *nib = [UINib nibWithNibName:NSStringFromClass([self class]) bundle:bundle];
UIView *v = [[nib instantiateWithOwner:self options:nil]objectAtIndex:0];
return v;
}

Related

How to avoid retain cycle when assigning NSDocument as NSViewController's representedObject

I am building a macOS Document based application. I am following Apple's example code, which can be found here.
In Apple's example, the NSDocument assigns itself as the NSViewController's representedObject. The NSDocument also retains a strong reference to the NSViewController. As far as I can see, this should cause a retain cycle, which indeed happens in my implementation of the code. But in Apple's example, it does not cause a retain cycle, and I can't work out why. Here is Apple's example code:
class Document: NSDocument {
var contentViewController: ViewController!
override func makeWindowControllers() {
// Returns the storyboard that contains your document window.
let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil)
if let windowController =
storyboard.instantiateController(
withIdentifier: NSStoryboard.SceneIdentifier("Document Window Controller")) as? NSWindowController {
addWindowController(windowController)
// Set the view controller's represented object as your document.
if let contentVC = windowController.contentViewController as? ViewController {
contentVC.representedObject = content
contentViewController = contentVC
}
}
}
}
The only difference in my implementation is that my ViewController is further down the hierarchy, as it is contained within an NSSplitViewController, so my NSDocument subclass finds it like so:
override func makeWindowControllers() {
// Returns the Storyboard that contains your Document window.
let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil)
let windowController = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("Document Window Controller")) as! MainWindowWC
addWindowController(windowController)
if let splitVC = windowController.contentViewController as? NSSplitViewController {
for item in splitVC.splitViewItems {
if let vc = item.viewController as? ViewController {
vc.representedObject = self
contentViewController = vc
}
}
}
}
I can't see that this is causing the problem, and indeed when I close the window, the NSWindowController and NSSplitViewController both successfully deinitialise, but the NSViewController and NSDocument are retained.
I can solve the retain cycle by creating a weak reference to the NSDocument in the NSViewController, instead of using the .representedObject property. but it would be great to know why Apple's code is not causing the retain cycle I think it should.
Thanks,
Dan

Loading nib on OS X from NSViewController

I'm trying to load a NSWindow from an NSViewController on OS X and i'm doing the following:
private lazy var discoverable: DiscoverableWindow = {
return DiscoverableWindow.instanceFromNib()
} ()
The static method instanceFromNib() is defined as below:
class func instanceFromNib() -> DiscoverableWindow {
var instance = DiscoverableWindow()
var objects: NSArray?
NSBundle.mainBundle().loadNibNamed("DiscoverableWindow", owner: instance, topLevelObjects: &objects)
return instance
}
I'm using the window to show from my NSViewController:
NSApp.beginSheet(self.discoverable, modalForWindow: NSApplication.sharedApplication().mainWindow!, modalDelegate: nil, didEndSelector: nil, contextInfo: nil)
However, when I load it I see the following:
Is there something i'm doing incorrectly? Why is the NSWindow blank? I read the following on this:
https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Extensions.html
Cocoa - loadNibNamed:owner:topLevelObjects: from loaded bundle
Cocoa: NSApp beginSheet sets the application delegate?
Why don't you make a window controller to handle the DiscoverableWindow?
Create a subclass of NSWindowController, make sure "Also create xib file for user interface" is selected. Configure your window in the DiscoverableWindowController xib, uncheck "Visible At Launch" on the properties inspector for the window.
Then, in your ViewController:
#IBAction func showSheet(sender: NSButton) {
let discoverableWC = DiscoverableWindowController(windowNibName: "DiscoverableWindowController")
view.window?.beginSheet(discoverableWC.window!, completionHandler: nil)
}
Generally, each window in your app should be managed by its own window controller, let the window controller handle the nib loading and instantiation for you.
Download the sample project here.

xcode - Error when attaching xib file to custom UIView class

I have a UIView class where I create some buttons and labels programmatically, and I have a view inside a certain ViewController that is attached to this class. The class works perfectly fine as expected, but whenever I try to link this custom UIView class to a xib view i get these errors:
-"Main.storyboard: error: IB Designables: Failed to update auto layout status: Interface Builder Cocoa Touch Tool crashed"
-"Rendering the view took longer than 200 ms. Your drawing code may suffer from slow performance.
"
-"error: IB Designables: Failed to render instance of MyView: Rendering the view took longer than 200 ms. Your drawing code may suffer from slow performance.
"
Here is the initialization code in the UIView class:
#IBDesignable class MyView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func loadFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "MyView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
func setup() {
var view = loadFromNib()
view.frame = bounds
view.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
addSubview(view)
}
}
Both the class and the xib file have the same name,
UPDATE: I found out that the:
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
Is the one responsible for all the errors, but if I deleted it's content and ran the app the view doesn't show as expected, I also tried awakeFromNib() and same issues happened.
Not quite sure what is causing your specific errors, but here is what helped me when I was having trouble connecting a UIView in a xib file to a custom UIView class:
1) Delete the contents of your DerivedData sub-folder:
(The contents of DerivedData will be recreated when you build your projects again.)
Your folder can be found by visiting the "Locations" tab in your XCode preferences.
Just delete the folders inside and re-build / run your project again.
2) Clean your project
Product -> Clean (Or just use the [command + shift + K] command.
3) Restart xcode and re-build / run your code.
Hope this helps!

how to presentViewControllerAsSheet on OSX Mavericks?

It's a long story, but to cut it short; my first OSX app was written (on Yosemite) in Swift using a storyboard until I found out my (finished) app will not run on Mavericks. I need to run on Mavericks, so I have replaced the storyboard with NIBs.
My problem is with the segues; I was using 'sheet type' segues to show other view controllers in a sheet over the main view controller. A call to the presentViewControllerAsSheet method of NSViewController is a good replacement as it looks the same, but this API was introduced in Yosemite - so I need to work out how to do this for Mavericks.
In the action for a button on the main view, I've tried using beginSheet like this:
secondViewController = SecondViewController(nibName: "SecondViewController", bundle: nil)
self.view.window?.beginSheet(secondViewController!view.window!, completionHandler: nil)
But the second view controller's window is null at runtime. I've tried adding the new view controller as a subview to the application window but this is an unrecognised selector:
NSApplication.sharedApplication().windows[0].addSubView(secondViewController!.view)
I've search high and low for a description of how to show a sheet and all I can find is: Can a view controller own a sheet? but I'm sorry to admit I don't understand the answer. Can anybody help me with some working code? I'm beginning to worry that I'm trying to do something unusual but it looks OK on Yosemite, so how did people do this before Yosemite was released?
EDIT
I still haven't got to the solution, so I have put together a small app which shows the problems I'm having.
In AppDelegate.swift:
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
var mainViewController: FirstView!
func applicationDidFinishLaunching(aNotification: NSNotification) {
mainViewController = FirstView(nibName:"FirstView", bundle: nil)
window.contentView = mainViewController.view
mainViewController.view.frame = (window.contentView as! NSView).bounds
}
}
In FirstView.swift (associated NIB has a 'open sheet' button)
class FirstView: NSViewController {
var secondView: SecondView?
var secondWindow: SecondWinCon?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func pressButton(sender: AnyObject) {
secondView = SecondView(nibName: "SecondView", bundle: nil)!
// method 1 - this is the behaviour I want (but it only works on OSX 10.10)
// presentViewControllerAsSheet(secondView!)
// method 2 - this just creates a floating window
// self.view.addSubview(secondView!.view)
// self.view.window?.beginSheet(secondView!.view.window!, completionHandler: nil)
// method 3 - this also creates a floating window
secondWindow = SecondWinCon(windowNibName: "SecondWinCon")
self.view.window?.beginSheet(secondWindow!.window!, completionHandler: nil)
}
}
In SecondView.swift (associated NIB has a 'close' button)
class SecondView: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func dismissPressed(sender: AnyObject) {
if (presentingViewController != nil) {
presentingViewController?.dismissViewController(self)
} else {
self.view.window?.sheetParent?.endSheet(self.view.window!)
}
}
}
In SecondWinCon.swift (Associated NIB is empty)
class SecondWinCon: NSWindowController {
var secondView: SecondView?
override func windowDidLoad() {
super.windowDidLoad()
secondView = SecondView(nibName: "SecondView", bundle: nil)!
self.window?.contentView.addSubview(secondView!.view)
}
}
If method 1 is uncommented, you will see the behaviour I'm trying to emulate (remember it only works on OS X 10.10). Method 2 or 3 displays the second view, but not as a sheet.
I have the same problem, and found maybe is't an issue related to view life cycle.
When I call presentViewControllerAsSheet in viewDidLoad, sheet will not shown, and you will get this in console:
Failed to set (contentViewController) user defined inspected property on (NSWindow): presentViewController:animator:: View '''s view is not in a window/view hierarchy.
If you trigger this in viewWillAppear or viewDidAppear, it's totally no problem.
UPDATE
Okay, let's make it clear.
For this initial storyboard, NSWindowController is connected with a view controller, think this as a root view controller (RootVC).
Create another view controller desired as a sheet in storyboard (SheetVC).
in viewWillAppear or viewDidAppear of RootVC, [self presentViewControllerAsSheet: SheetVC]
The sheet will show, no additional code required.
If you get here looking for a solution, I was nearly there with method 3. The important step I had missed was to turn off "Visible At Launch" in the NSWindowController's NIB (it's an attribute of the NSWindow). In my sample code, this was in SecondWinCon.nib.

How would I reference the main storyboard in my app programmatically in swift?

How would I reference the main storyboard in my app programmatically in swift? I looked into the app delegate for a reference but so far I haven't found one.
Oh whoops I found the answer...
In another view controller of the that is connected to a storyboard you can simply use:
self.storyboard?
Or any object can get a storyboard by referencing its name and bundle:
let storyboard = UIStoryboard(name: "storyboardNameHere", bundle: nil) //if bundle is nil the main bundle will be used
It's easy. When I've come across a similar problem, I wrote a class that can obtain any resources from main bundle.
//Generate name of the main storyboard file, by default: "Main"
var kMainStoryboardName: String {
let info = NSBundle.mainBundle().infoDictionary!
if let value = info["TPMainStoryboardName"] as? String
{
return value
}else{
return "Main"
}
}
public class TPBundleResources
{
class func nib(name: String) -> UINib?
{
let nib = UINib(nibName: name, bundle: NSBundle.mainBundle());
return nib
}
//Main storybord
class func mainStoryboard() -> UIStoryboard
{
return storyboard(kMainStoryboardName)
}
class func storyboard(name: String) -> UIStoryboard
{
let storyboard = UIStoryboard(name: name, bundle: NSBundle.mainBundle())
return storyboard
}
//Obtain file from main bundle by name and fileType
class func fileFromBundle(fileName: String?, fileType: String?) -> NSURL?
{
var url: NSURL?
if let path = NSBundle.mainBundle().pathForResource(fileName, ofType: fileType)
{
url = NSURL.fileURLWithPath(path)
}
return url
}
class func plistValue(key:String) -> AnyObject?
{
let info = NSBundle.mainBundle().infoDictionary!
if let value: AnyObject = info[key]
{
return value
}else{
return nil
}
}
}
public extension TPBundleResources
{
//Obtain view controller by name from main storyboard
class func vcWithName(name: String) -> UIViewController?
{
let storyboard = mainStoryboard()
let viewController: AnyObject! = storyboard.instantiateViewControllerWithIdentifier(name)
return viewController as? UIViewController
}
class func vcWithName(storyboardName:String, name: String) -> UIViewController?
{
let sb = storyboard(storyboardName)
let viewController: AnyObject! = sb.instantiateViewControllerWithIdentifier(name)
return viewController as? UIViewController
}
//Obtain view controller by idx from nib
class func viewFromNib(nibName: String, atIdx idx:Int) -> UIView?
{
let view = NSBundle.mainBundle().loadNibNamed(nibName, owner: nil, options: nil)[idx] as! UIView
return view
}
class func viewFromNib(nibName: String, owner: AnyObject, atIdx idx:Int) -> UIView?
{
let bundle = NSBundle(forClass: owner.dynamicType)
let nib = UINib(nibName: nibName, bundle: bundle)
let view = nib.instantiateWithOwner(owner, options: nil)[idx] as? UIView
return view
}
class func viewFromNibV2(nibName: String, owner: AnyObject, atIdx idx:Int) -> UIView?
{
let view = NSBundle.mainBundle().loadNibNamed(nibName, owner: owner, options: nil)[idx] as! UIView
return view
}
}
Here are simple examples:
//Get a main storyboard
TPBundleResources.mainStoryboard()
//Get view controller form main storyboard
TPBundleResources.vcWithName("MyViewController")
//Get view from MyView.nib at index 0
TPBundleResources.viewFromNib("MyView", atIdx: 0)
//Get plist value by key
TPBundleResources.plistValue("key")
Even if #ManOfPanda's answer is correct, there are cases where you simply don't have a reference to a UIViewController, so you can grab it from the rootViewController of the UIWindow object from your AppDelegate.
// First import your AppDelegate
import AppDelegate
// ...
// Then get a reference of it.
let appDelegate = UIApplication().shared.delegate as! AppDelegate
// From there, get your UIStoryboard reference from the
// rootViewController in your UIWindow
let rootViewController = appDelegate.window?.rootViewController
let storyboard = rootViewController?.storyboard
You could also, of course, simply create a UIStoryboard by using (as #Mario suggested):
let storyboard = UIStoryboard(name: "storyboard", bundle:nil)
But that will, according to Apple documentation, create a new instance of the Storyboard (even if you already have one working). I always prefer to use an existing instance.
init(name:bundle:)
Creates and returns a storyboard object for the specified storyboard resource file.
init(name: String, bundle storyboardBundleOrNil: Bundle?)
Parameters
name: The name of the storyboard resource file without the filename extension. This method raises an exception if this parameter is nil.
storyboardBundleOrNil: The bundle containing the storyboard file and its related resources. If you specify nil, this method looks in the main bundle of the current application.
Source: Apple documentation
UIStoryboard * mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];

Resources