I'm following along with the Big Nerd Ranch ios book, chapter 11. It uses a table view in the ItemsViewController to display a list of items and if you click on an item it transfers control to a DetailViewController that shows the details about the individual item. The DetailViewController.xib has outlets for these fields declared in the header file DetailViewController.h
__weak IBOutlet UITextField *nameField;
__weak IBOutlet UITextField *serialNumberField;
__weak IBOutlet UITextField *valueField;
__weak IBOutlet UILabel *dateLabel;
The application worked fine up to a point. If i clicked on an item, the new view would open but the fields were empty. Therefore, the tutorial introduced us to a way to have the fields populated with the values of each item. After introducing this new code to populate the fields, the application crashes with the following errors once I click on an item to view its details
2014-02-12 07:18:30.720 Homepwner[3180:a0b] -[UIView setText:]: unrecognized selector sent to instance 0x89ef8c0
2014-02-12 07:18:30.724 Homepwner[3180:a0b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIView setText:]: unrecognized selector sent to instance 0x89ef8c0'
Based on the code excerpts below can you explain why the application might be crashing when I click on one of the items in the list to open it in the detail view?
This is the code we are given to populate the fields
DetailViewController.m
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[nameField setText:[item itemName]];
[serialNumberField setText:[item serialNumber]];
[valueField setText:[NSString stringWithFormat:#"%d", [item valueInDollars]]];
// Create a NSDateFormatter that will turn a date into a simple date string
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[dateFormatter setTimeStyle:NSDateFormatterNoStyle];
// Use filtered NSDate object to set dateLabel contents
[dateLabel setText:[dateFormatter stringFromDate:[item dateCreated]]];
// Change the navigation item to display name of item
[[self navigationItem] setTitle:[item itemName]];
}
In ItemViewController.m, we added the middle three lines in this function so that the DetailViewController has its item before viewWillAppear: gets called
- (void)tableView:(UITableView *)aTableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
DetailViewController *detailViewController = [[DetailViewController alloc] init];
NSArray *items = [[BNRItemStore sharedStore] allItems];
BNRItem *selectedItem = [ items objectAtIndex: [indexPath row]];
[detailViewController setItem:selectedItem];
[[self navigationController] pushViewController:detailViewController animated:YES];
}
Since the nameField property is initialized through a NIB/Storyboard, the most likely reason for the problem that you are seeing is an incorrect connection done in the Interface Builder. It appears that the nameField outlet is connected to the parent of UITextEdit, as opposed to the UITextEdit itself.
Removing and re-creating the nameField property should fix the problem. First, remove the property from the code. Then, control-drag from the text field into the DetailViewController's class extension inside DetailViewController.m, and name the property nameField.
Related
In one class called LevelSelectViewController, I have this public property
#property (nonatomic, strong, getter=getLevelNumber) NSNumber *levelNumber;
which stores an int value based on a UIButton* touch selection using the method
- (IBAction)buttonPressedSoWhatNumber:(id)sender
{
UIButton *button = (UIButton *)sender;
int row = button.tag;
_levelNumber = [NSNumber numberWithInt:row];
}
When I put a breakpoint at the end of the method to see if my touch interaction triggers the correct result based on what I coded (when I press button 1, really), _levelNumber reads 0 (which it should). I also have a getter method written out for it.
Now, in this second class called GameViewController, I have a method setUpBoards which (should) obtain that value for *levelNumber. It looks like this:
- (void)setUpBoards {
LevelSelectViewController* level = [[LevelSelectViewController alloc] init];
[level getLevelNumber];
[self createLevelModel:(int)level];
}
In that same class, the method createLevelModel:(int)levelIndex uses that value to be passed to 5 initialization methods that access a Levels.plist file to load data for my game.
Basically, that number represents what level button I pressed and uses that number to load the correct level. In another manner, I have verified that those 5 initialization methods work along with loading data from my Levels.plist file.
Now, between the transition from LevelSelectViewController to GameViewController, I receive the NSRangeException error message:
'NSRangeException', reason: '-[__NSCFArray objectAtIndex:]: index (554166800) beyond bounds (1)'
even when pressing the 1 button (which should work considering I only have Item 0 in my plist typed out.......which, again, I verified worked using another manner).
TO ADD ON TO THIS. Here's another important method:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"cvCell";
CVCell *cell = (CVCell *)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
NSMutableArray *data = [self.dataArray objectAtIndex:indexPath.section];
NSString *cellData = [data objectAtIndex:indexPath.row];
[cell.buttonClick setTag:indexPath.row];
[cell.buttonClick addTarget:self action:#selector(buttonPressedSoWhatNumber:)
forControlEvents:UIControlEventTouchUpInside];
[cell addSubview:cell.buttonClick];
return cell;
}
Any insight?
Here's the push controller method from LevelSelectViewController to GameViewController:
-(IBAction)buttonPressed:(id)sender {
GameViewController* obj = [[GameViewController alloc] initWithNibName:#"GameViewController" bundle:nil];
[self.navigationController pushViewController:obj animated:YES];
}
buttonPressed: is another method given to the UIButton*
You need a simple int property in LevelSelectViewController that you can use to store the level that has been selected:
#property int levelSelected;
The store the selected value in your button press handler:
- (IBAction)buttonPressedSoWhatNumber:(UIButton *)sender
{
self.levelSelected = sender.tag;
}
Then you can pass this to a corresponding int property on your GameViewController;
-(IBAction)buttonPressed:(id)sender {
GameViewController* obj = [[GameViewController alloc] initWithNibName:#"GameViewController" bundle:nil];
obj.level = self.selectedLevel
[self.navigationController pushViewController:obj animated:YES];
}
The problem is that you are casting a pointer to an int.
#property (nonatomic, strong, getter=getLevelNumber) NSNumber *levelNumber;
Defines a pointer to an object of type NSNumber.
[self createLevelModel:(int)level];
Is casting that NSNumber * to an int.
You also have another bug in that you are setting level as the view controller and calling getLevelNumber but not actually using the returned value. So here is what I would do. Firstly you don't need to define an NSNumber and don't need a custom getter. Just use this:
#property (nonatomic, assign) int levelNumber;
Then this becomes much simple:
- (void)setUpBoards {
LevelSelectViewController* levelSelectViewController = [[LevelSelectViewController alloc] init];
[self createLevelModel: levelSelectViewController.levelNumber]; // Always going to be zero at this point.
}
I'm using the master detail template.
Header file MasterViewController.h:
#import <UIKit/UIKit.h>
//(Imported both MasterViewController.h and DetailViewController.h in implementation file of MasterViewController.m)
#class DetailViewController;
#interface MasterViewController : UITableViewController
#property (strong, nonatomic) DetailViewController *detailViewController;
-(void)createFlowerData;
#end
Implementation file HeaderViewController.m:
#interface MasterViewController () {
NSMutableArray *_objects;
NSArray *_flowerData;
NSArray *_flowerSections;
}
#end
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.navigationItem.leftBarButtonItem = self.editButtonItem;
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(insertNewObject:)];
self.navigationItem.rightBarButtonItem = addButton;
self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController];
//invoking the method I implemented to give data to flowerData array
[self createFlowerData];
}
-(void)createFlowerData{
NSMutableArray *redFlowers;
NSMutableArray *blueFlowers;
redFlowers = [[NSMutableArray alloc] init];
blueFlowers = [[NSMutableArray alloc] init];
//create the 2 sections for the flowerSections array
_flowerSections = #[#"Red Flowers", #"Blue Flowers"];
//add the objects to the mutable array
//red flowers
[redFlowers addObject:#{#"name":#"Poppy",#"picture":#"Poppy.png",#"url":#"http://en.wikiepdia.org/wiki/Poppy"}];
[redFlowers addObject:#{#"name":#"Tulip",#"picture":#"Tulip.png",#"url":#"http://en.wikipedia.org/wiki/Tulip"}];
[redFlowers addObject:#{#"name":#"Gerbera",#"picture":#"Gerbera.png",#"url":#"http://en.wikiepdia.org/wiki/Gerbera"}];
//blue flowers
[blueFlowers addObject:#{#"name":#"Phlox",#"picture":#"Phlox.png",#"url":#"http:en.wikipedia.org/wiki/Gerbera"}];
[blueFlowers addObject:#{#"name":#"Pin Cushion Flower",#"picture":#"Pincushion flower.png",#"url":#"http://en.wikipedia.org/wiki/Scabious"}];
[blueFlowers addObject:#{#"name":#"Iris",#"picture":#"Iris.png",#"url":#"http://en.wikipedia.org/wiki/Iris_(plant)"}];
_flowerData = #[redFlowers, blueFlowers];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [_flowerSections count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//find the number of row elements in a given section of the flower Data array
return [_flowerData[section] count];
}
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
//index 0 is the red flower
//index 1 is the blue flower
return _flowerSections[section];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"flowerCell"];
cell.textLabel.text = _flowerData[indexPath.section][indexPath.row][#"name"];
cell.detailTextLabel.text = _flowerData[indexPath.section][indexPath.row][#"url"];
cell.imageView.image = _flowerData[indexPath.section][indexPath.row][#"picture"];
return cell;
}
I then get the following when I build the application on the ios simulator(iPad):
2013-09-01 23:49:40.015 flowerDetail2[2394:c07] -[__NSCFConstantString _isResizable]: unrecognized selector sent to instance 0x6af4
2013-09-01 23:49:40.017 flowerDetail2[2394:c07] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFConstantString _isResizable]: unrecognized selector sent to instance 0x6af4'
*** First throw call stack:
(0x1c95012 0x10d2e7e 0x1d204bd 0x1c84bbc 0x1c8494e 0x4ca7ab 0x121ae9 0x2c1f 0xd18fb 0xd19cf 0xba1bb 0xcab4b 0x672dd 0x10e66b0 0x2291fc0 0x228633c 0x2291eaf 0x1062bd 0x4eb56 0x4d66f 0x4d589 0x4c7e4 0x4c61e 0x4d3d9 0x502d2 0xfa99c 0x47574 0x4776f 0x47905 0x8dceab6 0x50917 0x1496c 0x1594b 0x26cb5 0x27beb 0x19698 0x1bf0df9 0x1bf0ad0 0x1c0abf5 0x1c0a962 0x1c3bbb6 0x1c3af44 0x1c3ae1b 0x1517a 0x16ffc 0x1bed 0x1b15)
libc++abi.dylib: terminate called throwing an exception
(lldb)
(Please note that I did not include everything, just the parts I thought were important)
This has been driving me crazy the whole day, I've checked multiple times, rewritten the whole thing and still the same result, I can't even get the cells to display. I googled, and I found something like it means that I'm sending a message to a method that doesn't know what to do with it, but I'm sure it's right? Could somebody please help me debug this!
cell.imageView.image = _flowerData[indexPath.section][indexPath.row][#"picture"];
this line cell.imageView.image expect UIImage type
[redFlowers addObject:#{#"name":#"Gerbera",#"picture":#"Gerbera.png",#"url":#"http://en.wikiepdia.org/wiki/Gerbera"}];
but you give it a NSString here, which of cause will make runtime error.
So should be like this
cell.imageView.image = [UIImage imageNamed:_flowerData[indexPath.section][indexPath.row][#"picture"]];
If you are NOT using ARC: You need to retain each of your dictionaries. Like this redFlowers = [[[NSMutableArray alloc] init] retain]; do that for each of your arrays. Also, NSLog(#"%#", redFlowers) before the line of code where your app crashes and post the output. Replace redFlowers with whatever the dictionary being called next is.
From what i am getting, what you need to do is first make NSDictionary objects and than add those objects to NSMutableArray.
NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
[dic setValue:#"Poppy" forKey:#"name"];
[dic setValue:#"Poppy.png" forKey:#"picture"];
[dic setValue:#"http://en.wikiepdia.org/wiki/Poppy" forKey:#"url"];
[redFlowers addObject:dic];
[dic release]; // Dont use this in case of ARC
Repeat this for all three or number of objects you need to add. Hope this helps.
I am working through an iOS book and I've been extending an example program for practice when I stumbled into a issue I don't understand completely.
I'm currently working with a UINavigationController that has a rootView, a detailView and a third level view that I intend to turn into a modal view controller.
Everything is working as expected. My rootViewController is a UITableView that allows a user to select a row. The row will open a detailViewController for the given object that displays:
Item Title
Item Serial Number
Item Value
Purchase Date
On this DetailViewController there is a "Change Purchase Date" button that presents another ViewController to select a new date:
After Selecting a date and hitting the "Set Purchase Date" button I have the viewWillDisappear method save the changes to the Item Object in the third ViewController and then pop a view off the stack to return to the DetailViewController:
FILE: PurchaseDateViewController.m:
- (void) viewDidDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// Clear first responder
[[self view] endEditing:YES];
// "Save" the changes made to the creationDate
[_item setDateCreated:[purchaseDatePicker date]];
/* Debugging Log */
// Create a NSDateFormatter that will turn the date into a simple string
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[dateFormatter setTimeStyle:NSDateFormatterNoStyle];
// NSLogging
NSLog(#"Original Date: %#", [dateFormatter stringFromDate:_itemsOldDate]);
NSLog(#"DatePicker Date: %#", [dateFormatter stringFromDate:[purchaseDatePicker date]]);
NSLog(#"Stored Date: %#", [dateFormatter stringFromDate:[_item dateCreated]]);
// Reload the DetailViewController to update the changes if the NavigationController is used to return to the DetailViewController
}
#pragma mark Action Methods to Update the Data Source
- (IBAction)setPurchaseDate:(id)sender {
// "Save" the changes made to the creationDate
[_item setDateCreated:[purchaseDatePicker date]];
// Move back 1 level on the NavigationController Stack
[[self navigationController] popViewControllerAnimated:YES];
}
- (void)cancelChange {
// "Reset" the changes made to the creationDate
[_item setDateCreated:_itemsOldDate];
// Move back 1 level on the NavigationController Stack
[[self navigationController] popViewControllerAnimated:YES];
}
All of the methods listed above work.
When using the "Set Purchase Date" button and PurchaseDateViewController is popped off the stack, the DetailViewController is updated immediately.
However, when I press the NavigationController back button, the view is NOT updated and the old date remains present on the DetailViewController. What's really weird is that the Item Object has been updated behind the scenes as my NSLog statements proved. This makes me believe that for some reason -viewWillAppear is not being called again.
This is confirmed if I hit the NavigationController's back button again and reselect the same row. When I take this testing approach, the date is updated to new date when re-entering the DetailViewController from the rootViewController. Since the DetailViewController is removed from the NavigationController stack and then instantiated again (therefore calling the DetailViewController's viewWillAppear again) the change is made.
Why isn't the viewWillLoad method getting called in this case?
How come the change is not present when using the UINavigationControls and is there a way to explicitly reload the view?
What is the recommend approach to overcoming this problem?
While writing this post I did find a way to force the viewWillAppear method to get called, but I feel there must be a more 'standardized' approach to overcoming this issue. What would the best practice be?
Here is the 'cheat' that I used to get around the problem:
PurchaseDateViewController.m
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
[viewController viewWillAppear:animated];
}
- (void) viewDidDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// Clear first responder
[[self view] endEditing:YES];
// "Save" the changes made to the creationDate
[_item setDateCreated:[purchaseDatePicker date]];
/* Debugging Log */
// Create a NSDateFormatter that will turn the date into a simple string
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[dateFormatter setTimeStyle:NSDateFormatterNoStyle];
// NSLogging
NSLog(#"Original Date: %#", [dateFormatter stringFromDate:_itemsOldDate]);
NSLog(#"DatePicker Date: %#", [dateFormatter stringFromDate:[purchaseDatePicker date]]);
NSLog(#"Stored Date: %#", [dateFormatter stringFromDate:[_item dateCreated]]);
// Reload the DetailViewController to update the changes if the NavigationController is used to return to the DetailViewController
[self navigationController:[self navigationController] willShowViewController:_detailViewController animated:YES];
}
The will be giving you a problem:
- (void) viewDidDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
Should be
- (void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
Typo? Or does that fix things?
I have a table view controller with (among others) a cell that is to represent a date. I followed this post "How do I make a modal date picker that only covers half the screen?" and I have it working with one big exception - I can't get the picker to disappear!
I tried registering for the event UIControlEventTouchUpOutside, but it seems that this is not generated by the picker (or at least in the mode that I am using it).
How can I recognize that the user has finished selecting a date?
Also, is there a way to disable the user from inputting directly into the UITextField? I want to force them to use the picker. I saw this post "Disable blinking cursor in UITextField?", but is there another way?
Reagards,
--John
Try this code.. here I am putting an datepicker to a uitextfield.. it will have a done button at the top right navigation bar.. so by clicking done I will user can dismiss the datepicker.. the another best method is by putting a toolbar above the datepicker having the done button.. Try this it will work.. when changing the datepicker you can populate the text field.. Hope this helps..
see this stackoverflow link https://stackoverflow.com/a/4824319/763747 this will have the datepicker with done button as toolbar above the keybord..
#pragma mark -
#pragma mark - TextField Delegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return TRUE;
}
- (void) textFieldDidBeginEditing:(UITextField *)textField {
itsRightBarButton.title = #"Done";
itsRightBarButton.style = UIBarButtonItemStyleDone;
itsRightBarButton.target = self;
itsRightBarButton.action = #selector(doneAction:);
if ([textField isEqual:itsIncidentDateTextField])
{
itsDatePicker = [[[UIDatePicker alloc] init] autorelease];
itsDatePicker.datePickerMode = UIDatePickerModeDate;
[itsDatePicker addTarget:self action:#selector(incidentDateValueChanged:) forControlEvents:UIControlEventValueChanged];
//datePicker.tag = indexPath.row;
textField.inputView = itsDatePicker;
}
}
- (IBAction) incidentDateValueChanged:(id)sender{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"MMM d, yyyy"];
itsIncidentDateTextField.text = [dateFormatter stringFromDate:[itsDatePicker date]];
[dateFormatter release];
}
I used a different method for disappearing the UIDatePicker.
create a XIB (nib) file and add to it a UIDatePicker then resize the view so it fits only the UIDatePicker, create a property (make it strong and nonatomic) in your ViewController (or whatever class your using, and synthesize of course).
#property (nonatomic, strong) UIView *myDatePickerView;
#synthesize myDatePickerView;
then create a loadDatePickerView method
- (void) loadDatePickerView
{
UINib *nib = [UINib nibWithNibName:kNIBname bundle:[NSBundle mainBundle]];
NSArray *views = [nib instantiateWithOwner:self options:nil];
self.myDatePickerView = [views firstObject];
[myDatePickerView setFrame:CGRectMake(0, 318, 320, 162)];
[self.view addSubview:myDatePickerView];
}
implement the UITextFieldDelegate, and in the textFieldDidBeginEditing method call the loadDatePickerView method,
[self loadDatePickerView];
to make function create a property which is a UIDatePicker instance
#property (nonatomic,strong)
UIDatePicker *myDatePicker;
(synthesize of course)
now create an IBAction like so:
-(IBAction)datePickerValueChanged:(UIDatePicker *)sender
{
myDatePicker = [[myDatePickerView subviews] lastObject];
//now you can do whatever you want with the DatePicker
}
now connect the IBAction to the picker in the XIB file, that way the XIB is now the UIDatePicker instance you created in the VC, if you want it to disappear you can add a UITapGestureRecognizer (in the ViewDidLoad) and the selector will be another IBAction which removes myDatePickerView from its' superView like this:
- (void)viewDidLoad
{
[super viewDidLoad];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(dropPicker:)];
[self.view addGestureRecognizer:tap];
[self datePickerValueChanged:myDatePicker];
}
-(IBAction)dropPicker:(id)sender
{
[myDatePickerView removeFromSuperview];
}
I'm implementing a uinavigationcontroller. The first view is a uitableview (imagine the Contacts app) with a list of names.
The second view is the person profile.
So, when I click a person in the uitable, it's suppose to load his profile.
How do I pass the person data to the second view?
In didSelectRowAtIndexPath I do:
ContactView * varContactView = [[ContactView alloc] initWithNibName:nil bundle:nil];
varContactView.title = [[contactsArray objectAtIndex:indexPath.row] name];
[varContactView initWithPerson:[contactsArray objectAtIndex:indexPath.row]];
[navigationController pushViewController:varContactView animated:YES];
In the interface of ContactView I've got:
Person * person;
And then:
#property (nonatomic, retain) Person * person;
-(void) initWithPerson:(Person *)newperson;
And in .m:
#synthesize person
-(void) initWithPerson:(Person *)newperson{
person = [[Person alloc] init];
person = newperson;
}
However, when I try to access person in ContactView, it says always EXC_BAD_ACCESS.
What is wrong here?
Instead of:
[varContactView initWithPerson:[contactsArray objectAtIndex:indexPath.row]];
you can simply use:
varContactView.person = [contactsArray objectAtIndex:indexPath.row];
That will make use of person property setter and assign the given object as varContactView's data. The default implementation of that setter is (in case of retain property):
- (void)setPerson:(Person *)newPerson
{
if (person != newPerson) {
[person release];
person = [newPerson retain];
}
}
That's what you're trying to achieve in -initWithPerson: method. That method is not needed, as its functionality is covered by person property setter. BTW, remember to release person property in -dealloc method of your view controller:
- (void)dealloc
{
[person release];
[super dealloc];
}
The bad access exception might be caused by something else in your code, though...
In .m file, change the code as given below.
-(void) initWithPerson:(Person *)newperson
{
self.person = newperson;
}