Why is adding a view crashing the program? - iphone

In an attempt to figure out how to integrate code from one program into another I have dumped 3 classes into an empty open GLES game project. The classes are ContentController, PhoneContentController and MyViewController from the PageControl apple sample.
I have then taken this initialisation code form the PageControl app delegate file and put it into the game project appDelegate.
contentController = [[PhoneContentController alloc] init];
[self.window addSubview:contentController.view];
with the necessary synthesize / protocols / includes and declarations where they are in PageControl.
I get a SIGABRT error on this line in main:
return UIApplicationMain(argc, argv, nil, NSStringFromClass([IntegrationTestAppDelegate class]));
I added a general breakpoint and it points to this line in MyViewController.m
newsItem = [[UITextView alloc] initWithFrame:self.view.frame];
I'm guessing this has something to do with self.view.frame ....? maybe? So, is there some hierarchy type issue at work here?
EDIT - on further inspection it does seem to have something to do with views... although I don't quite know what. Could this have something to do with a foreign nib file?
EDIT - more detail: the newsItem is called from init of myViewController which is called from a method within phone content controller, which in turn is called with the following line in appdelegate:
contentController = [[PhoneContentController alloc] init];

At what point is:
newsItem = [[UITextView alloc] initWithFrame:self.view.frame];
being run? In particular, is it after nib loading is complete? If not, and self.view was set in Interface Builder, then self.view is going be nil, and self.view.frame is going to be garbage. (-initWithFrame: expects a CGRect struct, so in this case a nil pointer is wholly unacceptable, unlike in methods that expect Objective-C objects.)

Related

UIViewController

My question is: i don't understand that we can create a ViewController with a nib file and we can create it without a nib file. i mean that : for example can anyone explan me the template, Navigation based application how it work, what is the first object instanciated ?
thanks for your answers
The app's Info.plist file contains a property called "Main nib file base name" (NSMainNibFile). The nib file that is set here ("MainWindow.xib" by default) controls what will be loaded at startup.
If you don't have that set, and you want to launch an application without a default nib file, you need to pass in the name of your app delegate in your main.m file.
int retVal = UIApplicationMain(argc, argv, nil, #"AppDelegate");
http://blog.hplogsdon.com/ios-applications-without-any-nib-files/
the only method to instantiate a UIViewControler is:
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle
that mean you "normally" ask it to load a .nib file...
but you can also pass "nil" to both parameters:
myUIViewController = [[MyUIViewController alloc] initWithNibName:nil bundle:nil];
...if you want to load it directly and manage it by yourself. Generally you crate a subClass of UIViewController (MyUIViewController in my sample) and in its #implementation you implement the method loadView
where you need to create the view of your class
- (void)loadView{
UIView *aUIView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320, 480)];
self.view = aUIView;
aUIView.backgroundColor = [UIColor colorWithRed:.2 green:.3 blue:.5 alpha:1];
// aUIView... other properties to set if needed...
[aUIView release];
}
this way you can manage it all without a ".nib file", adding all objects and subView only via code...

Bug with UIView frame property?

