I'm making an application, which has a Settings page. For the Settings page, I am using a UITableViewController with static UITableViewCells. I am using NSUserDefaults to store the settings.
Now, this is the Settings View Controller :
According to the code in the viewDidLoad function in the SettingsTableViewController.m, there is supposed to be a check on the saved setting, and depending on that, the alpha property of the Notification Interval cell is supposed to change. This works fine if I do it in the tableView:didSelectRowAtIndexPath method, however, it is not working in the viewDidLoad method. Here's the source :
#import "SettingsTableViewController.h"
#interface SettingsTableViewController ()
#end
#implementation SettingsTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
// Check if setting is already saved.
if ([defaults stringForKey:#"notification"]) {
self.notificationTableViewCell.detailTextLabel.text = [defaults stringForKey:#"notification"];
// Disable the interval cell, if the notifications are turned off.
if([[defaults stringForKey:#"notification"] isEqualToString:#"Off"]) {
self.intervalTableViewCell.alpha = 0.439216f;
self.intervalTableViewCell.userInteractionEnabled = NO;
}
} else {
// If no setting is saved, reset to default settings.
self.notificationTableViewCell.detailTextLabel.text = #"On";
[defaults setObject:#"On" forKey:#"notification"];
[defaults synchronize];
}
// Check if setting is already saved.
if([defaults stringForKey:#"interval"]) {
self.intervalTableViewCell.detailTextLabel.text = [defaults stringForKey:#"interval"];
} else {
// If no setting is saved, reset to default settings.
self.intervalTableViewCell.detailTextLabel.text = #"5 min";
[defaults setObject:#"5 min" forKey:#"interval"];
[defaults synchronize];
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Get selected cell.
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
// Reset the state of the cell.
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
if(cell == self.notificationTableViewCell) {
if([cell.detailTextLabel.text isEqual: #"On"]) {
cell.detailTextLabel.text = #"Off";
[[NSUserDefaults standardUserDefaults] setObject:#"Off" forKey:#"notification"];
self.intervalTableViewCell.alpha = 0.439216f;
self.intervalTableViewCell.userInteractionEnabled = NO;
} else {
cell.detailTextLabel.text = #"On";
[[NSUserDefaults standardUserDefaults] setObject:#"On" forKey:#"notification"];
self.intervalTableViewCell.alpha = 1.0f;
self.intervalTableViewCell.userInteractionEnabled = YES;
}
} else if(cell == self.intervalTableViewCell) {
NSArray *possibleTimes = #[#"1 min", #"5 min", #"10 min", #"30 min"];
int indexOfCurrent = 0;
for (int i = 0; i < possibleTimes.count; i++) {
if([self.intervalTableViewCell.detailTextLabel.text isEqualToString:possibleTimes[i]]) {
indexOfCurrent = i;
}
}
// Return to the starting value of the array.
if(++indexOfCurrent == possibleTimes.count) {
indexOfCurrent = 0;
}
self.intervalTableViewCell.detailTextLabel.text = possibleTimes[indexOfCurrent];
[[NSUserDefaults standardUserDefaults] setObject:possibleTimes[indexOfCurrent] forKey:#"interval"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
- (BOOL)prefersStatusBarHidden {
return YES;
}
#end
First of as NSString comparation method you should use this:
isEqualToString:
instead of
isEqualToString:
Second thing is the way you find if selected cell is the one you want to check is not the best idea, I mean this:
if(cell == self.notificationTableViewCell)
Don't do that - you will find yourself in a huge troubles pretty quickly.
Proper way of handling that is to use enumerations, and switch case based on indexPath section, row values. Otherwise you keep a reference to a memory where cell is, but this cell is gonna be reused, by queuing mechanism of UITableView, for a different row - so you're gonna end up keeping some random reference to random cells at the end here.
For the alpha problem I think you should reload table data after you synchronize NSUserDefaults inside didSelectRowAtIndexPath method.
Use reloadData method from UITableView, or even better reload only the section you know that's gonna change on click (optimized version, but you can start with reloading whole table).
Move the code that changes the alpha property of the cell into a method that occurs later in the view lifecycle. A good one to try would be viewWillAppear:.
Related
This is my error:
-[__NSArrayM objectAtIndex:]: index 12 beyond bounds for empty array
I know this error means I'm trying to access an "empty array".
This error only happens in viewX when it is popped back from viewY. When you press 'back button' on navigation bar in viewY and scroll the tableView immediately, it will crash and cause this error. I am using the RETableViewManager to load my tableView.
In viewX's viewDidLoad:
[[RACSignal combineLatest:#[RACObserve(self, record), RACObserve(self, restaurant)]] subscribeNext:^(id x) {
[self setupItems];
}];
in setupItems:
RecordManager *recordManager = [[EZRecordManager alloc] initWithRecord:self.record restaurant:self.restaurant sender:self.navigationController];
self.items = [recordManager items];
self.section = [RETableViewSection section];
[self.items each:^(id data) {
if ([data isKindOfClass:[NSString class]]) {
self.navigationItem.title = (NSString *)data;
} else {
[self registerItem:[data class]];
[self.section addItem:data];
}
}];
[self.manager addSection:self.section];
[self.tableView reloadData];
I NSLogged my array 'self.items'. and this is what logs according to the method:
viewDidAppear - (
"\U5df2\U8a02\U4f4d\Uff0c\U5c1a\U672a\U7528\U9910",
"<REReservationHeaderItem: 0x14015b0b0>",
"<REAttributedStrItem: 0x14015b1b0>",
"<REAttributedStrWithNextItem: 0x140191a70>",
"<REAttributedStrItem: 0x140193f60>",
"<RESpacerItem: 0x140194870>",
"<REAttributedStrWithNextItem: 0x14019ce10>",
"<REAttributedStrItem: 0x140199230>",
"<RESpacerItem: 0x1401a04e0>",
"<REActionItem: 0x14019e490>",
)
The NSLog logs the same array in setupItems so I know the array is still there because self.item is saved as a property:
#property (nonatomic, strong) NSArray *items;
So this algorithm works as expected when I'm loading viewX for the first time, but as soon as I go into another view(viewY) and press the 'back button' on viewY to pop to viewX and then immediately scroll, it crashes with the above error. If I wait for a second (maybe even half a second), viewX will work properly and have no issue. I know this is minor but my PM is stressing that this shouldn't happen. How can I solve this problem?
The method the error occurs in (part of the RETableViewManager library):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
RETableViewSection *section = [self.mutableSections objectAtIndex:indexPath.section];
RETableViewItem *item = [section.items objectAtIndex:indexPath.row];
UITableViewCellStyle cellStyle = UITableViewCellStyleDefault;
if ([item isKindOfClass:[RETableViewItem class]])
cellStyle = ((RETableViewItem *)item).style;
NSString *cellIdentifier = [NSString stringWithFormat:#"RETableViewManager_%#_%li", [item class], (long) cellStyle];
Class cellClass = [self classForCellAtIndexPath:indexPath];
if (self.registeredXIBs[NSStringFromClass(cellClass)]) {
cellIdentifier = self.registeredXIBs[NSStringFromClass(cellClass)];
}
if ([item respondsToSelector:#selector(cellIdentifier)] && item.cellIdentifier) {
cellIdentifier = item.cellIdentifier;
}
RETableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
void (^loadCell)(RETableViewCell *cell) = ^(RETableViewCell *cell) {
cell.tableViewManager = self;
// RETableViewManagerDelegate
//
if ([self.delegate conformsToProtocol:#protocol(RETableViewManagerDelegate)] && [self.delegate respondsToSelector:#selector(tableView:willLoadCell:forRowAtIndexPath:)])
[self.delegate tableView:tableView willLoadCell:cell forRowAtIndexPath:indexPath];
[cell cellDidLoad];
// RETableViewManagerDelegate
//
if ([self.delegate conformsToProtocol:#protocol(RETableViewManagerDelegate)] && [self.delegate respondsToSelector:#selector(tableView:didLoadCell:forRowAtIndexPath:)])
[self.delegate tableView:tableView didLoadCell:cell forRowAtIndexPath:indexPath];
};
if (cell == nil) {
cell = [[cellClass alloc] initWithStyle:cellStyle reuseIdentifier:cellIdentifier];
loadCell(cell);
}
if ([cell isKindOfClass:[RETableViewCell class]] && [cell respondsToSelector:#selector(loaded)] && !cell.loaded) {
loadCell(cell);
}
cell.rowIndex = indexPath.row;
cell.sectionIndex = indexPath.section;
cell.parentTableView = tableView;
cell.section = section;
cell.item = item;
cell.detailTextLabel.text = nil;
if ([item isKindOfClass:[RETableViewItem class]])
cell.detailTextLabel.text = ((RETableViewItem *)item).detailLabelText;
[cell cellWillAppear];
return cell;
}
Usually when "waiting a little fixes the problem", it's because you have an async problem.
Something to check first :
Make sure your reload code is called when you move back. Maybe your tableview didn't get emptied, but the array did. Moving back would let you scroll the old content (still loaded) but the delegate methods won't be able to create new cells because the array is now empty.
If you wait, your async method does it's job and the array is now full again, which makes everything work fine.
Possible solution :
Empty then reload the tableview in viewWillAppear. This will cause a visual flash of the tableview going empty and then full again. It will also scroll you to the first element. That being said, it's really easy and fast, and with a spinner it will appear much smoother.
Other possible solution :
Keep the data loaded after leaving the page, so when you come back it's still there. You can use anything that will keep the data loaded while in the app. It could be a singleton class that stays instantiated, or save in a database and reload from it (it's much faster than straight up loading from the internet), or anything that you can think of.
I've been working on a new tips & tricks project for a game called Dofus. In the first view the user is asked to pick their class out of a picker. Then they go to a next view and pick their level and stuff like that. The problem with the picker however is that its getting the wrong class out of the picker.
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
return [self.classNames count];
}
- (NSString *)pickerView:(UIPickerView *)pickerView
titleForRow:(NSInteger)row
forComponent:(NSInteger)component {
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSString *_myClass = self.classNames[row];
[prefs setObject:_myClass forKey:#"class"];
return self.classNames[row];
}
In the ViewDidLoad i declared my classes:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"background"]];
self.classNames = #[#"Cra", #"Ecaflip", #"Eliotrope", #"Eniripsa",
#"Enutrof", #"Feca", #"Foggernout", #"Iop", #"Masqueraider", #"Osamodas", #"Pandawa", #"Rogue", #"Sacrier", #"Sadida", #"Sram", #"Xelor"];
}
Then in the next view I get the information like this (different .m file):
- (void)viewDidLoad {
[super viewDidLoad];
// Do view setup here.
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"background"]];
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
// getting an NSString
NSString *myClass = [prefs stringForKey:#"class"];
_textLabel.text = [NSString stringWithFormat:#"So you are a %# huh? Pretty cool! Its a good class. Fill out the rest so we are ready to go!", myClass];
}
Anyone any idea's how this is happening? Thanks in advance!
You are setting your preferences on the Picker's datasource call for the row title, instead of the delegate call for when the row is selected. For every row that is loaded into the view, pickerView:titleForRow:forComponent: is called, at which point you set the class preference, however, this does not correspond to the row that is selected in the picker, it corresponds to the next row to be displayed.
Instead, respond to the
pickerView:didSelectRow:inComponent: method, and set your preference there (you will still need to respond to the other one to set the row titles, just don't set the preference).
- (NSString *)pickerView:(UIPickerView *)pickerView
didSelectRow:(NSInteger) row
inComponent:(NSInteger) component {
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSString *_myClass = self.classNames[row];
[prefs setObject:_myClass forKey:#"class"];
}
If you want store the selected row by the user you should this method from UIPickerViewDelegate
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component;
Something like that would work:
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSString *_myClass = self.classNames[row];
[prefs setObject:_myClass forKey:#"class"];
[prefs synchronize];
}
}
Take a look at these links to better understand how works UIPickerView :
http://nscookbook.com/2013/01/ios-programming-recipe-7-using-the-uipickerview/
http://www.techotopia.com/index.php/An_iOS_7_UIPickerView_Example
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIPickerView_Class/index.html#//apple_ref/doc/uid/TP40006842-CH3-SW9
Hi friends am new in iphone apps developing in my project, am using storyboards I have a task where I need to authenticate for loading viewController on clicking from tableViewCell. I have subclassed UITableViewCell and I created a segue from that cell to viewController.
Now the problem is i need to authenticate the controller while clicking cell. if the user has not logged in it should goto login controller, else it it should go to activity controller. I have saved user details in NSUserDefaults. but I don't know how to authenticate it in cellForRowAtIndexPathon UITableViewCell
here is the code for my configuring cell:
else if(indexPath.row == 4) {
static NSString *simpleTableIdentifier4 =#"MenuDetails4";
activismCell *cell4 = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier4 forIndexPath:indexPath];
cell4.imgactivism.image= [UIImage imageNamed:#"icon5.png"];
cell4.activism.text = localize(#"some.activism");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if([defaults objectForKey:#"firstname"]!=NULL && [defaults objectForKey:#"lastname"]!=NULL) {
[self performSegueWithIdentifier:#"goto" id:indexpath];
}
else if ([defaults objectForKey:#"firstname"] ==NULL && [defaults objectForKey:#"lastname"]==NULL) {
[self performSegueWithIdentifier:#"login" id:indexpath];
}
return cell4;
On clicking that particular tableViewCell if user already logged in it should goto activity ViewController else it it should goto login view controller.
Please help me how to do it. Thanks in advance
You don't perform the segue in the method cellForRowAtIndexPath, you need to put this code in the method:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if([defaults objectForKey:#"firstname"]!=NULL && [defaults objectForKey:#"lastname"]!=NULL){
[self performSegueWithIdentifier:#"goto" id:indexpath];
}
else if ([defaults objectForKey:#"firstname"] ==NULL && [defaults objectForKey:#"lastname"]==NULL) {
[self performSegueWithIdentifier:#"login" id:indexpath];
}
}
in this method you can check the nsuserdefaults and then perform the segue accordingly.
Hi if you want to move after selection a specific row.Then you need to implement this method of tableView.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//You can apply your logic of selected row.
ViewControllerName *VC=[self.storyboard instantiateViewControllerWithIdentifier:#"ViewControllerName"];
aboutUsVC.strQuote=[quoteArry objectAtIndex:[indexPath row]];
[self.navigationController pushViewController:VC animated:YES];
//This one for presenting the viewController modally
//[self presentViewController:VC animated:YES completion:nil];
}
Thanks
I store the information of a user in a variable of type NSUserDefaults when the user clicks on the save button.
I would like the launch of the application, the settings can be saved in the corresponding cells filled. So here's what I did in the corresponding file in the viewDidLoad method.
- (void)viewDidLoad
{
// [super viewDidLoad];
// Do any additional setup after loading the view.
if (self.navigationPaneBarButtonItem)
[self.navigationBar.topItem setLeftBarButtonItem:self.navigationPaneBarButtonItem
animated:NO];
//Initialise IndexPath
NSIndexPath *indexPathBroker = [NSIndexPath indexPathForRow:0 inSection:0];
NSIndexPath *indexPathPort = [NSIndexPath indexPathForRow:1 inSection:0];
NSUserDefaults *defaultConf;
defaultConf=[NSUserDefaults standardUserDefaults];
NSString * broker =[defaultConf stringForKey:#"broker"];
NSString *port= [defaultConf stringForKey:#"port"];
NSLog(#"Brokerdidload: %# et Portdidload: %#",broker,port);
//Create strings and integer to store the text info
ConfigDetailEditTableCell *editCellBroker = (ConfigDetailEditTableCell *)[_tableView cellForRowAtIndexPath:indexPathBroker];
ConfigDetailEditTableCell *editCellPort = (ConfigDetailEditTableCell *)[_tableView cellForRowAtIndexPath:indexPathPort];
//set data
editCellPort.EditCellValue.text =port;
editCellBroker.EditCellValue.text=broker;
// [editCellBroker.EditCellValue setText:broker];
// [[editCellPort textLabel] setText:port];
// [[editCellBroker textLabel] setText:broker];
// [[editCellPort textLabel] setPlaceholder:port];
// [[editCellBroker textLabel] setPlaceholder:broker];
// editCellPort.EditCellValue.text =*/
NSLog(#"editCellPort.EditCellValue: %# et editCellBroker.EditCellValue: %#",editCellPort.EditCellValue.text,editCellBroker.EditCellValue.text);
}
Variables in the broker and port, I have information but when I try to update the cell it does not work. The last NSLog that I am Null references
How to remedy????
Thank you in advance
Instead of trying to set the value of cell from viewDidLoad you should do it in cellForRowAtIndexPath: dataSource method of tableView
- (void)viewDidLoad
{
// [super viewDidLoad];
// Do any additional setup after loading the view.
if (self.navigationPaneBarButtonItem)
[self.navigationBar.topItem setLeftBarButtonItem:self.navigationPaneBarButtonItem
//Rest are not needed
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//Initialize cell
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
NSString * broker =[defaults stringForKey:#"broker"];
NSString *port= [defaults stringForKey:#"port"];
cell.EditCellValue.text = indexPath.row==0?broker:port;
return cell;
}
You can also use the free Sensible TableView framework to automatically fetch your data from NSUserDefaults and display it on a table view. It will also automatically save whatever data has been entered back to NSUserDefaults. Comes in very handy.
I have a tableView where I can select a ship from a list, and when I tap it I want the information to pop up on another screen. The way I am currently doing this is:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
{
cargoShips* ship=[boatsForOwner objectAtIndex:indexPath.row];
UITableViewCell *cell = [self.boatsTableView cellForRowAtIndexPath:indexPath];
[self performSegueWithIdentifier:#"boatInfoSegue" sender:cell];
NSString *nameString = ship.name;
NSString *sizeString = ship.size;
NSUserDefaults *shipName = [NSUserDefaults standardUserDefaults];
[shipName setObject:nameString forKey:#"globalName"];
[shipName synchronize];
NSUserDefaults *shipSize = [NSUserDefaults standardUserDefaults];
[shipSize setObject:sizeString forKey:#"globalSize"];
[shipSize synchronize];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
And to load it back into text (in another file)
NSString *getName = [[NSUserDefaults standardUserDefaults]
objectForKey:#"globalName"];
NSString *getSize = [[NSUserDefaults standardUserDefaults]
objectForKey:#"globalSize"];
shipNameText.text = getName;
shipSizeText.text = getSize;
Now , this works great, except for the fact that it does not return the cell I selected to get to the infoView, but the previous selected object. So if I select the first object on the list, it returns null, the next item i select gets the result I should have had for the first object and so on.
What did I do wrong and how can I fix it?
NSUserDefaults is tool for saving settings, not for transferring object.
If I were you I made it this way:
#pragma mark - Segue
- (void)prepareForSegue:(UIStoryboardSegue *)segue
sender:(id)sender {
if ([segue.identifier isEqualToString:#"boatInfoSegue"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
CargoShip *ship = boatsForOwner[indexPath.row];
BoatViewController *vc = (BoatViewController *)segue.destinationViewController;
vc.ship = ship;
}
}
Class names should be capitalized, that's why I wrote CargoShip
From what I can see in your code. your problem is that you first perform the seque and then update the values in the NSUserDefaults move this line [self performSegueWithIdentifier:#"boatInfoSegue" sender:cell]; to the end of the method.
This should make it work, but you need to refactor you code.