UIButton "addTarget" crashes app - objective-c

Just learning to add actions with code in my .m file and my first attempt throws an exception and crashes the app.
This is an authentication script that I hope will communicate with my web server. Here is the relevant code (this is all in the .m file, very simple .h file):
.h (header) file:
#import <UIKit/UIKit.h>
#interface Login_ViewController : UIViewController <UITextFieldDelegate>
#end
.m (implementation) file:
- (void)viewDidLoad {
UILabel *emailLabel = [[UILabel alloc] initWithFrame:CGRectMake(31.0f, 75.0f, 256.0f, 20.0f)];
emailLabel.text = #"Email Address";
emailLabel.backgroundColor = [UIColor grayColor];
emailLabel.textColor = [UIColor whiteColor];
UITextField *emailField = [[UITextField alloc] initWithFrame:CGRectMake(31.0f, 100.0f, 256.0f, 32.0f)];
emailField.delegate = self;
emailField.placeholder = #"Enter email address";
emailField.borderStyle = UITextBorderStyleRoundedRect;
[self.view addSubview:emailLabel];
[self.view addSubview:emailField];
UILabel *passLabel = [[UILabel alloc] initWithFrame:CGRectMake(31.0f, 175.0f, 256.0f, 20.0f)];
passLabel.text = #"Password";
passLabel.backgroundColor = [UIColor grayColor];
passLabel.textColor = [UIColor whiteColor];
UITextField *passField = [[UITextField alloc] initWithFrame:CGRectMake(31.0f, 200.0f, 256.0f, 32.0f)];
passField.delegate = self;
passField.placeholder = #"Enter Password";
passField.borderStyle = UITextBorderStyleRoundedRect;
passField.secureTextEntry = YES;
UIButton *loginButton = [[UIButton alloc] initWithFrame:CGRectMake(31.0f, 275.0f, 256.0f, 32.0f)];
[loginButton setTitle:#"Login" forState:UIControlStateNormal];
[loginButton addTarget:self action:#selector(authenticate) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:passLabel];
[self.view addSubview:passField];
[self.view addSubview:loginButton];
[self.view setBackgroundColor: [UIColor grayColor]];
UIAlertView *noAccount = [[UIAlertView alloc] initWithTitle:#"Welcome to T-of-U" message:#"Please enter your TofU credentials." delegate:self cancelButtonTitle:#"continue" otherButtonTitles: nil];
[noAccount show];
[super viewDidLoad];
}
also in the .m file (IBAction for the button):
-(IBAction)authenticate:(id)sender{
// This will be the validation code
// if we validate we will set the default Setting with accountID here.
UIAlertView *auth1 = [[UIAlertView alloc] initWithTitle:#"You clicked a button" message:#"Oh, do you want to login do you?" delegate:self cancelButtonTitle:#"Sure.. Why not!" otherButtonTitles: nil];
[auth1 show];
}
I am using a storyboard with a View that has a custom class of login_ViewController and it loads successfully based on a check for a local setting for account ID. This window is only pulled up if they have not logged in before, so I can authenticate and get that account ID to store locally. I am still experimenting with programmatically creating all these items as you can see in the viewDidLoad section.
Is this failing because I don't have the button/IBAction reference in the .h file , or is it ok to use the addTarget method where its all in the .m file?
Crash info from the log:
2012-01-08 04:54:32.868 TofU-5[10452:10103] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Login_ViewController authenticate]: unrecognized selector sent to instance 0x6c94780'

Your fix may be as easy as:
[loginButton addTarget:self action:#selector(authenticate:) forControlEvents:UIControlEventTouchUpInside];
(add a COLON after the authenticate name in your #selector). If a method takes any parameter, there will be a colon after it and for the run time environment to be able to send a message to it, that colon needs to be included in the selector of the action target.
I hope this makes sense and helps you out!
p.s. it wouldn't hurt to add
-(IBAction)authenticate:(id)sender
to the .h file.
IBAction here is an abbreviation for InterfaceBuilder-Action so this actually helps a lot more if you're building the interface in a XIB file. Because you're setting the target programatically, you could just use -(void) authenticate (no colon, because your method uses or needs no parameters), but don't do this yet until you get more overall experience with Objective C coding and actions.

Related

My Tweak Isn't Executing

EDIT:
Running on an iPad Mini on iOS 8.1
I tried a test project to hook viewDidLoad in the app to show a UIAlertView, but nothing happened. Does anyone have idea what could cause the tweak to compile but not run properly?
10 hours of debugging, I'm turning to SO.
There's obviously something wrong with this code, and I have no idea what could be causing it. I'm hooking the TestOut view in the ChineseSkill app, but when I get to the view it acts as if nothing changed from the original app.
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#interface TestOutViewController : UIViewController <UIAlertViewDelegate>
- (UIButton *)quitButton;
#end
%hook TestOutViewController
- (void)viewDidAppear:(BOOL)view {
%orig;
UIButton *infiniteLivesButton = [UIButton buttonWithType:UIButtonTypeSystem];
[infiniteLivesButton setTitle:#"Infinite Lives" forState:UIControlStateNormal];
[infiniteLivesButton setFrame:CGRectMake(/*[self quitButton].frame.origin.x + [self quitButton].frame.size.width, [self quitButton].frame.origin.y + 20*/50, 50, 200, 50)];
[infiniteLivesButton setBackgroundColor:[UIColor redColor]];
[infiniteLivesButton addTarget:self
action:#selector(makeLivesInfinite:)
forControlEvents:UIControlEventTouchUpInside];
UIButton *passTestButton = [UIButton buttonWithType:UIButtonTypeSystem];
[passTestButton setTitle:#"Skip Test" forState:UIControlStateNormal];
[passTestButton setFrame:CGRectMake(infiniteLivesButton.frame.origin.x + infiniteLivesButton.frame.size.width + 10,
infiniteLivesButton.frame.origin.y,
150,
50)];
[passTestButton addTarget:self
action:#selector(skipTest:)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:passTestButton];
[self.view addSubview:infiniteLivesButton];
UIAlertView *successAlertView = [[UIAlertView alloc] initWithTitle:#"Success!" message:#"Infinite hearts for the rest of this test." delegate:self cancelButtonTitle:#"Dismiss" otherButtonTitles:nil];
[successAlertView show];
}
- (void)touchesBegan:(id)began withEvent:(id)event {
UIAlertView *testView = [[UIAlertView alloc] initWithTitle:#"Test" message:#"Test Alert" delegate:self cancelButtonTitle:#"Dismiss" otherButtonTitles:nil];
[testView show];
}
%new
- (void)makeLivesInfinite:(UIButton *)sender {
[self performSelector:#selector(setNNumberofLeftHearts:) withObject:#1000000];
UIAlertView *successAlertView = [[UIAlertView alloc] initWithTitle:#"Success!" message:#"Infinite hearts for the rest of this test." delegate:self cancelButtonTitle:#"Dismiss" otherButtonTitles:nil];
[successAlertView show];
}
%new
- (UIButton *)quitButton {
return objc_getAssociatedObject(self, #selector(quitButton));
}
%new
- (void)skipTest:(UIButton *)sender {
[self performSelector:#selector(setNTotalTest:) withObject:#1];
[self performSelector:#selector(CheckOrContinueButtonClick:) withObject:NULL];
[self performSelector:#selector(CheckOrContinueButtonClick:) withObject:NULL];
}
%end
There's no alert views, no added views, no nothing. Nearly anything will help.
Your problem is you have to set the right filters in your tweak's plist file. By default the plist file has a bundle identifier filter com.apple.springboard. You should change this to the bundle identifier of the app you want your code injected.
Furthur information (Just a little):
What is happening in behind? MobileLoader is responsible for patching your code into a program. These plist files tell MobileLoader to patch the code in which program(s), in which framework(s) or in which running process. So your process is the app and not SpringBoard. This link
has good explanation about CydiaSubstrate (MobileSubstrate). You could also have a look at its documentation.

textField does not show in the alertView

Here is my code:
UIAlertView *theAlert = [[UIAlertView alloc] initWithTitle:#"Alert" message:nil delegate:self cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
UITextField *textField = [[UITextField alloc] initWithFrame: CGRectMake(12, 45, 260, 30)];
[textField setBackgroundColor: [UIColor clearColor]];
textField.borderStyle = UITextBorderStyleRoundedRect;
[theAlert addSubview:textField];
[theAlert show];
[textField release];
[theAlert release];
There is no textFiled in alertView, only a title "Alert" and a button "OK"
I will refer you the Apple Documentation in regards to UIAlertView Class Reference. Specifically
Subclassing Notes
The UIAlertView class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified
So what this means is you can't add subviews to an instance of UIAlertView the recent UIAlertView still has the addSubview: method is because UIAlertView is a subclass of UIView which has this method, but as of iOS7 the method addSubview: for UIAlertView no longer calls the super method on UIView it just does nothing.
So basically what you are after is not possible.
There are however alertViewStyles that you can use like UIAlertViewStylePlainTextInput which will add a single UITextField to the UIAlertView which you can then access using the method textFieldAtIndex:. However please be aware that you can't really do anything with this again because it is part of the UIAlertViews hierarchy so again it is made to be used as is.
Messing with the UIAlertView hierarchy will get your app rejected from the Apple Review process under
2.5 - Apps that use non-public APIs will be rejected
your background color is clear so It's not showing
[textField setBackgroundColor: [UIColor redColor]];.
and also show default text field
theAlert.alertViewStyle = UIAlertViewStylePlainTextInput;
//or for username and password
theAlert.alertViewStyle = UIAlertViewStyleLoginAndPasswordInput;
// or for password
theAlert.alertViewStyle = UIAlertViewStyleSecureTextInput;
Try this
You can use the default alertView with textfield.For that you need to set the alertViewStyle
[message setAlertViewStyle:UIAlertViewStylePlainTextInput];
Here message is object of alertView.
You can get the textfield value using the delegate method as shown below
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString *title = [alertView buttonTitleAtIndex:buttonIndex];
if([title isEqualToString:#"Login"]) {
UITextField *username = [alertView textFieldAtIndex:0];
UITextField *password = [alertView textFieldAtIndex:1];
NSLog(#"Username: %#\nPassword: %#", username.text, password.text);
}
}
Or you can set alert.alertViewStyle = UIAlertViewStylePlainTextInput;
This will add a text field for you. You can access it in the
UIAlertView delegate callback by using
UITextField *textField = [alertView textFieldAtIndex:0];
If your using ios 7 then add below code to your set of code
theAlert.alertViewStyle=UIAlertViewStylePlainTextInput;
Full code
UIAlertView *theAlert = [[UIAlertView alloc] initWithTitle:#"Alert" message:nil delegate:self cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
UITextField *textField = [[UITextField alloc] initWithFrame: CGRectMake(12, 45, 260, 30)];
[textField setBackgroundColor: [UIColor greenColor]];
textField.borderStyle = UITextBorderStyleRoundedRect;
theAlert.alertViewStyle=UIAlertViewStylePlainTextInput;
[theAlert show];

OCUnit crashes when creating UITextView

Unit testing is handling out humble-pie by the bucketload here at the moment. I am trying to run tests on my BannerAdViewController-class, but everything comes tumbling down when reaching the following line:
UITextView *buyAppTextView = [[UITextView alloc] initWithFrame:CGRectMake(8, 2, 304, 50)];
The console output is as follows:
-[__NSCFType markupDescription]: unrecognized selector sent to instance 0x1bc2de0
This is the method it reaches before the crash occurs:
-(void)setup{
_bannerFrame = [self frameRectForBannerAd];
_bannerWrapperFrame = [self frameRectForBannerWrapperFrame];
UITextView *buyAppTextView = [[UITextView alloc] initWithFrame:CGRectMake(8, 2, 304, 50)];
[buyAppTextView setText:#"some text"];
[buyAppTextView setTextColor:[UIColor blackColor]];
[buyAppTextView setFont:[UIFont systemFontOfSize:10]];
[buyAppTextView setAutoresizingMask:UIViewContentModeScaleAspectFit];
[buyAppTextView setUserInteractionEnabled:NO];
_placeHolderBanner = [[UIView alloc] initWithFrame:_bannerFrame];
[_placeHolderBanner setBackgroundColor:[UIColor whiteColor]];
[[_placeHolderBanner layer] setBorderColor:[UIColor blackColor].CGColor];
[[_placeHolderBanner layer] setBorderWidth:1.0];
[_placeHolderBanner addSubview: buyAppTextView];
[[self view] setFrame:_bannerWrapperFrame];
[[self view] setBackgroundColor:[UIColor clearColor]];
[[self view] addSubview: _placeHolderBanner];
_iAdBanner = [[ADBannerView alloc] initWithFrame:CGRectZero];
[[self view] addSubview: _iAdBanner];
_iAdBanner.delegate = self;
_iAdBanner.hidden = YES;
}
If I comment out everything to do with the buyAppTextView the tests run fine.
(Yes, the test-rig is linked with UIKit in case you were wondering).
Oh, and the test-class looks like this
#import "NorBannerAdViewTests.h"
#import "NORBannerAdViewController.h"
#implementation NorBannerAdViewTests
NORBannerAdViewController *_adViewController;
- (void)setUp{
[super setUp];
_adViewController = [[NORBannerAdViewController alloc] init];
}
- (void)tearDown{
[super tearDown];
[_adViewController release];
}
-(void) testThatFrameRectForBannerAdWrapperDoesNotReturnZero{
CGRect receivedFrame = [_adViewController frameRectForBannerWrapperFrame];
STAssertFalse((CGRectIsEmpty(receivedFrame)), #"receivedFrame should not be zero");
}
-(void) testThatFrameRectForBannerAdDoesNotReturnZero{
CGRect receivedFrame = [_adViewController frameRectForBannerAd];
STAssertFalse((CGRectIsEmpty(receivedFrame)), #"receivedFrame should not be zero");
}
#end
After extensive googling, and tracking through some related threads I (somewhat surprisingly) managed to solve the problem. The problem stems from the fact that the Test target was not setup when creating the project. As it was based on one of the Cocos2D templates I had to add it myself. Doing so, I didn't hook up the test-target's build settings with the App.
I had to add the following two flags to the test-target's build settings and the tests now run fine:
Bundle Loader: $(BUILT_PRODUCTS_DIR)/AppName.app/AppName
Test Host: $(BUNDLE_LOADER)

Animating Picker in + Done button + TextField updating

Hi Can anyone help me out with this, ive been looking at alot of the previous questions on this subject and they seem a little complicated. I want to setup a picker that animates in on the touch of a text field which has an IBOutlet called locationField.
I dont want to add the picker or toolbar and buttons in my story boards I would rather do it programatically.
So far I have:
- (void)viewDidLoad
{
//populate picker arrays
locationPickerArray = [[NSArray alloc] initWithObjects:#"1",#"2",#"3",#"4",#"5", nil];
actionSheet = [[UIActionSheet alloc] initWithTitle:#"" delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:nil];
[actionSheet showInView:self.view];
UIToolbar *pickerToolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0,480,32)];
[pickerToolbar sizeToFit];
pickerToolbar.barStyle = UIBarStyleBlackTranslucent;
NSMutableArray *barItems = [[NSMutableArray alloc] init];
UIBarButtonItem *cancelBtn = [[UIBarButtonItem alloc] initWithTitle:#"Cancel" style:UIBarButtonSystemItemCancel target:self action:#selector(cancel_clicked:)];
[barItems addObject:cancelBtn];
UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
[barItems addObject:flexSpace];
UIBarButtonItem *doneBtn = [[UIBarButtonItem alloc] initWithTitle:#"Done" style:UIBarButtonItemStyleDone target:self action:#selector(done_clicked:)];
[barItems addObject:doneBtn];
[pickerToolbar setItems:barItems animated:YES];
[actionSheet addSubview:pickerToolbar];
UIPickerView *picker = [[UIPickerView alloc] init];
picker.frame = CGRectMake(0, 44, 320, 216);
picker.delegate = self;
picker.dataSource = self;
picker.showsSelectionIndicator = YES;
[actionSheet addSubview:picker];
}
-(void)done_clicked:(id)sender
{
[actionSheet dismissWithClickedButtonIndex:0 animated:YES];
}
-(void)cancel_clicked:(id)sender
{
[actionSheet dismissWithClickedButtonIndex:0 animated:YES];
}
I have a few questions about this, it crashes on:
* Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[DetailsScreenViewController
numberOfComponentsInPickerView:]: unrecognized selector sent to
instance 0xb505be0'
What does this mean?
My final question is how can I populate this picker and assign it to popup when the iboutlet I created earlier is clicked (location field).
Thank you
The reason for your crash is that you've assigned an instance of DetailsScreenViewController to be the dataSource and delegate of a UIPickerView without actually implementing the methods for these protocols. The assignment takes place here:
picker.delegate = self;
picker.dataSource = self;
The specific crash is telling you that you haven't implemented numberOfComponentsInPickerView:, part of the UIPickerViewDataSource protocol. You should have gotten a compiler warning about this actually. Learning to use those warnings can be really helpful! Anyways, to fix it, provide implementations for all of the required methods on both protocols. The details of the implementations will depend on the needs for your app. If you still need help with details let me know.
Regarding your second question, I guess putting a UIPickerView inside a UIActionSheet is a thing now. I've never had a need to do it before, but here is a SO question with a lot of sample code for some background, not sure if you've read that one yet. But I think you'd just want to wait for the time you actually want to show the picker to call
[actionSheet showInView:self.view];
I think you can do this in a UITextFieldDelegate method, there are probably other ways. I'm going to try to do some experiments with that later and update this post.
EDIT:
If you assign your view controller as the delegate of whatever text field you're interested in, then you can implement this UITextFieldDelegate method:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
[actionSheet showInView:self.view];
return NO;
}
END EDIT
Hope this helps, and let me know if you have any questions.

A Tale of Two UIViewControllers

So I think I'm going crazy.
I've updated to XCode 4.2 so I'd have the IOS 5 SDK.
I have an app that's been working great until the upgrade. Now, I have a strange problem. It does not work on my IOS 5 iPad 2, nor will it work in the IOS 5 simulator. Works fine in the 4.3 simulator.
For the purposes of this question, I have two classes based on UIViewController. They do not use NIB files. One, called HistoryBrowser, works great. The other, NoteBrowseViewController, constructed along the same lines, does not.
From NoteBrowseView.Controller.h:
#interface NoteBrowseViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate, UIActionSheetDelegate, UISearchDisplayDelegate, UITabBarDelegate, MKMapViewDelegate> {
UITableView* tableView;
... buncha other vars ...
}
#property (retain, nonatomic) UITableView* tableView;
... buncha other properties ...
From NoteBrowseViewController.m:
#synthesize tableView
- (id)initWithEditing:(BOOL)inEditingMode inserting:(BOOL)inserting {
self=[super init];
if (self) {
self.isInserting=inserting;
self.isEditing=inEditingMode;
self.viewIsMap=NO;
self.insertType=defInsertTypeLink;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:0],#"viewSortOrder",
[NSNumber numberWithInt:1],#"priorityView",
[NSNumber numberWithBool:NO],#"simpleView",
nil];
[defaults registerDefaults:appDefaults];
self.viewIsSimple=[[NSUserDefaults standardUserDefaults]boolForKey:#"simpleView"];
}
return self;
}
-(void)loadView {
self.view=[[UIView alloc]initWithFrame:[[UIScreen mainScreen] applicationFrame]];
UIButton* aButton;
UIImage* buttonImage;
UIBarButtonItem* spacer=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
UISegmentedControl* sc=[[UISegmentedControl alloc]initWithItems:[NSArray arrayWithObjects:#"Alpha",#"Edit", #"View", nil]];
[sc addTarget:self action:#selector(segmentedControlValueChanged:) forControlEvents:UIControlEventValueChanged];
[sc setWidth:55.0 forSegmentAtIndex:0];
[sc setWidth:55.0 forSegmentAtIndex:1];
[sc setWidth:55.0 forSegmentAtIndex:2];
sc.selectedSegmentIndex=[[NSUserDefaults standardUserDefaults] integerForKey:#"viewSortOrder"];
sc.segmentedControlStyle=UISegmentedControlStyleBar;
UISegmentedControl* prisc=[[UISegmentedControl alloc]initWithItems:[NSArray arrayWithObjects:#"Open",#"All", nil]];
[prisc addTarget:self action:#selector(prioritySegmentedControlValueChanged:) forControlEvents:UIControlEventValueChanged];
[prisc setWidth:55.0 forSegmentAtIndex:0];
[prisc setWidth:55.0 forSegmentAtIndex:1];
prisc.selectedSegmentIndex=[[NSUserDefaults standardUserDefaults] integerForKey:#"priorityView"];
prisc.segmentedControlStyle=UISegmentedControlStyleBar;
UIBarButtonItem* segmentedButton=[[UIBarButtonItem alloc]initWithCustomView:sc];
UIBarButtonItem* prioritySegmentedButton=[[UIBarButtonItem alloc]initWithCustomView:prisc];
buttonImage=[UIImage imageNamed:#"13-plus.png"];
aButton=[UIButton buttonWithType:UIButtonTypeCustom];
[aButton setImage:buttonImage forState:UIControlStateNormal];
aButton.frame=CGRectMake(0.0, 0.0, buttonImage.size.width, buttonImage.size.height);
UIBarButtonItem* addButton=[[UIBarButtonItem alloc]initWithCustomView:aButton];
[aButton addTarget:self action:#selector(addButton:) forControlEvents:UIControlEventTouchUpInside];
buttonImage=[UIImage imageNamed:#"187-pencil.png"];
aButton=[UIButton buttonWithType:UIButtonTypeCustom];
[aButton setImage:buttonImage forState:UIControlStateNormal];
aButton.frame=CGRectMake(0.0, 0.0, buttonImage.size.width, buttonImage.size.height);
UIBarButtonItem* editButton=[[UIBarButtonItem alloc]initWithCustomView:aButton];
[aButton addTarget:self action:#selector(editButton:) forControlEvents:UIControlEventTouchUpInside];
buttonImage=[UIImage imageNamed:#"21-circle-east.png"];
aButton=[UIButton buttonWithType:UIButtonTypeCustom];
[aButton setImage:buttonImage forState:UIControlStateNormal];
aButton.frame=CGRectMake(0.0, 0.0, buttonImage.size.width, buttonImage.size.height);
UIBarButtonItem* simplify=[[UIBarButtonItem alloc]initWithCustomView:aButton];
[aButton addTarget:self action:#selector(simplify:) forControlEvents:UIControlEventTouchUpInside];
buttonImage=[UIImage imageNamed:#"25-circle-west.png"];
aButton=[UIButton buttonWithType:UIButtonTypeCustom];
[aButton setImage:buttonImage forState:UIControlStateNormal];
aButton.frame=CGRectMake(0.0, 0.0, buttonImage.size.width, buttonImage.size.height);
UIBarButtonItem* complexify=[[UIBarButtonItem alloc]initWithCustomView:aButton];
[aButton addTarget:self action:#selector(complexify:) forControlEvents:UIControlEventTouchUpInside];
buttonImage=[UIImage imageNamed:#"243-globe.png"];
aButton=[UIButton buttonWithType:UIButtonTypeCustom];
[aButton setImage:buttonImage forState:UIControlStateNormal];
aButton.frame=CGRectMake(0.0, 0.0, buttonImage.size.width, buttonImage.size.height);
UIBarButtonItem* map=[[UIBarButtonItem alloc]initWithCustomView:aButton];
[aButton addTarget:self action:#selector(mapify:) forControlEvents:UIControlEventTouchUpInside];
buttonImage=[UIImage imageNamed:#"162-receipt.png"];
aButton=[UIButton buttonWithType:UIButtonTypeCustom];
[aButton setImage:buttonImage forState:UIControlStateNormal];
aButton.frame=CGRectMake(0.0, 0.0, buttonImage.size.width, buttonImage.size.height);
UIBarButtonItem* notes=[[UIBarButtonItem alloc]initWithCustomView:aButton];
[aButton addTarget:self action:#selector(showNotes:) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem* cancelButton=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(cancelButtonPressed:)];
self.doneToolbar=[NSArray arrayWithObjects:spacer, cancelButton, nil];
self.toolbar=[[[UIToolbar alloc]init]autorelease];
if(self.isEditing) {
self.complexToolbar=[NSArray arrayWithObjects:simplify, spacer, segmentedButton, prioritySegmentedButton, spacer, cancelButton, nil];
self.simpleToolbar=[NSArray arrayWithObjects:complexify, spacer, cancelButton, nil];
}
else {
self.complexToolbar=[NSArray arrayWithObjects:simplify, map, spacer, segmentedButton, prioritySegmentedButton, spacer, addButton, editButton, cancelButton, nil];
self.simpleToolbar=[NSArray arrayWithObjects:complexify, map, spacer, addButton, editButton, cancelButton, nil];
}
self.mapToolbar=[NSArray arrayWithObjects:notes, spacer, prioritySegmentedButton, spacer, cancelButton, nil];
if (self.viewIsSimple) {
[self.toolbar setItems:self.simpleToolbar animated:YES];
}
else {
[self.toolbar setItems:self.complexToolbar animated:YES];
}
self.toolbar.autoresizingMask=UIViewAutoresizingFlexibleWidth;
[self.toolbar sizeToFit];
CGFloat toolbarHeight = [self.toolbar frame].size.height;
CGRect rootViewBounds=self.view.bounds;
CGFloat rootViewHeight = CGRectGetHeight(rootViewBounds);
CGFloat rootViewWidth = CGRectGetWidth(rootViewBounds);
[self.toolbar setFrame:CGRectMake(0,0, rootViewWidth, toolbarHeight)];
self.mapView=[[[MKMapView alloc]initWithFrame:CGRectMake(0, toolbarHeight, rootViewWidth, rootViewHeight-(self.isInserting?49:0)-toolbarHeight)]autorelease];
self.mapView.delegate=self;
self.mapView.zoomEnabled=YES;
self.mapView.scrollEnabled=YES;
self.mapView.autoresizingMask=UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin;
self.tableView=[[UITableView alloc]initWithFrame:CGRectMake(0, toolbarHeight, rootViewWidth, rootViewHeight-(self.isInserting?49:0)-toolbarHeight)
style:UITableViewStylePlain];
self.tableView.delegate=self;
self.tableView.dataSource=self;
self.tableView.autoresizingMask=UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin;
self.view.autoresizesSubviews = YES;
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
UISearchBar* searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 44, rootViewWidth, 44.0)];
searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
searchBar.autocorrectionType = UITextAutocorrectionTypeNo;
self.tableView.tableHeaderView = searchBar;
self.searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self];
self.searchDisplayController.delegate = self;
self.searchDisplayController.searchResultsDataSource = self;
self.searchDisplayController.searchResultsDelegate = self;
[self.view addSubview:self.tableView];
[self.view addSubview:self.toolbar];
if (self.isInserting) {
UITabBarItem* item1=[[[UITabBarItem alloc]initWithTitle:#"Link" image:nil tag:defInsertTypeLink]autorelease];
UITabBarItem* item2=[[[UITabBarItem alloc]initWithTitle:#"Contents Later" image:nil tag:defInsertTypeContentsLater]autorelease];
UITabBarItem* item3=[[[UITabBarItem alloc]initWithTitle:#"Contents Now" image:nil tag:defInsertTypeContentsNow]autorelease];
UITabBar* tabbar=[[UITabBar alloc]initWithFrame:CGRectMake(0, rootViewHeight-49, rootViewWidth, 49)];
tabbar.autoresizingMask=UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleBottomMargin;
[tabbar setDelegate:self];
[tabbar setItems:[NSArray arrayWithObjects:item1, item2, item3, nil] animated:YES];
[tabbar setSelectedItem:item1];
[self.view addSubview:tabbar];
}
if(self.viewIsSimple) {
self.contentSizeForViewInPopover = CGSizeMake(200.0, 625.0);
}
else {
self.contentSizeForViewInPopover = CGSizeMake(450.0, 625.0);
}
self.view.autoresizesSubviews = YES;
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.note=nil;
[sc release];
[prisc release];
[addButton release];
[prioritySegmentedButton release];
[cancelButton release];
[segmentedButton release];
[spacer release];
[editButton release];
[map release];
[simplify release];
[complexify release];
}
And finally, the a NoteBrowseViewController is instantiated thusly from another view controller:
self.noteBrowseViewController=[[NoteBrowseViewController alloc]initWithEditing:NO inserting:NO];
self.noteBrowseViewController.delegate=self;
self.popoverController = [[UIPopoverController alloc]initWithContentViewController:self.noteBrowseViewController];
self.popoverController.delegate = self;
[self.popoverController presentPopoverFromRect:((UIButton*)sender).frame inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
So this is what happens. If I run this in debug, and follow along, as soon as execution gets to this line:
CGRect rootViewBounds=self.view.bounds;
The program crashes with the following errors:
2011-10-20 11:42:02.703 ActionNote3[12332:15803] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
*** First throw call stack:
(0x1eb0052 0x2a60d0a 0x1e9d36e 0x1e9e220 0x1968cb6 0x5153b 0x50e11 0x50879 0x52e4a 0xdcc64e 0x4bcb3 0x72d1 0x1eb1ec9 0xd095c2 0xd0955a 0xdaeb76 0xdaf03f 0xdae2fe 0xd2ea30 0xd2ec56 0xd15384 0xd08aa9 0x20c7fa9 0x1e841c5 0x1de9022 0x1de790a 0x1de6db4 0x1de6ccb 0x20c6879 0x20c693e 0xd06a9b 0x2dbd 0x2d35)
terminate called throwing an exception
By watching things during the debug, I know self.view is being set up properly, and that loadView is being called when it should be. And it's not this line that makes it fail - it's anything that refers to self.view! what???
What really makes me crazy is that I have another subclassed view controller, HistoryBrowser, that is loaded in exactly the same manner... and it works fine.
So, aside from being upset that I cannot figure this out, i'd like to understand:
What's changed in XCode 4.2 (from 4.1) that would cause this, or is
this an IOS 5 issue?
What does this error mean, and what can I do
about it?
EDIT:
So based on suggestions from Abberant's answer below, I:
changed the initwithnib to just be init
removed the loadView method
added the full error message
added more relevant code to the init method
And now, referencing self.view works properly. But things have just gotten stranger.
Execution makes it past the first reference to self.view without error. However, it now halts at:
self.tableView.tableHeaderView = searchBar;
in the init method.
The error message received is eerily similar to the one I was receiving before:
2011-10-20 12:34:04.818 ActionNote3[13487:15803] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
*** First throw call stack:
(0x1eaf052 0x2a5fd0a 0x1e9c36e 0x1e9d220 0x1967cb6 0x50b4b 0x50421 0x4fe89 0x4d757 0x6641 0x1eb0ec9 0xd085c2 0xd0855a 0xdadb76 0xdae03f 0xdad2fe 0xd2da30 0xd2dc56 0xd14384 0xd07aa9 0x20c6fa9 0x1e831c5 0x1de8022 0x1de690a 0x1de5db4 0x1de5ccb 0x20c5879 0x20c593e 0xd05a9b 0x212d 0x20a5)
terminate called throwing an exception
So, if I comment that line out, execution procedes until it reaches this group of lines, where it fails when it attempts to do the performFetch.
NSError *error = nil;
if (![[self currentFetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
Debugging into the code for [self currentFetchedResultsController] shows that the fetched results controller is being set up properly and without issue.
The error received here is the same as above. The numbers in the first throw call stack change, but otherwise the error is the same.
So I'm left thinking that I have a larger problem going on here. I just have no idea how to track it down. As I said way up there somewhere, I have another view, constructed in the same manner, that loads fine.
Edit:
So NJones in his answer below suggested following Apple's guidelines by placing the appropriate code in loadView and viewDidLoad. So I did. I moved all view construction out of init and into loadView, and fired up the fetchedResultsController in viewDidLoad.
The app still crashes in the same spot:
self.tableView.tableHeaderView = searchBar
With the same error as noted above.
Thanks!
I would say you should use the loading model apple suggests. This is some of the code generated in xCode when you create a new UIViewController subclass (abridged to fit):
/*
// Implement loadView to create a view hierarchy programmatically, without using a nib.
-(void)loadView{
}
*/
/*
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
-(void)viewDidLoad{
[super viewDidLoad];
}
*/
Based on just this, Since you are creating your view programmatically, I would say that most of what you have in your init method belongs in the loadView method.
So I found the answer, and it's not something anyone would have guessed without seeing a lot more code. But, hopefully this answer will help other people who might run into the same thing.
In what has become my loadView method (but was originally in my init method, and still is in the code above), I had the following lines:
UISegmentedControl* sc=[[UISegmentedControl alloc]initWithItems:[NSArray arrayWithObjects:#"Alpha",#"Edit", #"View", nil]];
[sc addTarget:self action:#selector(segmentedControlValueChanged:) forControlEvents:UIControlEventValueChanged];
[sc setWidth:55.0 forSegmentAtIndex:0];
[sc setWidth:55.0 forSegmentAtIndex:1];
[sc setWidth:55.0 forSegmentAtIndex:2];
sc.selectedSegmentIndex=[[NSUserDefaults standardUserDefaults] integerForKey:#"viewSortOrder"];
sc.segmentedControlStyle=UISegmentedControlStyleBar;
segmentedControlValueChanged: would set a string var (and store the index of the selected button in user defaults). The string var was later used to determine sort order when firing up my fetchedresultscontroller.
Prior to IOS 5, when I set sc.selectedSegmentIndex, the method identified in the selector would get fired, and everything would get set up properly. In IOS 5, this is no longer the case, and the string var (setting a sort order) would not get set.
What's interesting is that upon execution this line:
self.tableView.tableHeaderView = searchBar;
would cause the fetchedReultsController to be accessed and, by accessing it, it would be initialized. When it went to be initialized, it would set the sort order and include a null value for the field name, hence the error message, which now makes sense.
So the root cause of the problem was that prior to IOS 5, setting the selectedSegmentIndex on a UISegmentedControl would fire the action associated with the control. Starting with IOS 5, this is no longer the case.
The original cause of the problem, which several people helped me resolve, was that I was not following the viewController load process properly. I am not using NIB files. Apple says that if you dont use a nib file, you have to define loadView to set up the view. If loadView is not defined, self.view is set internally, and viewDidLoad is called. The reason the app crashed at the first reference of self.view, then, is that I had logic in viewDidLoad to set up the fetchedResultsController. When I moved the view setup logic into loadView, references to self.view no longer started up the fetchedresultscontroller.
Been thinking about and looking for information on this for a while, but it might be any number of things. Perhaps the self.view gets called before the loadView somewhere, maybe it's because initWithNibName triggers something you don't want, maybe it's because your view is smaller than the screen/window, which is not advised, maybe the new iOS has a bug in it... With so little of your code, no way to debug and an incomplete error message, I can't see what causes it.
However, I might have a workaround:
How about removing that loadView override so that the viewController makes its own view, then add your mainView to that default view?
The exception says you are adding a nil to an array. I'm think tableViewHeader could be considered an objectAtIndex:0 since it's the first of an array of rows. Try putting this line above it :
NSLog(#"searchBar = %#",searchBar;
self.tableView.tableHeaderView = searchBar;
Edit(1):
Then try these:
NSLog(#"self = %#",self);
NSLog(#"self.tableView = %#",self.tableView);
NSLog(#"self.tableView.tableHeaderView = %#",self.tableView.tableHeaderView);
self.tableView.tableHeaderView = searchBar;
Edit(2):
if ([self.tableView respondsToSelector:#selector(setTableHeaderView:)]){
NSLog(#"tableView does respond");
[self.tableView setTableHeaderView:nil];
}
I don't think this will fix the problem, but it should help hunt down the problem.