I am new to iOS development. I have created two classes of UITableViewController. One is list of Tasks and second one is the list of Notes.
Now the problem is both of these UITableViewController are needed on many screens (on some screens both simultaneously). I don't want to re-write all code for UITableView delegates and datasource on every screen. I want to re-use both UITableViewController with some mechanism of inheritance or module system.
So that on every UIViewController where i need to display list, i just have to add a UITableView in nib file, sets its custom class and it should start displaying data.
Is it possible with some kind of inheritance, component or module system?
Any help is appreciated.
Create subclass of UITableViewController add all of the required delegate methods and in .h file add:
#property(nonatomic, strong) NSArray *myData;
All of the method set up base on that array, for example:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.myData;
}
When you want to use this just create object and pass your data:
MYTable *tableViewController = [[MYTable alloc] init];
tableViewController.myData = DATA YOU WANT TO DISPLAY;
It will work but you have to pass exactly the same data structure to your NSArray to handle it right in cellForRowAtIndexPath: method.
Hey mate you need to maintain two array and create a toggle button ex. a/b
if a is pressed then fetch the count of one array, if b is pressed count the array of other dictionary. And do this by maintaining a BOOL variable.
ex.
BOOL checkvalue;
-(UIButton *)a:(id)button
{
checkvalue = TRUE;
[tableview reloadData];
}
-(UIButton *)b:(id)button
{
checkvalue = FALSE;
[tableview reloadData];
}
now put the condition in the tableview counting number of rows, and cellforrowatindexpath method.
like,
// for numberOfRowsInTableview
if(checkvalue == TRUE)
{
return [arr1 count];
}
else
{
return [arr2 count];
}
and similarly for cellForRowAtIndexPath
Thanks and have a nice day.
Related
I won't go into the WHY on this one, I'll just explain what I need.
I have a property in my implementatioon file like so...
#property (nonatomic, strong) MyCustomCell *customCell;
I need to initialize this on viewDidLoad. I return it as a row in cellForRowAtIndexPath always for the same indexPath, by returning "self.customCell".
However it doesn't work, I don't appear to be allocating and initializing the custom cell correctly in viewDidLoad. How can I do this? If you know the answer, save yourself time and don't read any further.
Ok, I'll put the why here in case people are curious.
I had a table view which was 2 sections. The first section would always show 1 row (Call it A). The second section always shows 3 rows (Call them X, Y, Z).
If you tap on row A (which is S0R0[Section 0 Row]), a new cell would appear in S0R1. A cell with a UIPickerView. The user could pick data from the UIPickerView and this would update the chosen value in cell A.
Cell X, Y & Z all worked the same way. Each could be tapped, and they would open up another cell underneath with their own respective UIPickerView.
The way I WAS doing this, was all these cell's were created in the Storyboard in the TableView, and then dragged out of the View. IBOutlets were created for all. Then in cellForRAIP, I would just returned self.myCustomCellA, or self.myCustomCellY.
Now, this worked great, but I need something more custom than this. I need to use a custom cell, and not just a UITableViewCell. So instead of using IBOutlets for all the cells (8 cells, 4 (A,X,Y,Z) and their 4 hidden/toggle-able UIPickerView Cell's), I created #properties for them in my implementation file. But it's not working like it did before with the standard UITableViewCell.
I'm not sure I'm initializing them correctly? How can I properly initialize them in/off viewDidLoad so I can use them?
.m
#interface MyViewController ()
#property (strong, nonatomic) MyCustomCell *myCustomCellA;
...
viewDidLoad
...
self.myCustomCellA = [[MyCustomCell alloc] init];
...
cellForRowAtIndexPath
...
return self.myCustomCellA;
...
If only I understood your question correctly, you have 3 options:
I would try really hard to implement table view data source with regular dynamic cells lifecycle in code and not statically – this approach usually pays off when you inevitably want to modify your business logic.
If you are certain static table view is enough, you can mix this method with overriding data source / delegate methods in your subclass of table view controller to add minor customisation (e.g. hiding certain cell when needed)
Alternatively, you can create cells using designated initialiser initWithStyle:reuseIdentifier: to instantiate them outside of table view life cycle and implement completely custom logic. There is nothing particular that you should do in viewDidLoad, that you wouldn't do elsewhere.
If you have a particular problem with your code, please post a snippet so community can help you
I suggest you to declare all your cells in storyboard (with date picker at right position) as static table and then override tableView:heightForRowAtIndexPath:
Define BOOL for determine picker visibility and its position in table
#define DATE_PICKER_INDEXPATH [NSIndexPath indexPathForRow:1 inSection:0]
#interface YourViewController ()
#property (assign, nonatomic) BOOL isPickerVisible;
#end
Then setup initial value
- (void)viewDidLoad {
[super viewDidLoad];
self.isPickerVisible = YES;
}
Override tableView delegate method
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([indexPath isEqual:DATE_PICKER_INDEXPATH] && !self.isPickerVisible) {
return 0;
} else {
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}
}
And finally create method for toggling picker
- (void)togglePicker:(id)sender {
self.isPickerVisible = !self.isPickerVisible;
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
which you can call in tableView:didSelectRowAtIndexPath:
According to your problem, you can create pairs (NSDictionary) of index path and bool if its visible and show/hide them according to that.
Here's what I was looking for:
MyCustomCell *cell = (MyCustomCell *)[[[UINib nibWithNibName:#"MyNibName" bundle:nil] instantiateWithOwner:self options:nil] firstObject];
First of all I am very new in Objective C and iOS development.
Currently I am trying to use TableView
I have property
#property (strong, nonatomic) IBOutlet UITableView *tableView;//for table view
Now I seen something like this,
-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
return [data count];
}
What is exact meaning of the code. What I am doing with tableView here, and how name of property and name of message is same, i.e. tableView.
In first line you are declaring property of an tableview which you create by storyboard.
The goal of the #property directive is to make it easy to create and configure properties by automatically generating these accessor methods. It allows you to specify the behavior of a public property on a semantic level, and it takes care of the implementation details for you.
-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
return [data count];
}
This is a tableview delegate method which define the no of row in a particular section in table view
data is your array name contain element which you want to show in tableview cell
[data count] return the number of row in your table view
for more detail check
property
table view
What I am doing with tableView here
tableView:numberOfRowsInSection: is a method from the UITableViewDatasource Protocole to do simple, the object of type UITableView will do something like it :
[self.delegate tableView:self numberOfRowsInSection:section]
and so, if you do tableView.datasource = self in your viewController, it's this object which will receive the message and so, need to answer to the tableview.
and how name of property and name of message is same, i.e. tableView.
#property is just an indication for the compiler to create accessors and iVar in your class, that why it seems to do many things at the same time.
I have one parent class with one tableview.
That class is the delegate and datasource of that tableview as well.
Now I subclassed (derived) that class and made a child class.
I have one tableview in child class too.
Then I defined delegate and datasource functions in that child class, but it overrides parent class tableview data source/delegate methods.
But I want both of them to be separate.
However my Requirement is as Follows :
I want to retain a search bar and side button, on the top of all the viewControllers that search bar includes , a recent searches terms table underneath that.
So i thought of defining parent class for that and subclass other viewControllers from that class.
Am i doing it the right way ?
I assume you are talking about a view controller class. If I understood you right, then you are about to mess it up. Delegation is a way to avoid subclassing. Of course you can subclass the delegate - no problem. But you want a table view in the super class that owns a table in its view. And you want a subclass that has another table in its view plus the table that the superclass owns.
That is not impossible. But from your subclass' point of view, your subclass owns two table views. Even that is possible. Your view controller is the delegate of two tables (regardless of where in the view hierarchy they are declared and instanciated). When you now override the delegate and data source methods theny your subclass must either:
Determine which table it is dealing with/being called from. And then serve both tables appropriately.
Determine wich table it is dealing with/being called from. And then serve "its own" table appropriately and calls [super sameMehtod:withSamePamaters] to ensure that the superclas can still provide the data and server as delegate.
Which of both is smarter depends on the context and what you are about to achieve in detail.
A way of determinnig which table's delegate was called can be done by tagging the table views (do not use 0 as tag) or by comparing the tableView parameter of the delegate method with the corresponding properties (IBOutlets in this case). (In other cases you can compare the sender parameter with the IBOutlets. But tagging is probably easier to understand when reading the code later.)
Let's look at an example of the UITableViewDataSourceDelegat:
Your superclass implements:
#interface MySuperTableViewController:UITableViewController <UITableViewDelegate>
// There will be something in here.
// But it inherits self.tableView from UITableViewController anyway. We leave it with that.
#end
#implementation MySuperTableViewController
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// This method creates or re-uses a cell object and sets its properties accordingly.
}
#end
And your subclass:
#interface MySubTableViewController : MySuperTableViewController // no need to declare the delegate here, it is inherited anyway
#property (weak, nonatomic) IBOutlet UITableView *mySecondTableView; // self.table will be used by the superclass already.
#end
#implementation MySubTableViewController
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (tableView == self.table) { // This call refers to the one talbe that is managed by super
return [super tableView:tableView cellForRowAtIndexPath:indexPath];
}
// This method now creates or re-uses a cell object and sets its properties accordingly.
// You may want to check wether tableView == self.mySecondTableView etc.
}
#end
(This comes from scratch, not syntax checked etc. Do not expect this to run properly right away :)
BUT ... please re-consider your class structure. I am afraid you are getting lost in some rather unlogical class hierarchy. There is nothing wrong with having two talbes managed by a common view controller even without this subclassing-thing. And there is nothing wrong with using multiple tables in a view where each of the tables has its own delegate (can be a view controller). Since iOS 5 (or was it introduces with 6) we can use the UIContainerView for that purpose and nicely build it up in IB/storyboard.
try this,
ViewController.h
IBOutlet UITableView *firstTable;
IBOutlet UITableView *secondTable;
ViewController.m
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
if (tableView == firstTable) {
return 1;
}
else if(tableView == secondTable)
{
return 1;
}
return 0;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
if (tableView == firstTable) {
return [arrItems count];
} else if(tableView == secondTable)
{
return [arrData count];
}
return 0;
}
etc etc ....
The code below is creating a search for many strings. Initially there are 5 rows, when you reach row five, it adds another row. Instead of just directly editing the row, i load a filter controller (another view controller that as you type it completes words for you). When the user finishes finding a word he clicks it and comes back to this view controller. Now i want to fill the cell that was originally tapped with the text from the filter.
I tried asking earlier and didn't get any concrete answers.
I am running into a problem where when i scroll (after adding a new row), it starts filling in those rows with info already in the table, (as opposed to staying blank)
Please help me where i am going wrong
//global indexpath to remember which cell tapped
NSIndexPath *globalPath;
#interface SearchViewController ()
#end
#implementation SearchViewController
//Load implementation once per launch
- (void)viewDidLoad
{
[super viewDidLoad];
[self linkInputTableToDelegate];
_temporaryResultsArray =[[NSMutableArray alloc]init];
_flurryArray=[[NSMutableArray alloc]init];
_numberOfSections=6;
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:NO];
[InputTable reloadData];
textFromUserDefaults=[[[HelperMethods alloc]init]getObjectUserDefault:#"textFiltered"];
[self addTextToFlurryArrayForFlurryAndSavedLists:_textFromUserDefaults];
}
-(void)viewDidDisappear:(BOOL)animated{
}
- (IBAction)searchButtonPressed:(UIButton *)sender {
self.tabBarController.selectedIndex = 1;
}
//Makes the input table respond to delegate table view methods
-(void)linkInputTableToDelegate{
_inputTable.dataSource=self;
_inputTable.delegate=self;
}
-(void)performSearch:(NSString*)text{
//do search
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
int numberOfRows=_numberOfSections;
//Rows for iPhone 4
if ([[UIScreen mainScreen]bounds].size.height==480) {
numberOfRows=numberOfRows;
//Rows for iPhone 5
}else if ([[UIScreen mainScreen]bounds].size.height==568){
numberOfRows=numberOfRows+1;
}
return numberOfRows;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//In reality groups are created with 1 row inside, this is to allow spacing between the rows
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *kCellID = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellID];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellID];
}
//Is the cell the same as the one clicked when going to ingredient filter
BOOL cellIndexPathSameAsSelected=[self isCellIndexSameAsPreviousClicked:indexPath];
cell.textLabel.textColor=[UIColor blackColor];
if (cellIndexPathSameAsSelected && _textFromUserDefaults!=nil) {
if (![cell.textLabel.text isEqualToString:_textFromUserDefaults]) {
cell.textLabel.text=_textFromUserDefaults;
[self performTextSearch:_textFromUserDefaults];
}
}
return cell;
}
//Compares the previous clicked cell with the cell now selected
-(BOOL)isCellIndexSameAsPreviousClicked: (NSIndexPath*)cellPath{
if (cellPath.row == globalPath.row && globalPath.section==cellPath.section) {
return YES;
}
else{
return NO;
}
}
- (void)updateTableViewWithExtraRow :(NSIndexPath*)rowSelected{
NSLog(#"number of sections =%i",_numberOfSections);
if (rowSelected.section == _numberOfSections) {
_numberOfSections ++;
}
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellText = [tableView cellForRowAtIndexPath:indexPath].textLabel.text;
[[[HelperMethods alloc]init]saveObjectToUserDefaults:cellText :#"textFiltered"];
globalPath = indexPath;
[self updateTableViewWithExtraRow:indexPath];
}
-(void)addTextToFlurryArrayForFlurryAndSavedLists:(NSString*)text{
if ([_flurryArray count]==0 &&[text length]>0) {
[_flurryArray addObject:text];
}
for (int i=0;i<[_flurryArray count];i++) {
NSString *textInArray=[_flurryArray objectAtIndex:i];
if (![textInArray isEqualToString:text]) {
[_flurryArray addObject:text];
}
}
NSLog(#"Total number of saved items = %i",[_flurryArray count]);
}
// Dispose of any resources that can be recreated.
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
I have a couple of reactions looking at the code:
A couple of observations about the proper use of the UITableViewDataSource methods, specifically numberOfRowsInSection, numberOfSectionsInTableView, and cellForRowAtIndexPath:
These really should be driven by some model data structure (e.g. a NSMutableArray) and nothing else;
These methods should be stateless. They should not relying on the value of some NSString instance variable, like _textFromUserDefaults) but rather always look up the value in the NSMutableArray model structure on the basis of the value of the indexPath parameter. You simply cannot make any assumptions about when cellForRowAtIndexPath will be called. This may well account for your duplicate values.
None of these should be doing anything besides responding to the UITableView inquiry. For example, your cellForRowAtIndexPath is invoking performTextSearch. It really shouldn't do anything except return the cell.
Your cellForRowAtIndexPath currently has conditional logic and only updates the cell if certain conditions holds. Because cells are reused, you really want to make sure that you initialize the cells regardless. You can't be assured that the cell is blank when you get it, nor that the previous contents are the previous values for that indexPath. Because cells are reused, it could be for an entirely different row. This could also account for your duplicative entries.
Regarding the interaction of the master view controller and the details view controller, there are more elegant ways than passing data back and forth via NSUserDefaults. For example when you initiate the details view controller, you could just pass it the information it needs. And when it's done, it should call a method in the master view controller to update the data in the master view. To do that, the master view controller should conform to some protocol of your own creation. If you see the example I shared via chat, you can see what that might look like. Anyway, by having some delegate method in the master view controller that the detail view controller calls when it's done, that eliminates the rather fragile technique of using viewDidAppear to control the updating of the master table view.
You might want to contemplate employing "edit" (which allows you to delete, possibly also edit a particular row) and "add" buttons like the standard "master-detail" template that Xcode provides. There are a number of standard conventions here that might be better than having an array of blank cells that you can then tap on. Clearly, your user experience is entirely up to you, but you can always contemplate whether there are existing, familiar conventions that you might employ.
Rob's feedback is good. In broader terms, you can't rely on the cells in a UITableView to hold onto their data. For efficiency, it will be creating, using, and destroying cells at will, and using cellForRowAtIndexPath to figure out what they should look like. Instead of testing what's in a cell, you need to have your own set of data which describe the value of each cell, and just set the value based on the indexPath. I'd recommend storing all your cell information in an NSMutableArray which contains NSStrings or something more complicated if necessary. It will be easy to set default values when you add cells to the array. Then cellForRowAtIndexPath can just access the array rather than attempting its own logic based on current cells.
I am making a new app that has four sets of data that are very similar. I want to make four UIButtons to navigate to one UITabBar with four UITableViews. I don't want to make four buttons that push to four sets of tableviews. I figure making an app with 13 UIViews would make it bigger than it needs to be. So, I have to figure out how to make the UIButton action change the data the tables receive. I am working on this project actively. Any suggestions would be helpful. Thank you.
In order to change a table views data source you need to change what is returned from numberOfRowsInSection on your table delegate. One way I have done this is to use an enum to represent which set of data I want to use and have a local variable to save which one is selected
typedef enum
{
DataSetOne,
DataSetTwo
} DataSetEnum
#property(nonatomic, retain) DataSetEnum dataset;
#property(nonatomic, retain) NSArray *datasetone;
#property(nonatomic, retain) NSArray *datasettwo;
- (void) viewDidLoad
{
dataset = DataSetOne;
}
- (IBAction) buttonPressed
{
dataset = DataSetTwo;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(dataset == DataSetOne)
{
return [self.datasetone count];
}
else if(dataset == DataSetTwo)
{
return [self.datasettwo count];
}
}
And then in cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(dataset == DataSetOne)
{
//configure cell
}
}
If your data is similar you could just set a data from each button's IBAction variable:
NSArray* data = ...
Then in your button method:
-(IBAction*)clickedButton1:(id)sender {
data = [self getButton1Data]
[self reloadAll]
}