I have a parent view (table) that passes a detail object to the child view (table).
The parent view has a network request that gets data and passes data to the child view.
I want to be able to update the data when I'm in the child view. I think I need to call the parent view's network request method while viewing the child view, and update the child table. Is this possible?
In Parent VC:
- (void)fetchAppointmentsForVisibleDate {
self.appointmentArray = [DataSource getTodayData:self.visibleDate];
NSMutableArray *array = [NSMutableArray arrayWithCapacity:50];
for (NSDictionary *appointment in self.appointmentArray)
{
[array addObject: [NSString stringWithFormat:#"%#: %#", [appointment objectForKey:#"scheduled_time"], [appointment objectForKey:#"patient"]]];
}
self.listData = array;
[self.appointmentTableView reloadData];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
ChildVC *vc = [Child VC alloc]initWithNib#"ChildVC"];
vc.appointmentDictionary = [self.appointmentArray objectAtIndex:path.row];
}
Is your network request is bound to be called by parent view.
If its just a method you can set the delegate of your child to the parent.
i.e in the parent view you can do
create child object and just do
child.delegate=self;
and call your network method from child
i.e [delegate networkMethod];
Ok so lets assume that you code works do the following.
in the .h file of your child class add
{
id delegate;
}
#property(nonatomic,retain) id delegate;
in the .m file of you child do all the needed things like synthesize it etc.
Now in you parent class do
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
ChildVC *vc = [Child VC alloc]initWithNib#"ChildVC"];
vc.delegate=self; //here you set the delegate of your child.
vc.appointmentDictionary = [self.appointmentArray objectAtIndex:path.row];
}
Now in your child .m class if you want to call any function that resides in parent view class then just do.
[delegate myNetworkFunction] //it will give warning but dont worry
you can also pass argument if you want
[delegate myNetworkFunction:myArgument];
Related
I have been trying to figure this out for a while and not coming up with a solution. I have a view controller with a table and the first cell of the table is allocated for a button called "Add Friends". When clicked, it takes you to another view controller with a list of contacts in a table. When you click on a person, it goes back to the other view controller and adds the selected person. This is what I have so far.
ContactsViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
FirstViewController *newVC = [self.storyboard instantiateViewControllerWithIdentifier:#"newVCSegue"];
newVC.peopleArray = [[NSMutableArray alloc] init];
Person *user = [contactsList objectAtIndex:indexPath.row];
NSArray *userKeys = [NSArray arrayWithObjects:#"FirstName", #"LastName", nil];
NSArray *userObjects = [NSArray arrayWithObjects:user.firstName, user.lastName, nil];
NSDictionary *userDictionary = [NSDictionary dictionaryWithObjects:userObjects forKeys:userKeys];
[newVC.peopleArray addObject:userDictionary];
[self.navigationController pushViewController:newVC animated:YES];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
FirstViewController.h
#property (strong, nonatomic) NSMutableArray *peopleArray;
FirstViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//...
if (indexPath.row == 0) {
contactName.text = #"Add Person";
imgView.image = [UIImage imageNamed:#"plus-icon.png"];
} else {
NSString *firstName = [[peopleArray objectAtIndex:(indexPath.row)-1] objectForKey:#"firstName"];
NSString *lastName = [[peopleArray objectAtIndex:(indexPath.row)-1] objectForKey:#"lastName"];
contactName.text = [NSString stringWithFormat:#"%# %#", firstName, lastName];
}
return cell;
}
This lets me add one friend so far and if I decided to add another to the list, it replaces the first friend added.
What's basically happening is every time you select a new contact, you're recreating the array in the first view controller, hence it is replacing things. You ideally want to try and avoid getting the FirstViewController using the storyboard like that as well, it's pretty bad practice and may well lead to various problems later.
What I'd suggest in this situation is creating a protocol (look at the delegate pattern). This way, what you'd have is :
Use taps "Add Contact"
Contacts list appears, and FirstViewController is set as the delegate
User taps contact to add them
ContactsViewController informs the delegate of the user that was selected
FirstViewController adds the user, and dismissed the view controller
This is generally the approach you'd take, and it's pretty simple to implement. Start with the protocol
#protocol ContactsDelegate
-(void) contactsViewController:(ContactsViewController *)vc didSelectContact:(Person *)person;
#end
Then, make your FirstViewController implement this protocol. To do this, in your header file, in the angle brackets after the name (< >) add ContactsDelegate
In the implementation of FirstViewController, add the new method of the contacts delegate.
In your ContactsViewController.h file, add
#property (nonatomic, assign) NSObject<ContactsDelegate> *delegate;
Then when you display your contacts view controller, set the delegate
userVc.delegate = self;
[self presentModalViewController:userVc];
Then, in the user view controllers didSelectRowAtIndexPath:, simply inform the delegate that you've selected that person
[delegate contactsViewController:self didSelectContact:[contactsList objectAtIndex:indexPath.row]];
And lastly, in your FirstViewController, in the delegate method you added, we need to ADD the user to the list, not re-create the list
[peopleArray addObject:person];
And that should do what you're after :)
From what I understand, you are instantiating a new FirstViewController every time you select a contact in the ContactsViewController. Instead, you should reference the original FirstViewController (perhaps save it before transitioning to ContactsViewController), and use this reference to add the contact to the original array [original.people addObject:userDict]. As long as you make sure to reload the table, this should work.
I am developing an app which is similar to the settings app in iphone.
In my first VC i have a 4 rows upon selection it takes to the second VC. my second VC displays list of items. Once the user selected, the selected text should be displayed on the firstVC adjacent to the one selected.
How to achieve it with the help of dictionary objects or in other way.!Thanks in advance...
I did a similar task a few days back and here is how I accomplished that. Note, there might be many alternatives to achieving what you want.
KeyPoint: UITableView is dependent upon its DataSource (array or dictionary) for its data to be displayed in cell as well as number of sections and rows per section.
Now, initially your DataSource has values that are displayed as default. Upon tapping the row, initialize and push the 2ndViewController on navigation stack. In this 2ndViewController, you must somehow update the 1stViewController's DataSource (replacing original values).
Approach 1
You can use Protocols & Delegates
Create a protocol as following.
#protocol MyTableDelegate
- (void) dismissWithData:(NSString*)data;
Create a delegate reference in 2ndViewController, Call that delegate method and pass the selected data
#interface 2ndViewController : UIViewController
#property (assign) id<MyTableDelegate> delegate;
#property (assign) NSIndexPath *ip;
// Also synthesize it
#implementation 2ndViewController
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[self.delegate dismissWithData:#"Second Table Selection"];
[self.navigationController popViewControllerAnimated:YES];
}
Now in 1stViewController, implement the TableView & Protocol Method
#interface 1stViewController : UIViewController <MyTableDelegate>
#implementation 1stViewController
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
2ndViewController *controller = [[2ndViewController alloc] initWithNib....];
// Set the Delegate to Self
controller.delegate = self;
self.ip = indexPath;
// Push Controller on to Navigation Stack
}
- (void) dismissWithData: (NSString*) data
{
// We Will Store NSIndexPath ip in didSelectRowAtIndex method
// Use the ip to get the Appropriate index of DataSource Array
// And replace it with incoming data // Reload TableView
[self.dataSource replaceObjectAtIndex:[ip row] withObject:data];
[self.tableView reloadData];
}
Approach 2
You could also pass the data source to 2ndViewController in the didSelectRowAtIndexPath method..
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
2ndViewController *controller = [[2ndViewController alloc] initWithNib....];
controller.dataArray = self.dataSource;
// Push Controller on to Stack
}
Then in the same didSelectRowAtIndexPath method of 2ndViewController, update the data source, and call
[self.navigationController popViewControllerAnimated:YES];
also, you need to reload your tableview in 1stController's ViewWillAppear method
- (void)ViewWillAppear:(BOOL)animated
{
[self.tableView reloadData];
[super ViewWillAppear:animated];
}
I think you need to call [self.tableView reloadData] in viewDidAppear. If possible do go through this link Reload UITableView when navigating back?
The array that you are using in your first VC. Pass that array to the second VC, update it there in the secondVC class and write this statement "[firstVC.tableView reloadData]" in the viewWillAppear method of your firstVC class.
This will update your array of firstVC class in secondVC class when user selects anything in secondVC class, and than when user naivgates back table view of firstVC is reloaded. So that the update array is reflected in table view.
I have two UITableview. The first one shows 3 sections with 3 row each one, this is the default layout. When the user press line 2 section 0, the second view is showed to select a value. Once the value is selected, it returns to the first view, but this time, with a different layout. Depending on the value selected, the first view can only show one, two or three sections. I'm trying to achieve this behavior passing the indexpath from the child view to its parent and dynamically change the number of sections. I get the number of row selected in child view, but once it is passed to the parent view, the row numbers gets zero.
The code for child view is:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSUInteger row = indexPath.row;
//Create parent view instance to pass back indexpath as a integer
DiarioA *plc = [[DiarioA alloc] init];
NSInteger i1 = row;
plc.rowTipo = i1;
[plc release];
[self.navigationController popViewControllerAnimated:YES];
}
Pa
ret view (DiarioA.h)
#property (nonatomic, assign) int rowTipo;
PArent view (Diario.m)
#synthesize rowTipo;
- (void)viewWillAppear:(BOOL)animated{
[self.tableView reloadData];
//Check if rowTipo has value
NSLog(#"RowTipo: %d",rowTipo);
}
//Use rowTipo to dynamically adjust uitableview layout.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
if (self.rowTipo==0) {
return 3;
}
return self.rowTipo;
}
Could you help me to indentify why I'm loosing the indexpath that is coming from the childview to parentview. Your help is greatly appreciated.
DiarioA *plc = [[DiarioA alloc] init];
you are creating a new object here and setting the plc.rowTipo = i1; changes the value of this object not the parent object.
Either you pass your parent object to the child controller and then change its value like this.
// child .h file
DiarioA *plc;
#property (nonatomic, assign) DiarioA *plc;
//child .m file
#synthesize plc;
// code when you push your child controller
ChildController *controller = [[ChildController allo] initWithNibName.....];
controller.plc = self
and
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSUInteger row = indexPath.row;
plc.rowTipo = row;
[self.navigationController popViewControllerAnimated:YES];
}
I think you should make protocol of your child view controller and make your parent view controller its delegate. Than just set delegate property after initialization of your child view controller and call your delegate method sending indexpath there.
I am trying to refresh an UITableView every time I navigate the the view that contains this Table.
I Have a ViewController and a custom UITableViewController that manages to set the Table Correctly when the application starts, using an NSMutableArray contained inside the controller.
When I navigate to the page containing the table, the ViewController calls a function that gets the data from a server with an HTTP request and parse it in an NSMutableArray.
Now here is my problem. I manage to send this array to my UITableViewController, but when I want to refresh my tableView, nothing happens.
I tried to use [myTable reloadData], but it doesn't calls the numberOfRowsInSection, or cellForRowAtIndexPath functions. I saw that people with the same problem solved it using [self.myTable ReloadData], but I get an error :
accessing unknown getter/setter method
I am pretty new to objective-C, and this error is still a bit mysterious to me as I get it a bit randomly.
Anyway, there is a high probability that I made a mess with the declaration of the UITableViewController (where am I supposed to declare it?) and with the Interface Builder links, so this can be a clue to find the solution.
Any one have an idea?
Thank you very much!
EDIT : Here is my tableview controller class:
#import "MyCell.h"
#class Mycell;
#interface MyTableController : UITableViewController {
IBOutlet MyCell * myCell;
IBOutlet UITableView * myTable;
NSMutableArray *data;
}
#property (nonatomic, retain) IBOutlet UITableView * myTable;
- (void) EditTable : (NSMutableArray*) param;
#end
And now the .m:
#implementation MyTableController
#synthesize myTable;
- (void) viewDidLoad {
[super viewDidLoad];
myTable = [[UITableView alloc] init];
data = [[NSMutableArray alloc] init];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [data count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"MyCell";
MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; >
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"MyCell" owner:self options:nil];
for (id currentObject in topLevelObjects){
if ([currentObject isKindOfClass:[UITableViewCell class]]){
cell = (MyCell *) currentObject;
}
}
}
NSString *datastring = [listenom objectAtIndex:indexPath.row];
[cell setCell: datastring ];
return cell;
}
- (void) EditTable : (NSMutableArray*) param{
//This function is called by the ViewController when the user goes to the page containing the view
data = param; //The param array contains the data from the HTTP request
[self.tableView reloadData];
[self.myTable reloadData]; //I tried both, but only the first one actually calls the previous functions
}
You have a number of problems in this code sample. I'll point out a few of them here but I highly recommend reading the relevant Apple documentation at:
http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/Introduction/Introduction.html
and
http://developer.apple.com/library/ios/#documentation/userexperience/conceptual/TableView_iPhone/AboutTableViewsiPhone/AboutTableViewsiPhone.html
Some issues in your code:
Since the class MyTableController is a subclass of UITableViewController you don't need the attribute and property for myTableView. The tableView property is defined and initialized as part of UITableViewController's implementation with its dataSource and delegate set to the UITableViewController instance. This is why [self.tableView reloadData] is calling your delegate and dataSource protocol methods.
You are also using interface builder so if you did want to create your own subviews you should either do that within IB and set the outlet there or do it in your code which means creating the subview(s) in viewDidLoad and then adding them to your view with [view addSubview:mySubView].
A better way to set the data for your table would be to create a property for your data attribute and call setData from the view controller that has initialized the MyTableController instance. You would use the setData: method to do this. You can call [self.tableView reloadData] in setData. You don't need to explicitly reload the table when the view is loaded as this is done automatically. A more minor point, if you stay with EditTable I would rename it to be more descriptive and to use camel case (e.g. setDataForTable`) to be consistent with iOS conventions.
You don't show any init/alloc for the listenom attribute referenced in tableView:cellForRowAtIndexPath:. Did you mean to use data instead?
Is your MyTableController.m file the complete version? If so, you are missing viewDidUnload and dealloc methods. Both of which are required. viewDidUnload should release any objects allocated in viewDidLoad and dealloc should release anything retained by the controller (including objects released in viewDidUnload.
As you are using tableViewController you should be able to use self.tableView instead to reload the data like this
[self.tableView reloadData];
you need to synthesize first then you can use self.myTable
do on the top
#synthesize myTable
and then
[self.myTable reloadData];
I want to populate a TableView with data from a database and, when tap a cell, in didSelectRowAtIndexPath, sending selected ID to a new View.
I would like to view, in cell text, the description of the record and to put ID in a hidden field/property of the cell, but I could not find a way to do it.
I know I can get an ID from the data source (something like [my_data_source objectAtIndex:indexPath.row]; ) but I don't really like this way, I would prefer to have an ID assigned to a single cell.
Is there a way to do it?
Thanks in advance and greetings.
I'm guessing you've come from web development? I also found it difficult to do it this way, but its the best way. IT probably is possible - but its better if you get used to doing it like this, it really is.
Basically define an NSArray in the .h file (so the whole script can use it).
then in the init function:
// set the array
myArray = [NSArray arrayWithObjects:#"One",#"Two",#"Threee",nil];
[myArray retain];
then the table view delegate methods:
// set numebr of rows
- (CGFloat)tableView:(UITableView *)tableView numberOfRowsForSection:(NSUInteger)section {
return [myArray count];
}
// set the cell titleLabel value
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// set the cell - I can't remember the exact code then do:
cell.textLabel.text = [myArray objectAtIndex:[indexPath row]];
}
// similarly
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"%#",[myArray objectAtIndex:[indexPath row]];
}
(disclaimer: these are just off the top of my head, and I haven't checked the exact methods - they're probably wrong in some way - but the code inside the functions is what you want really.)
When you've started using this you'll see its so much better than "hiding" an id somewhere in a table. To get things from the database I would suggest adding it all to a dictionary, or an array or similar and doing it like that when you init the class, but if you really want to do it dynamically then pretend your "hidden" ids are just index's of an array. So id#1 is at index 1 in your array. :)
Allright here is a quick hack on a different approach. I always like to deal with objects that are self contained. Pack all the data that you want into a custom class (here called MyData) for the table view you initialise it with the minimal amout that you need there. id and text that you pulled from the database. You also implement a function that can load the rest of the data from the DB.
When the item gets selected you pass the instance of your object to the subview controller and fill its data from the database. You can trigger the filling in the main viewcontroller or the subviewcontroller, that does not matter.
The main point is to pack all the data that goes together into one object (basically a "model" you already have a view and controller) and then fill views by accessing that object. This keeps your interface the same all the way through your applications. And makes changes easier. For example if you find out that it is better to fill in all the data from the DB at the start of your program you can do that now without changing the other views.
#interface MyObject : NSObject
{
}
// Create a stump object that contains only the necessary info
+ (id) withName:(NSString)name id:(int)id;
// loads the rest of your data from the DB
- (void) fillFromDb;
#property (readwrite, retain) NSString name;
#property (readwrite, assign) int id;
// The data fields that you need
#end
// in tableview controller
#interface MyTableViewController ...
{
NSMutableArray _dbData;
}
#end
#implementation MyTableViewController
- (void) viewDidLoad {
// Load your data from DB
for (int i =0; i < dbCount; ++i)
{
MyObject* data = [MyObject withName:dbName[i] id:dbId[i];
[_dbData addObject:data];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
cell.textLabel.text = [_dbData objectAtIndex:[indexPath row]];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
//Create Subviewcontroller
// Optional call fillFromDb here and not in subviewcontroller
subviewcontroller.dbData = [_dbData objectAtIndex:[indexPath row]];
//activate subview
}
#interface SubViewController {
MyObject* _dbData;
}
#end
#implementation SubViewController
- (void) viewDidLoad {
[_dbData fillFromDb];
// Do View Initialisations with the newly fetched Data
}
The code is here just to demonstrate the architecture