Xcode 4: Loading a Nib in a method in a different file - objective-c

sorry if this question is elementary, but I've been stuck on this bug for the past 2 days & I haven't been able to get past it. I'm using Xcode 4.3.2
I'm trying to load a nib named AController.xib in a method called "- (void) process" in the file named BController.m
To be clear, I copied ./A/AController.xib (which is a UIView), ./A/AController.m, ./A/AController.h to the directory ./B
I only mention this because I'm not sure if it matters for my question.
Currently, my flow works as flows (which could be my problem):
A view loads with a "buy" button
the user clicks the "buy" button which has an IBOutlet named "buyNow"
"buyNow" calls "buy", which then calls "process"
process then tries to load the nib with the following (option 1):
AController *blah;
for (id object in bundle){
if ([object isKindOfClass:[AController class]])
blah = (AController *) object;
}
assert(blah != nil && "blah can't be nil");
[self.view addSubView: blah];
The error I get here is "Thread 1: signal SIGABRT" in main.m
I've also tried (option 2),
AController *myView = [[AController alloc] initWithFrame:self.view.bounds];
[self.view addSubview:myView];
[AController release];
And (option 3)
AController * vc = [[AController alloc] initWithNibBundle:#"AController" bundle:nil]; [self.nc pushViewController:vc animated:NO];
I get the same error for all 3 choices. Each option was tried in the method "process". "process" is written in B.m. Can anyone offer some help so that I may figure this out? Any guidance as to why these options failed would be very helpful for my understanding and would be much appreciated. Thanks for helping a noob!

If AController is a UIView subclass, it cannot load a NIB. Verify it is in fact a controller, but from the initWithFrame and the way you are adding it to a view, it looks like it is not, or is being handled incorrectly.

Related

Passing data back and forth using AppDelegate

To start I am building an app to learn the basics of Objective-C. If there is anything unclear please let me know and I will edit my question.
The app is supposed to have the next functionality.
Open the camera preview when the app is executed. On the top there is a button to go to a TemplateController where the user can select an array of frames to select from a UICollectionView. User selects the Template and returns to the Camera Preview. User takes a picture and the picture with the frame selected is shown in the PreviewController. If the user doesn't like the frame and wants to switch it for another one. PreviewController has button on top to go to the TemplateController, select the frame and go back again to the PreviewController with the new frame.
I do not want to create an object for the frame everytime. I want the AppDelegate to hold that object. To keep it alive per say?(sorry, English is not my mother tongue).
I was thinking to use NSUserDefaults BUT I really want to do it using the AppDelegate. So at this point NSUserDefaults is not an option.
Now, I am using storyboards with a navigation controller. A screenshot is available here
Right now when I pass from the TemplateController to my PreviewController my code looks like this:
Reaching TemplateController from MainController or PreviewController
- (IBAction)showFrameSelector:(id)sender
{
UIStoryboard *storyboard;
storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
TemplateController *templateController = [storyboard instantiateViewControllerWithIdentifier:#"TemplateController"];
templateController.frameDelegate = self;
[self presentViewController:templateController animated:YES completion:nil];
}
Passing the data from TemplateController to its controller's destiny (Either MainController or PreviewController)
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
_selectedLabelStr = [self.frameImages[indexPath.section] objectAtIndex:indexPath.row];
[self.collectionView deselectItemAtIndexPath:indexPath animated:NO];
[self dismissViewControllerAnimated:YES completion:^{
if ([self.frameDelegate respondsToSelector:#selector(templateControllerLoadFrame:)])
{
[self.frameDelegate performSelector:#selector(templateControllerLoadFrame:) withObject:self];
}
}];
}
This loads the selected frame in PreviewController
- (void)templateControllerLoadFrame:(TemplateController *)sender
{
UIImage *tmp = [UIImage imageNamed:sender.selectedLabelStr];
_frameImageView.image = tmp;
}
My problem is, I don't have very clear what changes I have to do on the AppDelegate(it is untouched right now). What would be the best approach to accomplish this?
Main issue is when Tamplate is chosen before taking the still image. If I select the frame after taking the picture then it displays.
I am not certain that I understand your question. Stuffing an object into the app delegate solution may not be the best way forward. In fact I believe you ought to look at the delegation pattern that is used by Apple to communicate between view controllers. Please note that you appear to be doing half of the delegate pattern already. For example you make your PreviewController a frameDelegate of the TemplateController.
So I would think you'd have something like the following to transfer information from TemplateController back to the PreviewController. Note that I've included prepare for segue as that is a common pattern to push a data object forward (it will be called if you connect a segue from the PreviewController to the TemplateController and in your action method call performSegueWithIdentifier:#"SegueTitle"). Use of the "templateControllerDidFinish" delegation method is a common pattern used to push information back from TemplateController when it closes.
TemplateController.h
#class TemplateController;
#protocol TemplateControllerDelegate <NSObject>
-(void) templateControllerDidFinish :(TemplateController*)controller;
#end
#interface TemplateController : UIViewController
#property (nonatomic, weak) id <TemplateControllerDelegate>delegate;
...
#end
TemplateController.m
//! The internals for this method can also be called from wherever in your code you need to dismiss the TemplateController by copying the internal
-(IBAction)doneButtonAction:(id)sender
{
__weak TemplateController*weakSelf = self;
[self dismissViewControllerAnimated:YES completion:^{
[self.delegate templateControllerDidFinish:weakSelf];
}];
}
PreviewController.h
#import "TemplateController.h"
#interface PreviewController<TemplateControllerDelegate>
...
#end
PreviewController.m
#implementation
...
-(void) templateControllerDidFinish :(TemplateController*)controller
{
self.dataProperty = controller.someImportantData;
...
}
...
-(void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
if ( [[segue identifier]isEqualToString:#""] )
{
TemplateController *tc = [segue destinationViewController];
tc.delegate = self;
tc.data = [someDataObjectFromPreviewController];
}
}
To fix this situation a bit more:
Add a segue from the PreviewController to the TemplateController
(Ctrl-drag from Preview view controller to the Template Controller
in the document outline mode)
Name the segue identifier in the identity inspector
Change your code that presents the view controller from:
(IBAction)showFrameSelector:(id)sender
{
UIStoryboard *storyboard;
storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
TemplateController *templateController = [storyboard instantiateViewControllerWithIdentifier:#"TemplateController"];
templateController.frameDelegate = self;
[self presentViewController:templateController animated:YES completion:nil];
}
to
- (IBAction)showFrameSelector:(id)sender
{
[self performSegueWithIdentifier:#"SegueTitle"];
}
Add your data object to the target view controller as noted in prepareForSegue and you will be in good shape. Then use the delegate method to catch any data returned from your template (just add the data as properties to the controller and you should be golden)
You can see a better example of this delegation in a utility project template from Xcode (I just keyed this in..) I hope this information helps. You can get more information at these resources and also by searching Google and SO for iOS delegation :
Concepts in Objective C (Delegates and Data Sources)
Cocoa Core Competencies

Loading xib crashes app

Hello all I am having a issue with showing a xib file from the main file not sure why this is happening with some of the xib files and not others.
if(segment == #"1"){
Results1 *myView1 = [[Results1 alloc]initWithNibName:#"Results1" bundle:nil];
[self.view addSubview:myView1.view];
}else if(segment == #"2"){
Results2 *myView2 = [[Results2 alloc]initWithNibName:#"Results2" bundle:nil];
[self.view addSubview:myView2.view];
}else if(segment ==#"3"){
Results3 *myView3 = [[Results3 alloc]initWithNibName:#"Results3" bundle:nil];
[self.view addSubview:myView3.view];
}else if(segment ==#"4"){
Results4 *myView4 = [[Results4 alloc]initWithNibName:#"Results4" bundle:nil];
[self.view addSubview:myView4.view];
}
Is my code the first xib files opens but not the rest I am not sure why, I have added .h files:
#import "Results1.h"
#import "Results2.h"
#import "Results3.h"
#import "Results4.h"
the app ends up on this line when debugging:
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([TestTypingToolAppDelegate class]));
}
and then it crashes, anyone have any idea?
Tim, here is a picture from the Interface Builder, maybe it helps you:
You need to look at the console output when the application takes you to that line in your main.m. For me it was always a mistake on my part with the most common mistake being 1) The file does not exist (Results1.xib) or 2) I forgot to set the view outlet. As I mentioned the details from the exception in console output will help you.
Keep in mind that sometimes loading a view crashes on the device but not the simulator. In this situation, check to ensure that when you call:
initWithNibName:#"YourNibName";
that the casing of your nib name string is exactly the same as the nib file. The simulator ignores cases, but the device requires an exact match (pretty irritating)

When do views get created?

I apologize in advance for the n00b question. I am just getting started w/ iOS!
I am trying to push a webViewController onto a navigation controller.
mudWebViewController *webViewController = [[mudWebViewController alloc] initWithNibName:nil bundle:nil];
[[webViewController webView] setDelegate:webViewController];
[[self navigationController] pushViewController:webViewController animated:YES];
But this doesn't seem to work, as I don't see any of the logs in the delegate messages.
If I set the delegate in the viewDidLoad: method, it works fine.
I guess the webView doesn't actually exist at that point, but why? If I initialize the controller, shouldn't the webView be initialized too?
Is viewDidLoad: the right place to be setting up this stuff?
initWithNibName should be not nil, since you obviously are using a nib file to build the view, else you have to create the view in code, which you don't
mudWebViewController *webViewController = [[mudWebViewController alloc] initWithNibName:#"webViewController" bundle:nil];
[[self navigationController] pushViewController:webViewController animated:YES];
Also any delegates should be set either from the Interface builder or from the view itself in the viewDidLoad delegate and not from the previous class, as the object might not been yet initialized in the code so it can fail to set the delegate properly.

iPad Popover -[UIPopoverController initWithContentViewController: must not be called with `nil`

I'm still working my way around the iOS SDK and I have another probably easy one for you.
I'm getting the following error when attempting to present a popover:
CoreAnimation: ignoring exception: -[UIPopoverController initWithContentViewController: must not be called with nil.
I thought I had put in code to deal with this, although apparently not. Anyway, code is below. Any thoughts on this would be great. Cheers!
if(popoverController == nil)
{
NSLog(#"is nil");
popoverController = [[UIPopoverController alloc] initWithContentViewController:popoverDownload];
}
popoverController.delegate = self;
[popoverController presentPopoverFromRect:CGRectMake(0,0,400,200) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
UPDATE
I guess I'm not initialising popoverDownload correctly/at all.
In my .h file
PopoverDownloadViewController *popoverDownload;
#property (nonatomic,retain) PopoverDownloadViewController *popoverDownload;
UPDATE WITH ANSWER
And it was as easy as...
PopoverDownloadViewController *popoverDownload = [[PopoverDownloadViewController alloc] init];
Just to mark this answer closed. I needed to initialise the popover using the following code...
PopoverDownloadViewController *popoverDownload = [[PopoverDownloadViewController alloc] init];
Thanks to omz for the hints in the right direction.

Image not being set in method

I have a class with a viewDidLoad method and an artworkInfo method as follows:
-(void)viewDidLoad {
mainDelegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
[super viewDidLoad];
}
-(void)artworkInfo:(NSNumber *)pos{
mainDelegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
[self.image setImage:(UIImage *)[[mainDelegate.mapAnnotations objectAtIndex:0]image]];
}
the mainDelegate thing is to gain access to the appDelegate where an array is stored, but anyway, with the "[self.image setImage...]" command where it is, the image on the app does not appear, but when I copy that exact line of code into the viewDidLoad method, it shows up like it should. I know that the artworkInfo method is being called because I debugged it and it goes through, so I can't figure out why the command would not be doing anything it's current method while it will in the viewDidLoad...?
Also, here is where the method is called and this new view is loaded from another class:
infoPage *info = [[infoPage alloc] initWithNibName:#"infoPage" bundle:nil];
info.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:info animated:YES];
infoPage *myInfoPage = [[infoPage alloc] init];
[myInfoPage artworkInfo:position];
[info release];
OH, I see the problem. You're instantiating 2 different infoPage classes.
Change this:
infoPage *info = [[infoPage alloc] initWithNibName:#"infoPage" bundle:nil];
info.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:info animated:YES];
infoPage *myInfoPage = [[infoPage alloc] init];
[myInfoPage artworkInfo:position];
[info release];
to this:
infoPage *info = [[infoPage alloc] initWithNibName:#"infoPage" bundle:nil];
info.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:info animated:YES];
[info artworkInfo:position];
[info release];
Ok detailed answer. In order to understand why this image is not displaying properly you have to first look at how Runloops work in Objective C.
While viewDidLoad is the method that is called when a view is loaded and it is technically also called before a view is displayed and it's view objects initialized. Since presentModalViewController is an animation there is actually some threading going on in the works.
viewDidLoad gets called before the animation is created for the presentModalView. This initializes your objects. However, due to some of the inner workings of UI Kit some processes are loaded off into a thread. When they complete they run callback methods on the main UI thread.
Since presentModalViewController is a non-blocking method your artworkInfo method gets added to the mainRunLoop before the initializer form thread adds its callback methods to the main run loop. The best approach would be to have both a UIImage property of your viewController and a UIImageView.
set the value of UIImage by calling artworkInfo BEFORE the presentModalViewController method.
in your ViewDidLoad go ahead and set the value of your UIImageView
[self.imageView setImage:self.image];
Problem solved.
This seems pretty straight forward.
So you initialize your nib and try to call your method artwork before the nib is fully loaded. <-- This is not working for you.
Then you do additional initialization by overrider viewDidLoad per the doco where the nib is loaded <-- This is working for you
So the answer is, when you call setImage before your nib is loaded, then there is nothing to set the image to. When you call setImage in viewDidLoad your nib is loaded and then things should work just fine.
I hope this explains it a bit.