I have a UITableView called UserProductViewController and this table view used for two purpose "create and edit" some data. When user click "edit button" open this UserProductViewController edit mode and populate some data within cell from another method. I don't want to store all server-side values in property instead I was use Lazy instantiation cell. This approach sometimes makes trouble. To be clear here is my code this approach correct or not ? Could you please guide me correct way to do this?
Implementation Interface
#interface UserProductViewController()
#property(strong, nonatomic) MyCustomTableViewCell *myCustomCell;
#end
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...
if(indexPath.row == 2) return self.myCustomCell;
...
}
- (MyCustomTableViewCell *)myCustomCell {
if(!_myCustomCell) {
_myCustomCell = [self.tabView dequeueReusableCellWithIdentifier:#"CustomIdentifier" forIndexPath:[self customIndexPath]];
}
return _myCustomCell;
}
- (NSIndexPath *)customIndexPath {
// 2 correspond to cellForRowAtIndexPath indexPath value.
return [NSIndexPath indexPathForRow:2 inSection:0];
}
Server side method I'm using that cell as a property like this:
- (void) getUserValues {
....
// getting server-side result
// completion block
self.myCustomCell.titleLabel = result.title;
}
Other question is what would happen if I don't want to use tableview dequeue system ?
Related
I want to put a tableview into a tableview cell. I like the formatted look it gives. I'm hoping to put in a few fields, for like name, email, etc. What am I missing to be able to make this work? Currently I can not set "ProfileCell" as the tableview class.
In my .h file of the profile cell I added:
#interface ProfileCell : UITableViewCell <UITableViewDataSource,UITableViewDelegate>
and in my .m file I added some basic methods for the tableview:
#import "ProfileCell.h"
#implementation ProfileCell
- (void)awakeFromNib {
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Number of rows is the number of time zones in the region for the specified section.
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Doing this");
static NSString *MyIdentifier = #"MyReuseIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
return cell;
}
#end
EDIT:
I have seperated the code for the cellview tableview into it's own files, and linked tableview to this class, however it does not fire when viewed:
#interface ProfileBasicDetails : UITableView <UITableViewDataSource,UITableViewDelegate>
Per comments below
This is what my page should look like when said and done. It's so users can enter in their details, while looking nice and formatted, but not ghetto looking.
I'm assuming you've embedded the UITableView into the cell?
You should wire the tableview to an outlet in the cell. Then set the delegate on the tableview in your awakeFromNib method.
*As vikingosegundo mentioned in the comments, this is potentially a pretty fragile approach.
I have a layout in which I will have 2 UITableViews with custom cells. The second UITableView must be inside the first.
My question is: how to delegate second UITableView?
Can I delegate both to my ViewController? In that case it will use the same methods and I have to find out which UITableView is managed right now.
Or I have to delegate it inside custom UITableViewCell of the first UITableView?
Any recommendations are appreciated.
EDIT: I don't know how to implement solutions here, because I have Storyboard. Inside my current UIViewController I set delegate and dataSource of the first UITableView to my View Controller.
My problem is that I don't know how to set the same properties of the second Table View (which will be inside UITableViewCell). I can not set them to UITableViewCell (IB does not allow that).
Where and how to set then in the IB?
A far better solution would be to abstract the DataSource and Delegate implementations away from your view controller so that they can be personalised per tableview as required (please note that the code is taken from the objc.io article Lighter View Controllers.
E.g.
#implementation ArrayDataSource
- (id)itemAtIndexPath:(NSIndexPath*)indexPath {
return items[(NSUInteger)indexPath.row];
}
- (NSInteger)tableView:(UITableView*)tableView
numberOfRowsInSection:(NSInteger)section {
return items.count;
}
- (UITableViewCell*)tableView:(UITableView*)tableView
cellForRowAtIndexPath:(NSIndexPath*)indexPath {
id cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier
forIndexPath:indexPath];
id item = [self itemAtIndexPath:indexPath];
configureCellBlock(cell,item);
return cell;
}
#end
Then you could utilise it as follows:
void (^configureCell)(PhotoCell*, Photo*) = ^(PhotoCell* cell, Photo* photo) {
cell.label.text = photo.name;
};
photosArrayDataSource = [[ArrayDataSource alloc] initWithItems:photos
cellIdentifier:PhotoCellIdentifier
configureCellBlock:configureCell];
self.tableView.dataSource = photosArrayDataSource;
The same process could be followed with the UITableViewDelegate implementations to provide you with a very clean, separated and de-coupled code base. Your requirement for two tableviews will then be intrinsically easier to implement.
My answer is
For identifying two table view data source and delegate method is,better to set tag for the table views.
Set this below coding in your tableview delegates method.
if(tableView.tag==0)
{
}
else
{
}
Also you can vary this by assigning different name to these table view.
if(tableView==FirstTableView)
{
}
else
{
}
You just check table condition for every delegate method
Use this code to register custom cell.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(tableView == self.yourFirstTable)
{
CustomCell *cell=[tableView dequeueReusableCellWithIdentifier:#"cellModifier"];
// your code
}
else
{
// second table cell code
}
return cell;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(tableView == self.yourFirstTable)
{
// first tableView number of row return
}
else
{
// second table number of row return
}
}
And create prototype cell in TableView
And Set CellReusableId like this way
I am looking to use the Parse PFQueryTableViewController to display a list of items stored in cloud.
However, when there are no items, I am looking to show a default Message in the tableview via a customized tableview cell that says "No Item Found".
I am looking for inputs on how customize the PFQueryTableViewController to achieve this.
Thanks in advance
It's doable with some additional method overriding and a little trickery. I would add that (imo) we're straying into territory where the net benefit of the parse.com specialization of the iOS class is reduced.
// lie about the actual count
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (self.objects.count) return [super tableView:tableView numberOfRowsInSection:section];
return 1;
}
// answer a dummy object when there are none
- (PFObject *)objectAtIndexPath:(NSIndexPath *)indexPath {
if (self.objects.count) return [super objectAtIndexPath:indexPath];
return [PFObject objectWithClassName:[self parseClassName]];
}
// change how we behave for the dummy object
- (PFUI_NULLABLE PFTableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFUI_NULLABLE PFObject *)object {
// check for your dummy object (it will be the only one with no id
// return a special cell in that case
if (!object.objectId) {
// create and return the special custom cell here
// if you're unsure about this, [see this SO answer][1]
} else {
PFTableViewCell *cell;
// the regular cellForRowAtIndexPath behavior goes here
return cell;
}
}
In terms of cost-benefit of the specialized subclass, I feel the same way even about UITableViewController. The most control (and not that much extra work) can be had with a regular UIViewController containing a table view.
I have a UITableView tall enough that it necessitates scrolling. The top-most cell in the table contains a UITextField for the user to enter some text.
The standard way to build this might be to create and add the text field and add it to a cell created or recycled in cellFOrRowAtIndexPath: However, this constant re-creation means that the text entered in the field is erased when the cell is scrolled out and back into view.
The solutions I've found so far suggest using UITextField delegation to track the text as it changes and store it in an iVar or property. I would like to know why this is recommended instead of the simpler approach I am using:
I am creating the UITextField in the init method of the UITableViewController and immediately storing it in a property. In cellFOrROwAtIndexPath I am simply adding the pre-existing field instead of initializing a new one. The cell itself can be recycled without issue, but because I am always using the one and only UITextField, the content is maintained.
Is this a reasonable approach? What might go wrong? Any improvements (perhaps I could still create the field in cellForRowAtIndexPath but first check if the property is nil?)
When you are creating cells in cellForRowAtIndexPath you have to use one reusable identifier for that first cell (ie. cellId1) and another for the rest (ie. cellId2).
If you do this, when you get the cell for the first element by calling [tableView dequeueReusableCellWithIdentifier:#"cellId1"] you will always get the same Object and will not be reused by other cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyCell *cell = nil;
// Only for first row
if (indexPath.row == 0) {
static NSString *cellId1 = #"cellId1";
cell = [tableView dequeueReusableCellWithIdentifier:cellId1];
if (cell == nil) {
cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId1];
}
}
else {
static NSString *cellId2 = #"cellId2";
cell = [tableView cellId2];
if (cell == nil) {
cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault cellId2];
}
}
// do whatever
return cell;
}
If there is only one UITextField, then I agree that your approach would be better/same as compared to using UITextField delegation (I think).
However, let us assume that you want to "expand" your view so that there are about 7-8 or more TextFields now. Then if you go about using your approach, then the problem will be that you will be storing 7-8 or more TextFields in memory and maintaining them.
In such a situation, a better approach would be that you create only that number of textfields as visible on screen. Then you create a dictionary which would maintain the content present in the textfield (which you can get by UITextFieldDelegate methods). This way, the same textfield can be used when the cell is reused. Only the values will change and will be dictated by the values in the dictionary.
On a sidenote, do minimal creation in cellForRowAtIndexPath as that is called during every table scroll and so creating a textField in cellForRowAtIndexPath can be expensive.
#import "ViewController.h"
#import "TxtFieldCell.h"
#define NUMBER_OF_ROWS 26
#interface ViewController ()<UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate>
#property (weak, nonatomic) IBOutlet UITableView *tablView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tablView.datasource = self; //set textfield delegate in storyboard
textFieldValuesArray = [[NSMutableArray alloc] init];
for(int i=0; i<NUMBER_OF_ROWS; i++){
[textFieldValuesArray addObject:#""];
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#pragma mark - TableView Datasource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TxtFieldCell *cell = [tableView dequeueReusableCellWithIdentifier:#"TxtFieldCellId" forIndexPath:indexPath];
cell.txtField.tag = indexPath.row;
if (textFieldValuesArray.count > 0) {
NSString *strText = [textFieldValuesArray objectAtIndex:indexPath.row];
cell.txtField.text = strText;
}
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return NUMBER_OF_ROWS;
}
#pragma mark - TextField Delegate
- (void)textFieldDidEndEditing:(UITextField *)textField {
[textFieldValuesArray replaceObjectAtIndex:textField.tag withObject:textField.text];
}
In my iPad app storyboard design, there are two table views added.
One table view is for displaying the folder names and the other table view is for displaying the file names. When ever a folder table view cell is selected the files inside the selected folder needs to to be displayed in the other table view(the files table view).
My problem is
I am confused about
How to add delegate and data source for each table view in ViewController ? or is it possible to add data source and delegates for each table view in a custom class other than ViewController ?
and
How to handle the selection of cell ?
Please help !
First off: Why don't you just display the files on a pushed viewController which takes up the whole screen? Seems more intuitive for me.
If you want to do it with two tableViews, and assuming they use dynamic cells than:
1, In the .h file of your view controller
specify two tableView properties like so:
#property (weak, nonatomic) IBOutlet UITableView *foldersTableView;
#property (weak, nonatomic) IBOutlet UITableView *filesTableView;
implement the two delegate protocols of the UITableVIew
#interface ViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
2, Add the two UITableViews onto your ViewController, and...
...link your Outlets to the two tableviews
...on the attributes inspector set the Tag of the foldersTableView to 1 and the filesTableView to 2
...select each of the UITableViews, go to the Connections Inspector and link their two delegate methods (delegate and datasource) to your ViewController (for both of them)
3, In the .m file of your ViewController implement the three datasource methods of UITableView like so:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
if (tableView.tag == 1) {
return theNumberOfSectionsYouWantForTheFolderTableView;
} else {
return theNumberOfSectionsYouWantForTheFilesTableView;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
if (tableView.tag == 1) {
return [foldersArray count];
} else {
return [filesArray count];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView.tag == 1) {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
return cell;
} else {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
return cell;
}
}
4, Implement the selection:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView.tag == 1) {
//fill filesArray with the objects you want to display in the filesTableView...
[filesTableView reloaddata];
} else {
//do something with the selected file
}
}
Hope I got everything correctly. Don't forget to #synthesize the properties in the .m file if you are using pre XCode 4.4.
If you are using multiple tables, don't forget to hide as appropriate:
- (void)viewWillAppear:(BOOL)animated
{
[self.table2 setHidden:YES];
[self.table1 setHidden:NO];
// Probably reverse these in didSelectRow
}