I am initializing a new UIViewCOntroller object.
then attempting to set its view's position of stage but I am having some trouble.
here is the code I am using
Note: this code is placed in the application main UIViewController's viewDidLoad method
UIViewController * cont = [[UIViewController alloc] init];
cont.view.backgroundColor = [UIColor redColor];
CGRect rect = CGRectMake(100, 0, 320, 480);
cont.view.frame = rect;
this code is still positioning the subview at (0,0) instead of (100,0)
However, if I introduce a decimal, such as using 320.01 (for the width value) or 480.01 (for the height value). The view would be positioned correctly.
It seems that if I use a size with an exact width:320.0 height: 480.0,
the origin will always be set to (0,0) !!!
This is a bit strange. I was hoping that someone could explain why this is happening, and possibly how it may be resolved.
Cheers ....
NSLog the value of cont.view and I think you will find it to be nil, which explains why nothing's happening. This is not the normal way to create a UIViewController -- it's not wrong to create one programmatically, but 99.99% of the time UIViewController subclasses are created with the main UIView in a .xib file. A freshly created UIViewController object has a nil "view" member, so you've got to initialize it somehow, either by loading a .xib:
MyViewController *vc = [[[MyViewController alloc] initWithNibName#"MyViewController" bundle:nil] autorelease];
or manually creating the view:
MyViewController *vc = [[[MyViewController alloc] init] autorelease];
UIView *theView = [[[UIView alloc] initWithFrame:viewframe] autorelease];
vc.view = theView;
Then you can move the view's frame to your heart's content, but moving the base view of a view controller is usually not what you want to do, you want to create sub-views and move those around.
[[UIViewController alloc]init] is wrong. The designated initializer for UIViewController is initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle. Even that does not necessarily initialize the view outlet to an actual UIView immediately. You need to subclass UIViewController and perform your customisations in the viewDidLoad method of that subclass.
In the interim is likely that view is nil so you can try setting whatever properties of it you like without anything ever happening.
I think you should be able to use
-(void) loadView {
[super loadView];
//create your views programmatically here
}
in order to create your viewController programmatically and avoid the IB. Normally the IB calls this method for you when your 'view' property is nil, however if you're avoid the IB make sure to include the above method so your view property is not nil.

error: expected expression before 'createCharacter'

so i am using presentmodalviewcontroller to change the active views in an ipad app. however when i try to change it using the statement [self presentModalViewController:createCharacter animated:NO]; in an ibaction that is triggered by a button. however i get an error saying expected expression before 'createCharacter'. createCharacter is a custom view controller that i have created... does anyone know what i am doing wrong? if you need any more relevant code just let me know,thanks
additional relevant code:
#import "createCharacter.h";
-(IBAction) buildCharacter{
[self presentModalViewController:createCharacter animated:NO];
}
createCharacter.h :
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#interface createCharacter : UIViewController {
IBOutlet UIView *view;
}
#end
I'd love to see some code to look at, and without it maybe this advice will be wrong, BUT...in my experience I've always used IBActions with a single argument, and that argument has always been the sender, so something like tying a button press to `
-(IBAction) presentNewController:(id)sender`
where sender is the button that was pressed.
If you use a method like that to detect the button press from IB, then in code what you would want is something like:
// In your current view controller, the target where you wired up the button
-(IBAction) presentNewController:(id)sender
{
if([sender isEqual:<whatever button you expect>])
{
CustomController *con = [[[CustomController alloc] init] autorelease];
[selfpresentModalViewController:con animated:YES];
}
}
You need to allocate and initialise createCharacter before you can push it into the view.
Assuming createCharacter is a view controller:
createCharacter *customView = [[createCharacter alloc] initWithNibName:yourNibNameORnil bundle:yourBundleNameORnil];
[self presentModalViewController:customView animated:YES];
[customView release];
It appears that you are sending a class to presentModalViewController:animated:. You need to initialize the class as Rog showed. As for MahatmaManic's answer, I have no idea why that was getting rid of your error. He is correct for OS X, but the argument is not required for iOS.
There are a few reasons you could still be getting the error after following Rog's example:
You have a variable named createCharacter, which means it was already initialized. In this case, change the name of your class to CreateCharacter. Classes are usually capitalized, and this would ensure that it is not confused with a variable.
The createCharacter header file is not properly imported. If you change the name of the file in XCode, it does not actually change the name of the file. If you try importing the file using the new name, it will not work.
Your initialization code wasn't right. In Rog's example, he used the default argument names. You should change yourNibNameORnil to the name of your NIB file, or nil if you are not using a NIB. You should also change yourBundleNameORnil to nil, assuming the NIB is located in your application's bundle. Here is an example assuming the NIB is in the application and named createCharacter.xib:
createCharacter *customView = [[createCharacter alloc] initWithNibName:#"createCharacter" bundle:nil];

Strange issue with UIDocumentInteractionController

I don't know what wrong with this code but everytime when I run the app, after the Menu is shown, the app crash.
NSString * path = [[NSBundle mainBundle] pathForResource:#"tung" ofType:#"doc"];
UIDocumentInteractionController *docController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:path]];
docController.delegate = self;
//[docController presentPreviewAnimated:YES];
CGRect rect = CGRectMake(0, 0, 300, 300);
[docController presentOptionsMenuFromRect:rect inView:self.view animated:YES];
Error I got:
*** Terminating app due to uncaught exception 'NSGenericException',
reason: '-[UIPopoverController
dealloc] reached while popover is
still visible.'
What should I do now ?
To preview a document via a "throwaway" UIDocumentInteractionController you should retain it after interactionControllerWithURL and autorelease it in the UIDocumentInteractionControllerDelegate method documentInteractionControllerDidDismissOptionsMenu. As remarked by David Liu, releasing it will crash. But autoreleasing works. I have checked that dealloc is actually called.
The following code should work:
- (void)previewDocumentWithURL:(NSURL*)url
{
UIDocumentInteractionController* preview = [UIDocumentInteractionController interactionControllerWithURL:url];
preview.delegate = self;
[preview presentPreviewAnimated:YES];
[preview retain];
}
- (void)documentInteractionControllerDidEndPreview:(UIDocumentInteractionController *)controller
{
[controller autorelease];
}
It's basically the old memory management problem.
[UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:path]] returns an autoreleased object, so it'll get autoreleased soon after your code block finishes. I'm guessing this is unlike presentModalViewController which will retain a copy for you, but that's a side point.
Basically, you need to retain it before your code block ends. The more annoying part is keeping track of what the docController is doing so you don't leak memory. You'll have to check the result from
[docController presentOptionsMenuFromRect:rect inView:self.view animated:YES];
If it returns NO, that means the menu never showed up, and so you should do a release on it right away (if you already did the retain).
However, if it returns YES, then you'll need to implement the delegate methods for docController, and release it when the menu is dismissed (in this case, it would be when:
- (void)documentInteractionControllerDidDismissOptionsMenu:(UIDocumentInteractionController *)controller
gets called.
EDIT:
I want to make a correction here:
The previous answer will crash if the popup menu is dismissed. Essentially there's really not any good way to create a throwaway DocController. Instead, I think it's best to just create one for every file you need in the viewcontroller, and deallocate when you're completely done. Otherwise you'll run into a myriad of possible cases where the DocController will get released too early and crash.
This error is caused (as others have mentioned) by the UIDocumentInteractionController being released while the presented view controller is still depending upon it. Thats a simple error and creating a strong reference to that view controller, in an reference counted environment, will solve the problem. The object can be released when it's no longer necessary by responding to delegate methods.
The reason this is confusing is that some other tools in Cocoa similar in appearance do not need to be retained the same way. For example UIImagePickerController or UIActivityViewController could be created and presented within a method without problem.
The difference between these other examples and UIDocumentInteractionController is that the other components are all subclasses of UIViewController. When they are pushed onto a navigation stack or presented they are retained by the navigation stack or the presenting view controller. When they are dismissed, that reference is removed andy they are released. UIDocumentInteractionController is not a UIViewController. Instead it provides view controllers which can display the relevant interface, but importantly do not (for good reason as it would cause a retain cycle) retain the document interaction controller. Because of that, whoever is creating the document controller must also maintain strong reference to it as long as it's needed by the presented interface.
This example is essentially the same as the accepted answer, but using ARC friendly style of retaining an object.
#interface MYViewController : UIViewController <UIDocumentInteractionControllerDelegate>
#property (nonatomic, strong) UIDocumentInteractionController *documentInteractionController;
#end
#implementation MYViewController
- (void)presentDocumentWithURL:(NSURL*)url {
self.documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:url];
self.documentInteractionController.delegate = self;
[self.documentInteractionController presentPreviewAnimated:YES];
}
- (void)documentInteractionControllerDidEndPreview:(UIDocumentInteractionController *)controller {
self.documentInteractionController = nil;
}
#end
SWIFT 3
Controller variable:
var documentIteratorController : UIDocumentInteractionController?
Call method:
documentIteratorController = UIDocumentInteractionController(url: reportURL)
documentIteratorController?.delegate = self
documentIteratorController?.presentOptionsMenu(from: self.printButton.frame, in: self.view, animated: true)
With Christian's technique...
Should you decide to launch different PDFs from different buttons in the view rather than from the navigation bar, don't use:
[controller autorelease];
Because it will remove the controller, so further instances won't work after the first use.
But if you are using it you may want to say
[self.controller autorelease];
I solved this problem by creating a property and then using this code.
[_DocController dismissMenuAnimated:NO];
_DocController = [UIDocumentInteractionController interactionControllerWithURL:url];
//docController.delegate = self;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
[_DocController presentOptionsMenuFromRect:((UIView*)control).bounds inView:control animated:YES];
}
else
{
[_DocController presentOptionsMenuFromBarButtonItem:control animated:YES];
}
The dismissMenuAnimated is important to prevent the UIPopover Dealloc error. The most common occurance of the error was when the popOver was still showing, and you pressed the button again to display the popover.

