I have a project that is all in Objective C, except for my view controller, which is in Swift.
When I run it, i get the error
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: '-[UIViewController
_loadViewFromNibNamed:bundle:] loaded the "..." nib but the view outlet was not set.'
So I opened up my nib file, look at "File's Owner", and I see that the view does not even show up as an outlet at all.
For my old view controller (objective c), the view outlet does show up.
In my Swift view controller, I tried overriding the "view" variable from UIViewController in order to force it to be an #IBOutlet, but it complained about the "view" variable being of type UIView, complained about UIView?, and complained about UIView!.
Here are simplified versions of
my AppDelegate.h
#import <UIKit/UIKit.h>
#class MyViewController;
#class MyViewControllerSwift;
#interface MSAppDelegate : UIResponder <UIApplicationDelegate>
{
}
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) UIViewController *viewController;
#end
AppDelegate.m
#import "MyAppDelegate.h"
#import "MyViewController.h"
#import "MySwift-Swift.h"
#import <UIKit/UIKit.h>
#implementation MyAppDelegate
static BOOL USE_SWIFT_VIEW_CONTROLLER = YES;
- (void)dealloc
{
[_window release];
[_viewController release];
[super dealloc];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Override point for customization after application launch.
id viewControllerPtr = nil;
if(USE_SWIFT_VIEW_CONTROLLER)
{
viewControllerPtr = [MyViewControllerSwift alloc];
}
else
{
viewControllerPtr = [MyViewController alloc];
}
UIViewController* vController = nil;
if(USE_SWIFT_VIEW_CONTROLLER)
{
vController = [[viewControllerPtr initWithNibName:#"MyViewControllerSwift" bundle:nil] autorelease];
}
else
{
vController = [[viewControllerPtr initWithNibName:#"MyViewController" bundle:nil] autorelease];
}
self.viewController = vController;
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
ViewController.swift
import Foundation
import AVFoundation
#objc class MyViewControllerSwift : UIKit.UIViewController {
var player : AVFoundation.AVAudioPlayer?;
#IBOutlet weak var myTextView : UITextView!;
required init(coder aDecoder : NSCoder) {
super.init(coder:aDecoder);
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName:nibNameOrNil, bundle:nibBundleOrNil);
}
override func viewDidLoad() {
super.viewDidLoad();
println("Using MyViewControllerSwift");
}
deinit {
//TODO
}
}
What do I need to do to get my view to display?
Thanks.
(Yes, this is a similar question to Loaded nib but the view outlet was not set - new to InterfaceBuilder but the view outlet does not show up. )
First - set custom class in your nib file
(File's Owner -> third icon -> custom class : YourViewController )
Second - the last icon in file's owner -> link (drag) the "view" property to the interface view
Init your ViewController like so:
YourViewController(nibName: "YourViewName", bundle: nil)
It will work. Don't do any manipulations with View.
If you have tried everything and you still get this error, try re-creating the class file from scratch but remember to select the "Also create XIB file" check box. This will auto link up a few items that are not linked when creating these files separately. After this is created, you can likely cut and paste everything onto the new XIB and it should work fine.
I am finding this issue specifically with creating files separately in Swift.
I had the same problem , my 2nd view on my view controller couldn't load . I changed the view's name in property(as I had previously given "View"). Your existing view controller already had references to the view on top of it with the same name(View). Hence the error . Try changing the outlet names . It worked perfect in my case .
Originally I ran into this same issue when I had an empty .xib and dropped in a UIViewController.
My fix was to remove the UIViewController and just add a UIView instead. I made the File's Owner (under Placeholders) Custom Class -> Class the name of the UIViewController class that would contain all the controller logic. I connected the UIView in the .xib file to the view Placeholder -> File's owner -> Outlets.
Maybe Apple/Xcode is trying to enforce a more strict MVC design by removing the UIViewController from everything but storyboard? I tried to follow the advice of many other posts I saw in trying to connect my UIViewController's view (of the UIViewController I originally dropped into the .xib) but it wouldn't connect no matter whether I made the Class type in File's Owner or for the UIViewController the same as my custom class.
This is the only thing that worked for me in Xcode 11.0
I found on chinese forum that it could be happen when you have similar names for ViewController and view from XIB.
Example SpecialOfferViewController and SpecialOfferView
I renamed
SpecialOfferViewController and OfferView and error has gone!!!
I had a xib file named FooView and a view controller class named FooViewController.
FooVew was not intended to the be the root view of FooViewController, only a subview. Yet, UIViewController's default behavior is to automatically attempt exactly that. And thus, my app would crash with this error at the first point that my controller's view was accessed.
This behavior can be avoided using one of:
Rename your controller class or the xib file to dissociate them
override var nibName: String? { nil }
override func loadView() and assign to view yourself
I went with 2 as it seemed the least likely sneak back to bite me later.
Related
I'm completely new to Objective-C, XCode, and iOS development and I'm trying to figure out how to run certain code at startup, after all UI views and controls have been instantiated. I have a generic NSObject that I've added through interface builder by dragging it into my view controller scene. It's defined as follows:
#import <Foundation/Foundation.h>
#interface Controller : NSObject {
IBOutlet UISlider *slider;
IBOutlet UILabel *label;
}
-(IBAction)sliderChanged:(id)sender;
#end
I need to run sliderChanged on initialization. I've tried the following way:
#import "Controller.h"
#implementation Controller
-(id)init {
self = [super init];
if (self){
[self sliderChanged:nil];
}
return self;
}
// More code here
But both my slider and label are nil when this is called. I understand there's a viewDidLoad method within the ViewController class which may be what I need, but I'm not sure how to access the instance of my Controller class (which seems to be instantiated somewhere behind the scenes by the interface builder) from within that method. Should all of this code simply be moved to the ViewController itself? That would seem to make sense, but the design above is what we've been instructed in class, so I'm not really sure how to go about doing this.
After the XIB/Storyboard loader finishes loading all the objects and wiring them up, it sends awakeFromNib to every object that was instantiated from the XIB. So try adding this to your Controller class:
- (void)awakeFromNib {
[super awakeFromNib];
[self sliderChanged:nil];
}
You can find more information in the NSObject UIKit Additions Reference and “The Nib Object Life Cycle” in the Resource Programming Guide.
HOWEVER, if you created Controller as a top-level object, and you didn't connect any outlets to it, then nothing references it after the XIB loader finishes with it, so the system will deallocate it again. That's probably not what you want, so you should connect an outlet in your view controller to the Controller. If you do that (and let's say the outlet is named controller), then you can access it in viewDidLoad in your view controller class:
#import <UIKit/UIKit.h>
#import "Controller.h"
#interface ViewController : UIViewController {
IBOutlet Controller *controller;
}
#end
Implementation:
- (void)viewDidLoad {
[super viewDidLoad];
[self.controller sliderChanged:self];
}
I recently converted my .xib file to Storyboards and decided to programmatically add a UITableView to my ViewController in the storyboard.
I used this code to accomplish this:
// ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
UITableView *table;
}
// ViewController.m
#import "ViewController.h"
#interface ViewController (PrivateStuff)
// unimportant code...
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
table = [[UITableView alloc] initWithFrame:self.view.bounds];
table.delegate = self;
table.dataSource = self;
[self.view addSubview:table];
}
Unfortunately, whenever I try to run it, Xcode is giving me this message...
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[UITableViewController loadView] loaded the "ViewController" nib but didn't get a UITableView.'
... which doesn't make any sense. I have everything that needs to be hooked up on its correct order and all of my other views with UITableViews have no problem, in spite that the code is the same.
What could I do to solve this problem? Thanks in advance.
I agree with #rdelmar-Xcode is saying that a nib was loaded during a [tableviewcontroller loadView] call-so that means there is a tableviewcontroller somewhere in your code...but it also means that somehow the nib you created is still linked to your view controller. You may need to look at the connections inspector under utilities in Xcode and see if your nib still has any persistent links present and delete them. Otherwise, as long as you are providing a TableViewCell, either custom or generic, in the cellForRowAtIndexPath method, the above code should work
The Problem
An IBOutlet is released before I have a chance to use it.
What I Want
I want to access a navigation controller from my app delegate so I can reload a table view.
My Setup
I have:
A Main.xib that's set as my main interface in target settings
An IBOutlet to the navigation controller as an ivar on my app delegate
This IBOutlet hooked up to the correct navigation controller in Main.xib
App Delegate is instantiated in the xib but not set as File's Owner
I'm using ARC, Xcode 4.3.2 and iOS5.1
What I've Tried
Changing deployment target
Putting a break point on dealloc for the navigation controller, app delegate - they're never called
Reading everything I can find on ARC and IBOutlets - nothing seems to contradict what I'm doing
Creating a fresh project with just a the minimum classes required - I see exactly the same problem
Code
KPAppDelegate.h
#interface KPAppDelegate : UIResponder <UIApplicationDelegate> {
IBOutlet KPBrowseExpensesNavigationController *nc;
}
#property (strong) IBOutlet KPBrowseExpensesNavigationController *nc;
KPAppDelegate.m
#implementation KPAppDelegate
#synthesize nc;
-(void)setNc:(KPBrowseExpensesNavigationController *)nc_ {
nc = nc_; // This gets called on view load and nc gets set.
}
...snip...
// This is called about 5 seconds after app startup
-(void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {
// By the time we get here, nc is nil.
UITableViewController *tvc = [[nc viewControllers] objectAtIndex:0];
[[tvc tableView] reloadData];
}
#end
UPDATE
I must be doing something really silly here. Even an incredibly simple project still shows this problem. See link below.
Download a simple test project that shows the problem.
In Window nib, set the FilesOwner Class as UIApplication and then point it's delegate from Outlets to the AppDelegate object. This is what is wrong in your project example.
is your outlet from the Interface Builder set as an KPBrowseExpensesNavigationController type?
If not it is not going to create the connection between your nib and ViewController.
You should set its Custom Class as KPBrowseExpensesNavigationController in the Identity Inspector
I am not sure why you declare it as a property & a non-property. I should do something like this:
#interface KPAppDelegate : UIResponder <UIApplicationDelegate>
#property (nonatomic, strong) IBOutlet KPBrowseExpensesNavigationController *nc;
And in your implementation:
#implementation KPAppDelegate
#synthesize nc = _nc; // So you don't accidentally use nc
...snip...
// This is called about 5 seconds after app startup
-(void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {
// By the time we get here, nc is nil.
UITableViewController *tvc = [[**self.nc** viewControllers] objectAtIndex:0];
[[tvc tableView] reloadData];
}
#end
Hope this helps!
I didn't see where you alloc your nav controller. Just declaring the property won't assign any value to it, so it would be nil. In you -didFinishLaunchingWithOptions in the app delegate, set your alloc/init statement. Everything else looks fine.
KPBrowseExpensesNavigationController *nc = [[KPBrowseExpensesNavigationController alloc] init];
If you have a custom init, you can use that too, but just make sure to set it up before you try and use it.
Using an instance of a UIViewController, is there any way I can find the UIPopoverController being used to present it? I would also want to find the UIViewController that displayed the UIPopoverController in the first place.
I would normally use a delegate or other sort of notification to send a signal from the displayed view controller to the displaying one, but in this case I'm trying to create a reusable custom segue that dismisses the popover and then moves on to another view in the main view.
You would think that this would be simple (the UIViewController even has a private _popoverController property!), but it is not.
The general answer is that you have to save a reference to the UIPopoverController in the UIViewController that it is presenting, at the time the UIViewController is created.
If you are creating the UIPopoverController programmatically, then that's the time to store the reference in your UIViewController subclass.
If you are using Storyboards and Segues, you can get the UIPopoverController out of the segue in the prepareForSegue method:
UIPopoverController* popover = [(UIStoryboardPopoverSegue*)segue popoverController];
Of course, be sure that your segue really is a UIStoryboardPopoverSegue!
My recommendation is to leverage a combination of your own custom property and the private APIs in UIKit. To avoid app store rejection, any private APIs should compile out for release builds, and should be used only to check against your implementation.
First let's build the custom property into a category on UIViewController. This allows some perks in the implementation, and it doesn't require you to go back and derive every class from some custom view controller subclass.
// UIViewController+isPresentedInPopover.h
#import <UIKit/UIKit.h>
#interface UIViewController (isPresentedInPopover)
#property (assign, nonatomic, getter = isPresentedInPopover) BOOL presentedInPopover;
#end
Now for the implementation - we'll be using the Objective C runtime's associated object API to provide the storage for this property. Note that a selector is a nice choice for the unique key used to store the object, as it's automatically uniqued by the compiler and highly unlikely to be used by any other client for this purpose.
// UIViewController+isPresentedInPopover.m
#import "UIViewController+isPresentedInPopover.h"
#import <objc/runtime.h>
#implementation UIViewController (isPresentedInPopover)
- (void)setPresentedInPopover:(BOOL)presentedInPopover
{
objc_setAssociatedObject(self,
#selector(isPresentedInPopover),
[NSNumber numberWithBool:presentedInPopover],
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)isPresentedInPopover
{
NSNumber *wrappedBool = objc_getAssociatedObject(self, #selector(isPresentedInPopover));
BOOL userValue = [wrappedBool boolValue];
return userValue ?: [[self parentViewController] isPresentedInPopover];
}
#end
So there's a convenient side effect of using this as a category - you can call up to the parentViewController and see if that is contained in a popover as well. This way you can set the property on, say, a UINavigationController and all of its child view controllers will respond correctly to isPresentedInPopover. To accomplish this with subclasses, you'd be either trying to set this on every new child view controller, or subclassing navigation controllers, or other horrific things.
More Runtime Magic
There is still more that the Objective C Runtime has to offer for this particular problem, and we can use them to jump into Apple's private implementation details and check your own app against it. For release builds, this extra code will compile out, so no need to worry about the all-seeing eye of Sauron Apple when submitting to the store.
You can see from UIViewController.h that there is an ivar defined as UIPopoverController* _popoverController with #package scope. Luckily this is only enforced by the compiler. Nothing is sacred as far as the runtime is concerned, and it's pretty easy to access that ivar from anywhere. We'll add a debug-only runtime check on each access of the property to make sure we're consistent.
// UIViewController+isPresentedInPopover.m
#import "UIViewController+isPresentedInPopover.h"
#import <objc/runtime.h>
#implementation UIViewController (isPresentedInPopover)
- (void)setPresentedInPopover:(BOOL)presentedInPopover
{
objc_setAssociatedObject(self,
#selector(isPresentedInPopover),
[NSNumber numberWithBool:presentedInPopover],
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)isPresentedInPopover
{
NSNumber *wrappedBool = objc_getAssociatedObject(self, #selector(isPresentedInPopover));
BOOL userValue = [wrappedBool boolValue];
#if DEBUG
Ivar privatePopoverIvar = class_getInstanceVariable([UIViewController class], "_popoverController");
UIPopoverController *popover = object_getIvar(self, privatePopoverIvar);
BOOL privateAPIValue = popover != nil;
if (userValue != privateAPIValue) {
[NSException raise:NSInternalInconsistencyException format:
#"-[%# %#] "
"returning %# "
"while private UIViewController API suggests %#. "
"Did you forget to set 'presentedInPopover'?",
NSStringFromClass([self class]), NSStringFromSelector(_cmd),
userValue ? #"YES" : #"NO",
privateAPIValue ? #"YES" : #"NO"];
}
#endif
return userValue ?: [[self parentViewController] isPresentedInPopover];
}
#end
When using the property incorrectly, you'll get a message like this on the console:
2012-09-18 14:28:30.375 MyApp[41551:c07] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Consistency error in -[UINavigationController isPresentedInPopover]: returning NO while private UIViewController API suggests YES. Did you forget to set 'presentedInPopover'?'
...but when compiling with the DEBUG flag off or set to 0, it compiles down to the exact same code as before.
For The Free and the Foolhardy
Maybe you're doing Ad-Hoc/Enterprise/personal builds, or you're sufficiently bold to see just what Apple thinks about this one for the App Store. Either way, here's an implementation that just works using the current runtime and UIViewController - no setting properties needed!
// UIViewController+isPresentedInPopover.h
#import <UIKit/UIKit.h>
#interface UIViewController (isPresentedInPopover)
#property (readonly, assign, nonatomic, getter = isPresentedInPopover) BOOL presentedInPopover;
#end
// UIViewController+isPresentedInPopover.m
#import "UIViewController+isPresentedInPopover.h"
#import <objc/runtime.h>
#implementation UIViewController (isPresentedInPopover)
- (BOOL)isPresentedInPopover
{
Ivar privatePopoverIvar = class_getInstanceVariable([UIViewController class], "_popoverController");
UIPopoverController *popover = object_getIvar(self, privatePopoverIvar);
BOOL privateAPIValue = popover != nil;
return privateAPIValue ?: [[self parentViewController] isPresentedInPopover];
}
#end
Most helpful would probably be to make popover a class variable, so in the .m file of the class that is going to present the popover, do something like this:
#interface ExampleViewController()
#property (nonatomic, strong) UIPopoverController *popover
#end
#implementation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"some segue"])
{
//prevent stacking popovers
if ([self.popover isPopoverVisible])
{
[self.popover dismissPopoverAnimated:YES];
self.popover = nil;
}
[segue.destinationViewController setDelegate:self];
self.popover = [(UIStoryboardPopoverSegue *)segue popoverController];
}
}
#end
As #joey wrote above, Apple eliminated the need for the dummy control in iOS 8 with the popoverPresentationController property defined for UIViewController as the "The nearest ancestor in the view controller hierarchy that is a popover presentation controller. (read-only)".
Here is an example in Swift for a UIPopoverPresentationController-based segue defined on a storyboard. In this case, a button has been added programmatically and can be defined in this way as the pop-over's anchor. The sender could also be a selected UITableViewCell or a view from it.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showCallout" {
let button = sender as UIButton
let calloutViewController = segue.destinationViewController as CalloutViewController
if let popover = calloutViewController.popoverPresentationController {
popover.sourceView = button
popover.sourceRect = button.bounds
}
}
}
Taking off from ndoc's anwser: this answer shows a neater way in iOS 6 to prevent a popover from showing multiple time through segues. The method in the link was the one that worked awesomely for me for preventing popover stacking.
If you JUST want to know if your controller is being presented inside a popover (not interested to get a reference to the popover controller), you can simply do this, without storing variables nor hacking private API's.
-(BOOL)isPresentedInPopover
{
for (UIView *superview = self.view.superview; superview != nil; superview = superview.superview)
{
if ([NSStringFromClass([superview class]) isEqualToString:#"_UIPopoverView"])
return YES;
}
return NO;
}
I'm struggling getting started with the Universal App template in Xcode 4.1. I'm following the basic principles set out by kotancode here. The problem is getting the relevant view controller to load. I will focus on the iPhone part as an example. I create a subclass of UIViewController without a XIB as my "master" view controller class (where shared code will go). I then subclass this to create the iPhone specific UIViewController class called BaseViewController_iPhone, this time with a XIB.
The iPhone specific app delegate the header is set to:
#import "TestAppDelegate.h"
#import "BaseViewController_iPhone.h"
#interface TestAppDelegate_iPhone : TestAppDelegate {
BaseViewController_iPhone *viewController;
}
#property (nonatomic, retain) IBOutlet BaseViewController_iPhone *viewController;
#end
and for the implementation I try to override the applicationdidfinishlaunchingwithoptions method.
#import "TestAppDelegate_iPhone.h"
#implementation TestAppDelegate_iPhone
#synthesize viewController;
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self.window addSubview:viewController.view];
[self.window makeKeyAndVisible];
return YES;
}
-(void)dealloc {
[viewController release];
[super dealloc];
}
#end
This doesn't appear to work, it compiles fine and runs, but the new view controller and the corresponding XIB are not displayed (the original main window xib from the template is). I'm sure i'm missing something very simple, but i've spent a long time googling to no avail. Any help gratefully received.
Thank you.
Have you connected the ViewController in the app's delegate's XIB to your "BaseViewController_iPhone *viewController" ?
Are you sure the ViewController in the app's delegate's XIB is of the type BaseViewController?
Is the ViewController loading the correct XIB (check that in the app's delegate's xib)?
Check that and give some feedback.