In my application I have a tableview with a barbuttonitem. When I press the barbuttonitem, I see an alertcontroller that allows me to enter data. Then later, save these data within a NSMutableArray and my purpose is to print one of these data within the cells of a tableview. The problem is that the data acquisition is correct, but the tableview remains empty. I do not understand why, it seems all right!
Class: FeedInfo
#import <Foundation/Foundation.h>
#interface FeedInfo : NSObject
#property (nonatomic,strong) NSString *feedURL;
#property (nonatomic,strong) NSString *feedTitle;
#property (nonatomic,strong) NSString *feedCategory;
#end
And then I have a barbuttonitem inside a tableview. When I click on this button, I open a controller alert that allows me to enter 3 fields: url, feed, category.
I have declared #property(nonatomic,strong) NSMutableArray *feedArray;
I then insert these dates into the NSMutableArray.
- (IBAction)inserFeed:(UIBarButtonItem *)sender {
UIAlertController * alertController = [UIAlertController alertControllerWithTitle: #"Feed RSS: info?"
message: #""
preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = #"Feed URL";
textField.clearButtonMode = UITextFieldViewModeWhileEditing;
textField.borderStyle = UITextBorderStyleRoundedRect;
}];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = #"Feed Title";
textField.clearButtonMode = UITextFieldViewModeWhileEditing;
textField.borderStyle = UITextBorderStyleRoundedRect;
}];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = #"Feed category";
textField.clearButtonMode = UITextFieldViewModeWhileEditing;
textField.borderStyle = UITextBorderStyleRoundedRect;
}];
[alertController addAction:[UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
NSArray * textfields = alertController.textFields;
UITextField * urlfield = textfields[0];
UITextField * titlefield = textfields[1];
UITextField * categoryfield = textfields[2];
FeedInfo *newFeed = [[FeedInfo alloc] init];
newFeed.feedURL = urlfield.text;
newFeed.feedTitle = titlefield.text;
newFeed.feedCategory = categoryfield.text;
[self.feedArray addObject:newFeed];
[self.tableView reloadData];
}]];
[self presentViewController:alertController animated:YES completion:nil];
}
Everything works here. The problem is printing one of these fields within a cell of my tableview.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"cell"];
FeedInfo *feedToShow = [self.feedArray objectAtIndex:indexPath.row];
cell.textLabel.text = feedToShow.feedTitle;
return cell;
}
Problem with your cell creation. Your cell object is nil
Use the following code to get the cell object.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
dequeueReusableCellWithIdentifier: forIndexPath : newer dequeue method guarantees a cell is returned and resized properly, assuming identifier is registered
If you didn't set the cell identifier on the xib/storyboard then
You must call registerClass or registerNib in order to be able to use the dequeueReusableCellWithIdentifier:forIndexPath: method.
Update :
Datasource array is not allocated. Allocate feedArray inside the viewDidLoad()
- (void)viewDidLoad {
[super viewDidLoad];
self.feedArray = [[NSMutableArray alloc]init];
}
Related
I call a UIAlertController from a ViewController. When I press Ok in the UIAlertController will prompt another OK dialog.
Now the problem is when I clicked on the Ok button from the dialog, I able to exit the UIAlertController but what i want is exit the UIAlertController and refresh the primary ViewController.
Can any one help me out? :(
- (IBAction)btnAddDidPressed:(id)sender {
AddCashValueVC *addCashValueVC = [storyboard instantiateViewControllerWithIdentifier:#"addCashValueVC"];
alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(#"nav_Add_Credit", nil) message:nil preferredStyle:UIAlertControllerStyleAlert];
[alertController setValue:addCashValueVC forKey:#"contentViewController"];
[self presentViewController:alertController animated:YES completion:nil];
}
Above is the Primary ViewController. Show how i call the UIAlertController.
- (IBAction)btnProceedDidPressed:(id)sender {
[self convertCashValue];
[self dismissKeyboard];
}
-convertCashValue:{
self->alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(#"msg_App", nil) message:[result objectForKey:#"msg"] preferredStyle:UIAlertControllerStyleAlert];
self->cashValueVC.update= YES;
UIAlertAction *openAction = [UIAlertAction actionWithTitle:NSLocalizedString(#"btn_Ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
CashValueVC *cashVC = [[CashValueVC alloc] initWithNibName:nil bundle:nil];
[self dismissViewControllerAnimated:YES completion:^{
[cashVC viewDidLoad];
[cashVC viewWillAppear:YES];
[cashVC.tableView reloadData];
}];
}];
[self->alertController addAction:openAction];
[self presentViewController:self->alertController animated:YES completion:nil];
}
Above is the another UIAlertController in UIAlertController.
Following code firing one OK dialog and after tapping OK, fire another one and if tapped OK - tableview updates on main thread:
#interface ViewController ()<UITableViewDelegate,UITableViewDataSource>
#property (weak, nonatomic) IBOutlet UITableView *table;
#property (strong, nonatomic) NSArray *array; //table view dataSource
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
// update button tapped
- (IBAction)action:(id)sender {
//changing table data source
_array = #[
#"afasfasf",
#"afasfasf",
#"afasfasf",
#"afasfasf",
#"afasfasf",
#"afasfasf",
#"afasfasf"
];
//alert controllers
UIAlertController *firstAlertController = [UIAlertController alertControllerWithTitle:#"First alert"
message:nil
preferredStyle:UIAlertControllerStyleAlert];
UIAlertController *secondAlertController = [UIAlertController alertControllerWithTitle:#"Second alert"
message:nil
preferredStyle:UIAlertControllerStyleAlert];
//Actions
UIAlertAction *firstControllerOKAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
//firing second dialog
[self presentViewController:secondAlertController animated:YES completion:nil];
}];
UIAlertAction *secondControllerOKAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
//calling on main thread table view update
dispatch_async(dispatch_get_main_queue(), ^{
[self.table reloadData];
});
}];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:#"cancel"
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action) {}];
//adding actions to first dialog
[firstAlertController addAction:firstControllerOKAction];
[firstAlertController addAction:cancel];
//adding actions to second dialog
[secondAlertController addAction:secondControllerOKAction];
[secondAlertController addAction:cancel];
//firing first dialog
[self presentViewController:firstAlertController animated:YES completion:nil];
}
//delegate and datasource methods
- (nonnull UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
[tableView registerClass:UITableViewCell.class forCellReuseIdentifier:#"23"];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"23"];
cell.textLabel.text = _array[indexPath.row];
return cell;
}
- (NSInteger)tableView:(nonnull UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _array.count;
}
#end
I have a UIViewController with a TableView. The TableView is populated with an image and a text for each cell. Now I need to present others Scenes when the user taps a cell. For example:
- tap on first row --> present ViewController1 - tap on second row --> present ViewController2
ViewController1 and ViewController2 are scenes in my Storyboard.
I've tried various solutions, but none of these works. Moreover it seems that the method didSelectRowAtIndexPath: is not called when I tap a cell (for example I tried to show up an alert).
Here the code of my ViewController:
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
{
NSArray *recipes;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
recipes = [NSArray arrayWithObjects:#"News", #"Calendar",#"Take a photo",#"Draw",#"Mail us",#"Follow us on Facebook", nil];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [recipes count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
cell.textLabel.text = [recipes objectAtIndex:indexPath.row];
if ([cell.textLabel.text isEqualToString:recipes[0]]) {
cell.imageView.image = [UIImage imageNamed:#"newsicon"];
}
else if ([cell.textLabel.text isEqualToString:recipes[1]]) {
cell.imageView.image = [UIImage imageNamed:#"mensaicon"];
}
else if ([cell.textLabel.text isEqualToString:recipes[2]]) {
cell.imageView.image = [UIImage imageNamed:#"fotoicon"];
}
else if ([cell.textLabel.text isEqualToString:recipes[3]]) {
cell.imageView.image = [UIImage imageNamed:#"drawicon"];
}
else if ([cell.textLabel.text isEqualToString:recipes[4]]) {
cell.imageView.image = [UIImage imageNamed:#"mailicon"];
}
else if ([cell.textLabel.text isEqualToString:recipes[5]]) {
cell.imageView.image = [UIImage imageNamed:#"fbicon"];
}
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// here we get the cell from the selected row.
UITableViewCell *selectedCell=[tableView cellForRowAtIndexPath:indexPath];
if ([selectedCell.textLabel.text isEqualToString:recipes[0]]) {
UIAlertView *messageAlert = [[UIAlertView alloc]
initWithTitle:#"Row Selected" message:#"You've selected a row" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
// Display Alert Message
[messageAlert show];
NSString *storyboardName = #"Main";
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle: nil];
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"news"];
[self presentViewController:vc animated:YES completion:nil];
}
// use the image in the next window using view controller instance of that view.
}
#end
I'm new to iOS developing, so I don't know if my code is right or if there are other solutions more elegant of these. Anyway, let's focus on the problem, can anyone help me?
If your view is only a table view I'd suggest using a UITableViewController instead of a UIViewController. If you have a UITableView in an existing UIViewController you need to set up the delegate and data source methods yourself.
You can do this in the storyboard. Click on your tableView and then select the connections inspector. Then click the circle next to dataSource and drag it to your view controller. Do the same for delegate. This is probably why your table view methods aren't being called.
If it's a static table, you can create independent segues from each cell in the storyboard.
Moreover it seems that the method didSelectRowAtIndexPath: is not called when I tap a cell (for example I tried to show up an alert).
Your based class is UIViewController, therefore, your UITableViewDelegate and UITableViewDataSource won't get set automatically. You need to do it yourself. For example, you can set them in the init function:
- (instancetype)init {
self = [super init];
if( self ) {
self.delegate = self;
self.datasource = self;
}
return self;
}
An alternative is to use UITableViewController as your base call, then you don't need to worry about setting the delegates.
according to your code alert will only be shown when you click on 1st cell only..
but according to your needs let me write a code for that will help you..
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// here we get the cell from the selected row.
NSString *selectedcelltext=[recipes objectAtIndex:indexPath.row];
UIAlertView *messageAlert = [[UIAlertView alloc]
initWithTitle:#"Row Selected" message:[NSString stringWithFormat:#"You've selected a %# row ",selectedcelltext] delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
// Display Alert Message
[messageAlert show];
if ([selectedcelltext isEqualToString:recipes[0]]) {
NSString *storyboardName = #"Main";
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle: nil];
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"news"];
[self presentViewController:vc animated:YES completion:nil];
}
else if ([selectedcelltext isEqualToString:recipes[1]]) {
NSString *storyboardName = #"Main";
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle: nil];
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"calendar"];
[self presentViewController:vc animated:YES completion:nil];
}
// use the image in the next window using view controller instance of that view.
}
Ok, I have new dilemma. I have been searching all over S.O. to find the answer, but I cannot seem to get any of the code to work. I have also looked at many tutorials and other resources but can't seem to find anything to help.
Here's the deal: I have a tableview with a custom cell with custom objects. The custom cell contains an image that can be selected to indicate whether the type of transportation listed in the cell has been used or not. The image changes from a grayed "unselected" state to a blue "selected" state (or back from "selected" to "unselected"). A tap gesture on the image fires up an alert view where the user can select "yes" or "no". The alert view then causes the image to change to a "selected" or "unselected" state, essentially working like a checkmark. This all works perfectly. (I added the alert view as a safeguard against an unintentional tap gesture on the image).
http://tinypic.com/r/s5d3s3/8
The problem occurs when the cell disappears during scrolling and then re-appears again, the image becomes deselected. Lots of people have asked about this problem. It looks like I need to:
(1) set up a mutable array to hold the selections
(2) perform some check in cellForRowAtIndexPath to tell the cell what to show
(3) and, somewhere, set the state of the image selected so it can be added to the mutable array.
I've tried implementing ideas from some the given answers but a lot of them use a button or cell accessory. Part of the problem is where to implement some of this code since I've got the gesture recognizer and alert view firing on the image. Also, would saving the selection to nsuserdefaults keep the state of the image during scrolling as well, or would I need to deal with both problems separately? (meaning, add code to keep the state during scrolling as well as code to save to nsuserdefaults). I've tried some S.O. answers to use nususerdefaults, but have the same problem...where do I put this code? How do I save the image state instead of saving a button state or cell accessory state?
I also want to note that the tableview is embedded in a navigation controller and each cell goes to a detail view which adds to the problem when adding code to didSelectCellAtIndexPath (which most of the S.O. answers include). How do I single out the image that is tapped and not the whole cell (which often causes a segue to occur along with the image selection).
Any kind of help would be most appreciated!! Thank you ahead of time. :)
Below is all my code:
**TRANSPORT.h**
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#interface Transport : NSObject
#property (nonatomic, strong) NSString *name;
#property (nonatomic, strong) UIImage *transportImage;
#property (nonatomic, strong) UIImage *usedTransportImage;
#end
**TRANSPORTDATACONTROLLER.h**
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "Transport.h"
#interface TransportDataController : NSObject
#property (nonatomic, strong) NSMutableArray *transportDataArray;
-(NSMutableArray *)populateDataSource;
#end
**TRANSPORTDATACONTROLLER.m**
#import "TransportDataController.h"
#implementation TransportDataController
-(NSMutableArray *)populateDataSource
{
_transportDataArray = [[NSMutableArray alloc] init];
Transport *transportData = [[Transport alloc] init];
transportData.name = #"Bus";
transportData.transportImage = [UIImage imageNamed:#"Bus"];
transportData.usedTransportImage = [UIImage imageNamed:#"stamp-grayed"];
[_transportDataArray addObject:transportData];
transportData = [[Transport alloc] init];
transportData.name = #"Helicopter";
transportData.transportImage = [UIImage imageNamed:#"Helicopter"];
transportData.usedTransportImage = [UIImage imageNamed:#"stamp-grayed"];
[_transportDataArray addObject:transportData];
transportData = [[Transport alloc] init];
transportData.name = #"Truck";
transportData.transportImage = [UIImage imageNamed:#"Truck"];
transportData.usedTransportImage = [UIImage imageNamed:#"stamp-grayed"];
[_transportDataArray addObject:transportData];
transportData = [[Transport alloc] init];
transportData.name = #"Boat";
transportData.transportImage = [UIImage imageNamed:#"Boat"];
transportData.usedTransportImage = [UIImage imageNamed:#"stamp-grayed"];
[_transportDataArray addObject:transportData];
transportData = [[Transport alloc] init];
transportData.name = #"Bicycle";
transportData.transportImage = [UIImage imageNamed:#"Bicycle"];
transportData.usedTransportImage = [UIImage imageNamed:#"stamp-grayed"];
[_transportDataArray addObject:transportData];
transportData = [[Transport alloc] init];
transportData.name = #"Motorcycle";
transportData.transportImage = [UIImage imageNamed:#"Motorcycle"];
transportData.usedTransportImage = [UIImage imageNamed:#"stamp-grayed"];
[_transportDataArray addObject:transportData];
transportData = [[Transport alloc] init];
transportData.name = #"Plane";
transportData.transportImage = [UIImage imageNamed:#"Plane"];
transportData.usedTransportImage = [UIImage imageNamed:#"stamp-grayed"];
[_transportDataArray addObject:transportData];
transportData = [[Transport alloc] init];
transportData.name = #"Train";
transportData.transportImage = [UIImage imageNamed:#"Train"];
transportData.usedTransportImage = [UIImage imageNamed:#"stamp-grayed"];
[_transportDataArray addObject:transportData];
transportData = [[Transport alloc] init];
transportData.name = #"Car";
transportData.transportImage = [UIImage imageNamed:#"Car"];
transportData.usedTransportImage = [UIImage imageNamed:#"stamp-grayed"];
[_transportDataArray addObject:transportData];
transportData = [[Transport alloc] init];
transportData.name = #"Scooter";
transportData.transportImage = [UIImage imageNamed:#"Scooter"];
transportData.usedTransportImage = [UIImage imageNamed:#"stamp-grayed"];
[_transportDataArray addObject:transportData];
transportData = [[Transport alloc] init];
transportData.name = #"Caravan";
transportData.transportImage = [UIImage imageNamed:#"Caravan"];
transportData.usedTransportImage = [UIImage imageNamed:#"stamp-grayed"];
[_transportDataArray addObject:transportData];
return _transportDataArray;
}
#end
**TRANSPORTCELL.h**
#import <UIKit/UIKit.h>
#interface TransportCell : UITableViewCell
#property (nonatomic, weak) IBOutlet UILabel *nameLabel;
#property (nonatomic, weak) IBOutlet UIImageView *transportImageView;
#property (nonatomic, weak) IBOutlet UIImageView *grayedImageView;
#end
**MAINTABLEVIEWCONTROLLER.h**
#import <UIKit/UIKit.h>
#import "Transport.h"
#import "TransportDataController.h"
#import "DetailTableViewController.h"
#interface MainTableViewController : UITableViewController
#property (nonatomic, strong) TransportDataController *transportController;
#property (nonatomic, strong) NSMutableArray *dataSource;
#end
**MAINTABLEVIEWCONTROLLER.m**
#import "MainTableViewController.h"
#import "TransportCell.h"
#interface MainTableViewController ()
#end
#implementation MainTableViewController
-(void)viewDidLoad
{
[super viewDidLoad];
_transportController = [[TransportDataController alloc] init];
self.dataSource = _transportController.populateDataSource;
self.title = #"Transportation Types";
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return _dataSource.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"mainCell";
TransportCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[TransportCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
Transport *transportData = [self.dataSource objectAtIndex:indexPath.row];
cell.nameLabel.text = transportData.name;
cell.transportImageView.image = transportData.transportImage;
cell.grayedImageView.image = transportData.usedTransportImage;
UITapGestureRecognizer *grayedImageTouched = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(transportImageTapped:)];
grayedImageTouched.numberOfTapsRequired = 1;
[cell.grayedImageView addGestureRecognizer:grayedImageTouched];
cell.grayedImageView.userInteractionEnabled = YES;
return cell;
}
-(void)transportImageTapped:(UIGestureRecognizer *)gesture
{
UIImageView *selectedImageView = (UIImageView *)[gesture view];
UIImage *grayedImage = [UIImage imageNamed:#"stamp-grayed"];
UIImage *darkImage = [UIImage imageNamed:#"stamp-color"];
UIAlertController *transportAlert = [UIAlertController alertControllerWithTitle:#"Yes, it's true..." message:#"I have used this type of transport before." preferredStyle:UIAlertControllerStyleAlert];
[transportAlert addAction:[UIAlertAction actionWithTitle:#"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action){
NSLog(#"cancel");
}]];
[transportAlert addAction:[UIAlertAction actionWithTitle:#"Yes" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){
if (selectedImageView.image == grayedImage)
{
selectedImageView.image = grayedImage;
[selectedImageView setImage:grayedImage];
}
else
{
selectedImageView.image = darkImage;
[selectedImageView setImage:darkImage];
}
if (selectedImageView.image == darkImage)
{
selectedImageView.image = darkImage;
[selectedImageView setImage:darkImage];
}
else
{
selectedImageView.image = grayedImage;
[selectedImageView setImage:grayedImage];
}
NSLog(#"has taken this transport before");
}]];
[transportAlert addAction:[UIAlertAction actionWithTitle:#"No" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){
if (selectedImageView.image == darkImage)
{
selectedImageView.image = grayedImage;
[selectedImageView setImage:grayedImage];
}
else
{
selectedImageView.image = grayedImage;
[selectedImageView setImage:grayedImage];
}
if (selectedImageView.image == grayedImage)
{
selectedImageView.image = grayedImage;
[selectedImageView setImage:grayedImage];
}
NSLog(#"has not taken this transport before");
}]];
[self presentViewController:transportAlert animated:YES completion:nil];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"showDetail"])
{
DetailTableViewController *detailController = [segue destinationViewController];
detailController.transport = [self.dataSource objectAtIndex:[self.tableView indexPathForSelectedRow] .row];
}
}
#end
Step away from the details and think about what you're doing.
A table view presents information about an ordered collection of things.
The things are the data model. The table view is the view object, and the view controller is the controller object in the MVC design pattern.
When the user interacts with the view in a way that needs to make a persistent change, the controller should record the changes in the model and tell the view to update it's appearance.
Then, if a cell scrolls off-screen and then back on-screen, the data source should set up the recycled cell with the new state for that entry from the data model.
For a non-sectioned table view it's quite common to store the data model as an NSArray of some sort of data object. You can create a custom data container object or just use a dictionary.
Let's say we have a custom data object.
Simply add a property to your data object that tells whether or not a transportation item has been used or not.
When the user taps on the view object, the view controller should respond to the messages from the gesture recognizer by changing the "has been used" property of the data model object for the specific index path and then tell the view to redraw itself.
In your cellForRowAtIndexPath method, set the view object based on the state of the "has been used" flag. Since you saved the change of state into the data model, next time the user displays a cell for a given index in your table data, it shows the changed state.
As you suspect, you need to change your datasource data upon button click such that cellForRowAtIndexPath can load the desired image appropriately. In this case, you can simply change your TransportCell's usedTransportImage (i.e. the UIImage tied to the `UIGestureRecognizer) to dark or gray depending on the alert view's input.
As your code stands now though, your logic within your alert blocks is flawed to the point that I'm not sure what you're trying to accomplish -- your "Yes" block always leaves your image as is, your "No" block always sets your image to gray, and you're repeatedly setting the image multiple times within each block. I'm guessing that maybe you want a "Yes" response to set the usedTransportImage to dark and a "No" response to set the usedTransportImage to gray (?)... So using that assumption as an example, this is what I recommend:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
// Set the UITapGestureRecognizer's image view's tag to
// indexPath.row
cell.grayedImageView.tag = indexPath.row;
...
return cell;
}
- (void)transportImageTapped:(UIGestureRecognizer *)gesture
{
// Fetch the datasource object associated with the current row
Transport *transportData = [self.dataSource objectAtIndex:gesture.view.tag];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:gesture.view.tag inSection:0];
UIAlertController *transportAlert = [UIAlertController alertControllerWithTitle:#"Yes, it's true..." message:#"I have used this type of transport before." preferredStyle:UIAlertControllerStyleAlert];
[transportAlert addAction:[UIAlertAction actionWithTitle:#"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action){
NSLog(#"cancel");
}]];
[transportAlert addAction:[UIAlertAction actionWithTitle:#"Yes" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){
NSLog(#"has taken this transport before");
// Create the image within the block so it's only
// created if needed
UIImage *darkImage = [UIImage imageNamed:#"stamp-color"];
// Set the Transport object's "used" image to the
// darkened image
transportData.usedTransportImage = darkImage;
// Replace the old Transport object associated with the
// current row with the updated Transport object
[self.dataSource replaceObjectAtIndex:gesture.view.tag withObject:transportData];
// Reload the table data at that row so it reflects those changes
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation: UITableViewRowAnimationNone];
}]];
[transportAlert addAction:[UIAlertAction actionWithTitle:#"No" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){
NSLog(#"has not taken this transport before");
// Create the image within the block so it's only
// created if needed
UIImage *grayedImage = [UIImage imageNamed:#"stamp-grayed"];
// Set the Transport object's "used" image to the
// grayed image
transportData.usedTransportImage = grayedImage;
// Replace the old Transport object associated with the
// current row with the updated Transport object
[self.dataSource replaceObjectAtIndex:gesture.view.tag withObject:transportData];
// Reload the table data at that row so it reflects those changes
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation: UITableViewRowAnimationNone];
}]];
[self presentViewController:transportAlert animated:YES completion:nil];
}
And if you want help for the second part of your question about preventing the view's transition upon button press, please post your code from didSelectRowAtIndexPath:.
This is what you need to do as given below. Changed the implementation of transportImageTapped method. Code is not tested so there is a possibility of minor error. Let me know if this helps.
Create a NSMutableArray say NSMutableArray *selected;
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"mainCell";
TransportCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[TransportCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
Transport *transportData = [self.dataSource objectAtIndex:indexPath.row];
cell.nameLabel.text = transportData.name;
cell.transportImageView.image = transportData.transportImage;
if([selected containsObject:indexPath]) {
cell.grayedImageView.image = transportData.usedTransportImage;
} else {
cell.grayedImageView.image = transportData.deSelectedTransportImage;
}
UITapGestureRecognizer *grayedImageTouched = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(transportImageTapped:)];
grayedImageTouched.numberOfTapsRequired = 1;
[cell.grayedImageView addGestureRecognizer:grayedImageTouched];
cell.grayedImageView.userInteractionEnabled = YES;
return cell;
}
-(void)transportImageTapped:(UIGestureRecognizer *)gesture
{
CGPoint point= [gesture locationInView:self.tableView];
NSIndexPath *theIndexPath = [theTableView indexPathForRowAtPoint:point];
UIAlertController *transportAlert = [UIAlertController alertControllerWithTitle:#"Yes, it's true..." message:#"I have used this type of transport before." preferredStyle:UIAlertControllerStyleAlert];
[transportAlert addAction:[UIAlertAction actionWithTitle:#"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action){
NSLog(#"cancel");
}]];
[transportAlert addAction:[UIAlertAction actionWithTitle:#"Yes" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){
if(selected containsObject:theIndexPath ) {
//Already selected - Remove the selection
[selected removeObject:theIndexPath];
} else {
// Set the object as selected
[selected addObjetc:theIndexPath];
}
NSLog(#"has taken this transport before");
NSArray* indexArray = [NSArray arrayWithObjects:theIndexPath, nil];
// Launch reload for the two index path
[self.tableView reloadRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationFade];
}]];
[transportAlert addAction:[UIAlertAction actionWithTitle:#"No" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){
if(selected containsObject:theIndexPath ) {
//Already selected - Remove the selection
[selected removeObject:theIndexPath];
} else {
// Set the object as selected
[selected addObjetc:theIndexPath];
}
NSLog(#"has not taken this transport before");
NSArray* indexArray = [NSArray arrayWithObjects:theIndexPath, nil];
// Launch reload for the two index path
[self.tableView reloadRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationFade];
}]];
[self presentViewController:transportAlert animated:YES completion:nil];
}
I'm having trouble implementing a toolbar (below my navigationbar) and a tableview. In my storyboard I created a normal ViewController, then made it part of the NavigationController, then added a UIToolBar and a UITableView. After this was done, I created the ViewController files. It all works. However, when I press a button in the toolbar it is being registered as a click on a cell and thus crashing the app. My knowledge isn't sufficient enough for me to be able to fix this, that's why I'm asking here.
Furthermore, if you scroll the toolbar doesn't stay at the top. It scrolls along with the table.
How can I fix these two problems?
#import "TableViewController.h"
#import "AFHTTPRequestOperationManager.h"
#import "UIImageView+AFNetworking.h"
#import "Ninja.h"
#import "DetailViewController.h"
#import "AMSlideMenuMainViewController.h"
#import "UIViewController+AMSlideMenu.h"
#import "UIImageView+WebCache.h"
#import "NSString+FontAwesome.h"
#interface TableViewController ()
#property (nonatomic, strong)NSArray *ninjas;
#property (weak, nonatomic) IBOutlet UIBarButtonItem *movies;
#property (weak, nonatomic) IBOutlet UIBarButtonItem *shows;
#property (weak, nonatomic) IBOutlet UIBarButtonItem *profile;
#property (weak, nonatomic) IBOutlet UIToolbar *mainToolBar;
#end
#implementation TableViewController
- (void)viewWillAppear:(BOOL)animated
{
[self.navigationController setToolbarHidden:YES animated:YES];
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"Your watched movies";
[self addRightMenuButton];
[self loadNinjas];
[self.movies setTitleTextAttributes:#{
NSFontAttributeName: [UIFont fontWithName:#"FontAwesome" size:24.0],
NSForegroundColorAttributeName: self.view.tintColor
} forState:UIControlStateNormal];
[self.movies setTitle:[NSString fontAwesomeIconStringForIconIdentifier:#"fa-dot-circle-o"]];
[self.shows setTitleTextAttributes:#{
NSFontAttributeName: [UIFont fontWithName:#"FontAwesome" size:24.0],
NSForegroundColorAttributeName: self.view.tintColor
} forState:UIControlStateNormal];
[self.shows setTitle:[NSString fontAwesomeIconStringForIconIdentifier:#"fa-pencil-square-o"]];
[self.profile setTitleTextAttributes:#{
NSFontAttributeName: [UIFont fontWithName:#"FontAwesome" size:24.0],
NSForegroundColorAttributeName: self.view.tintColor
} forState:UIControlStateNormal];
[self.profile setTitle:[NSString fontAwesomeIconStringForIconIdentifier:#"fa-plus"]];
self.mainToolBar.barTintColor = [UIColor whiteColor];
self.mainToolBar.layer.shadowColor = [[UIColor blackColor] CGColor];
self.mainToolBar.layer.shadowOffset = CGSizeMake(1.0f, 1.0f);
self.mainToolBar.layer.shadowRadius = 3.0f;
self.mainToolBar.layer.shadowOpacity = 1.0f;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return self.ninjas.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
cell.textLabel.text = [self.ninjas[indexPath.row] name];
NSString *imageUrl = [NSString stringWithFormat: #"%#", [self.ninjas[indexPath.row] thumbnail]];
[cell.imageView setImageWithURL:[NSURL URLWithString:imageUrl]
placeholderImage:[UIImage imageNamed:#"50-50.jpg"]];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 50.0f;
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get the new view controller using [segue destinationViewController].
DetailViewController *detailvc = [segue destinationViewController];
// Pass the selected object to the new view controller.
NSIndexPath *index = self.tableView.indexPathForSelectedRow;
Ninja *ninja = self.ninjas[index.row];
detailvc.ninja = ninja;
}
- (void)loadNinjas {
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://example.com/movies"]];
NSMutableURLRequest *mutableRequest = [request mutableCopy];
request = [mutableRequest copy];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *jsonArray = (NSArray *)[responseObject objectForKey:#"data"];
NSMutableArray *tempNinjas = [[NSMutableArray alloc] init];
for (NSDictionary *dic in jsonArray) {
Ninja *ninja = [[Ninja alloc] initWithDictionary:dic];
[tempNinjas addObject:ninja];
}
self.ninjas = [[NSArray alloc] initWithArray:tempNinjas];
tempNinjas = nil;
[self.tableView reloadData];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error Retrieving Shows"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alertView show];
}];
[operation start];
}
#end
And this is the storyboard screenshot: http://imgur.com/EOe9iQ3
It seems that you have UITableViewController as your view controller, which always has UITableView as its main view and that can't be changed.
What you could do is just drag new UIViewController onto your storyboard, which will come with usual UIView as its main view. Then cut/paste your existing table and toolbar views as your new UIView subviews.
Since toolbar now isn't a subview of your table view, it'll never scroll with it nor will it have any crashes upon taps on its items.
Finally, connect any relevant outlets/actions from the views of your new UIViewController to your new view controller, and don't forget to set the latter as a delegate/data source for your table view (which is done automatically for UITableViewController)
I am trying to create an account page where user will be able to select his preferable language and voice. For this part of my app I was thinking to create 2 different UIButtons and once user has press one of them, a UITableView will appear on appropriate location tableFrame (something like drop down menu). Things were working great, initially, when I had just one array. Now, I have a general array that used from Table View delegate methods to load data and 2 other arrays that contain languages and voices.
My problem is that the very first time when I am tapping on any of the buttons, the table displays no content but numberOfRowsInSection returns correct value. Next, if tap on the same or the other button again .. everything is fine.
I believe the problem appears where I am, initially, initialise the general array with no object in viewDidLoad.
I have tried reloadData with now success.
Any ideas? Below my code:
Account.h
#import <Foundation/Foundation.h>
#interface AccountViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>{
NSMutableArray *content;
NSMutableArray *languages;
NSMutableArray *voices;
CGRect tableFrame;
}
#property (strong, nonatomic) IBOutlet UIButton *languageBtn;
#property (strong, nonatomic) IBOutlet UIButton *narratorBtn;
#property (strong, nonatomic) IBOutlet UITableView *contentTbl;
- (IBAction)selectLanguage:(id)sender;
- (IBAction)selectVoice:(id)sender;
#end
Account.m
- (void) viewDidLoad{
[super viewDidLoad];
content = [[NSMutableArray alloc] init];
languages = [[NSMutableArray alloc] initWithObjects:#"English", #"Ελληνικά", #"French", #"Spanish", #"Deutch", nil];
voices = [[NSMutableArray alloc] initWithObjects:#"English", #"US English", #"Ελληνικά", #"Spanish", nil];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [content count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = [content objectAtIndex:indexPath.row];
return cell;
}
- (IBAction)selectLanguage:(id)sender{
if ([(UIButton *)sender tag] == 0) {
content = [languages copy];
tableFrame = CGRectMake(50, 173, 220, 203);
[self startAnimationAndDisplayTable:YES];
[(UIButton *)sender setTag:1];
} else{
[self startAnimationAndDisplayTable:NO];
[(UIButton *)sender setTag:0];
}
}
- (IBAction)selectVoice:(id)sender{
if ([(UIButton *)sender tag] == 0) {
content = [voices copy];
tableFrame = CGRectMake(50, 243, 220, 203);
[self startAnimationAndDisplayTable:YES];
[(UIButton *)sender setTag:1];
} else{
[self startAnimationAndDisplayTable:NO];
[(UIButton *)sender setTag:0];
}
}
- (void)startAnimationAndDisplayTable:(BOOL)show{
if (show) {
[self.contentTbl setHidden:NO];
[self.contentTbl setFrame:tableFrame];
[UIView beginAnimations:#"Fade In" context:nil];
[UIView setAnimationDuration:0.7];
[self.contentTbl setAlpha:1.0];
[UIView commitAnimations];
[self.contentTbl reloadData];
} else{
[UIView beginAnimations:#"Fade Out" context:nil];
[UIView setAnimationDuration:0.5];
[self.contentTbl setAlpha:0.0];
[UIView commitAnimations];
}
}
#end
"content" is an empty mutable array until you touch whatever (button?) action that calls the "selectLanguage" and "selectVoice" methods (and then you reassign the "content" variable).
I don't see any other manipulations of the "content" mutable array, so it's no wonder to me that nothing appears until you actually touch something.
You also do not show where you are calling "reloadData". "content" needs to have array entries in there for "reloadData" to work.