UIViewController: set self.view to my view or add my view as subview?

I have a question about UIViewController's subview, I created a UIView subclass MainView, which has the exact size of the screen, I wonder which is a better way of adding MainView, consider the following factors:
1 As MainView has same size as the whole screen, the MainView itself may have subviews, but there is no views at the save level as MainView(ie I don't need to add other subviews to self.view).
2 If I use self.view = mainView, do I put the code in loadView(as the viewDidLoad method means the view(self.view) is already loaded)? I see the loadView method is commented out by default, if I add the code to this method, what other code do I need to put together(e.g. initialize other aspects of the application)?
3 If I add mainView via [self addSubview:mainView], are there actually two off screen buffer? One for self.view, one for mainView, both has same size as the screen and one is layered on top of the other(so it wastes memory)?
Thanks a lot!
I'm not sure I completely understand what you're asking, but I'll try to answer a few of the questions you have.
First of all, if you have multiple UIViews on the screen they are all loaded into memory. You have to do -removeFromSuperview and release them to get the memory back.
You can assign your UIView as the UIViewController's view. For example:
MainView *mainView = [[MainView alloc] initWithFrame:CGRectMake(320.0, 480.0)];
self.view = mainView;
[mainView release]; //since the .view property is a retained property
in that case, you have have the view's initialization code in the -init method. Just redefine it like:
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
//initializations
}
return self;
}
You must implement loadView if you did initialize your view controller with a NIB.
UIViewController takes care of sizing its "main" view appropriately. This is all you need to do:
- (void)loadView
{
UIView* mainView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
self.view = mainView;
}
I'd solve all of this by doing it in a xib! If you create a UIView in your xib, you can then change it's class (when you select the UIView there should be a text field in the Class Identity section of the Identity inspector* - type 'MainView' here!)
Then, create your view controller by calling
myViewController = [[MainViewController alloc] initWithNibName:#"MyNibName" bundle:nil];
That should solve your problems; it's the main subview of your view controller (directly accessable from self.view) and you don't need to worry about memory usage, there's only one view :)
Sam
NB * Click tools -> Identity Inspector. I didn't know it was called this until I had to write this answer!
Yes, the first code-snippet shown above is the "standard" approach, AFAIK, when not using (evil!) NIB files -- i.e. when alloc'ing your view in-code, via loadView.
Note it seems one can also get away with the following, instead of hard-coding the screen-rect size:
UIView *myView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
self.view = myView;
[myView release];
Note you definitely want to do the [myView release] call since, indeed, as pointed out above, self.view (for UIView) is a retained property.
Cheers, -dk
Perhaps the most important thing to do is make sure you have the following:
self.view.userInteractionEnabled = YES;
While it might not be required all of the time, it fixes the issue where self.view is unresponsive. This issue pops up occasionally.