I have seen similar questions a lot on Stackoverflow and I tried a lot of things but I can't seem to figure this out. I have multiple TableViewControllers and 1 MainViewController. The MainViewController has buttons calling the different TableViewControllers and on selecting a tablecell the tableViewController dismisses.
The problem is that im pushing a new instance of my MainViewController every time I push from either one of my tableViewControllers. I currently use Segues to push between these different controllers.
In short: When switching from TableViewControllers to ViewController I want to prevent the ViewController to get pushed as a new instance because this way its removing my previous data input.
Im pretty sure I have to use either:
[self dismissModalViewController: withCompletion:]
performSegue
prepareForSegue
Or set some global variables in a class and call those, but im not experienced enough yet to implement this correctly.
A simple example of end result would be: 3 textfields in VC. On clicking textfield1 it opens tableview1 and on clicking a cell it updates textfield1. Textfield2 opens tableview2, etc.
Hope im clear enough, could post sample code if needed.
Edit, posting code (keep in mind, segues are performed in storyboard):
TableViewExample.h:
#interface IndustryViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
NSArray *tableViewArray;}
#property (nonatomic, strong) IBOutlet UITableView *tableViewIndustry;
TableViewExample.m:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"showIndustry"]) {
NSIndexPath *indexPath = [self.tableViewIndustry indexPathForSelectedRow];
ViewController *destViewController = segue.destinationViewController;
destViewController.industryText = [tableViewArray objectAtIndex:indexPath.row];
destViewController.industryTextName = [tableViewArray objectAtIndex:indexPath.row];
}}
Then in ViewController.m, viewDidLoad:
[industry setTitle:industryText forState:UIControlStateNormal];
These are the most important parts I think.
Is the segue of type "Push"? If so you should try dismissing the table view controllers using:
[self.navigationController popViewControllerAnimated:YES];
If the segue is of type "Modal" instead you should do something like this on your table view controller:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// your logic here
[self dismissModalViewControllerAnimated:YES];
}
As for the data exchange between controllers what I would personally do is creating a public property in the header file of the Table View Controller, like the following:
#property (nonatomic, weak) <Your_UIViewController_Subclass_Here> *mainController
Than, in the main controller, override the prepareForSegue:sender: method to set the newly created property to point to the main controller, like this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
<Your_Subclass_Of_UITableViewController_Here> *destinationController = segue.destinationController;
destinationController.mainController = self;
}
Now the Table View Controller will have a pointer to the main controller to send the data basically all you have to do is to implement some public method or property in the Main Controller to be called when the user selects a table view row in the table view controller in order to update the text in the textfields or whatever data model you are using.
Related
I have a view controller with a table view property and a detail view controller connected to each cell in the tableview via the navigation bar. In the detail view controller is a countdown timer, with an interval specified when the user creates the task. I am trying to make it so each cell (or task) has a unique detail view controller. I am using core data. This is what I have now:
-(void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
if (!self.detailViewController) {
self.detailViewController =
[[DetailViewController alloc] initWithNibName:#"DetailViewController"
bundle:nil];
}
Tasks *task = [[self fetchedResultsController] objectAtIndexPath:indexPath];
self.detailViewController.testTask = task;
[[self navigationController] pushViewController:self.detailViewController
animated:YES];
}
DetailViewController.m
#interface DetailViewController : UIViewController <UIAlertViewDelegate>
#property (weak, nonatomic) IBOutlet UILabel *timeLeft;
#property (weak, nonatomic) IBOutlet UILabel *timerLabel;
#property (nonatomic, strong) Tasks *testTask;
#end
I feel like this is the correct way to implement the detail view controller because it minimizes the amount of memory that needs to be created, however it doesn't really suit my needs. Currently when a user taps a cell, and taps back, and taps a different cell, only the first cell's properties are loaded. Also, if I were to delete a cell there would be no way to invalidate its timer (i think) with this method. Any suggestions?
---edit---
I guess the question I should be asking is: How should I make it so that each Detail View has a decrementing label (that gets its information from a timer)?
Your best solution is to follow the MVC properly in this scenario. In your case you are storing data for each detailViewController you are creating (such as task and the countdown timer/interval etc).. and in rdelmar's answer he is suggesting that you store all the view controllers in a mutableArray. I disagree with both approaches as yours will have memory problems when you dismiss the view controller (as u've seen for yourself) and in rdelmar's case, you are storing viewControllers (along with the data they reference) in a mutable array.. which is wasteful.
think about it this way.. you want to keep track of the data in one place (that's unaffected with which view is on display right now.. it could be detailVC 1 or 100 or the VC with the tableView or whichever) and at the same time you want to allocate one detailVC at a time that simply displays whatever the data source tells it to display. That way you can scale your app (imagine what would happen if you had hundreds of indexes in your table.. will you store hundreds of view controllers? very expensive and redundant).
so simply create a singelton.. the singelton will have a mutableArray that stores the timers pertaining to each tapped table index and so on.. the singelton will also launch the timers every time a cell has been tapped and keep a reference to it (ie store the NSIndexPath), so that when you jump back and forth between detailVCs and the table.. the timers are still in operation as required by you (b/c they are referenced by the singelton). the DetailVc will simply ask the singelton for what it should display and display it.
hope this helps. Please let me know if you need any further clarification.
The trouble with your code is that you're only creating one instance of DetailViewController, so each cell is pushing to the same one. You have to have some way in didSelectRowAtIndexPath to look at the index path and use that to determine which instance of DetailViewController to go to. One way to do that would be to create a mutable dictionary to hold references to the instances of DetailViewController. You could have the keys be NSNumbers that correspond to the indexPath.row, and the value would be an instance of DetailViewController. So, your code might look something like this:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
DetailViewController *detailVC;
if (![self.detailControllersDict.allKeys containsObject:#(indexPath.row)]) {
detailVC = [[DetailViewController alloc]initWithNibName:#"DetailViewController" bundle:nil];
[self.detailControllersDict setObject:detailVC forKey:#(indexPath.row)];
}else{
detailVC = self.detailControllersDict[#(indexPath.row)];
}
Tasks *task = [[self fetchedResultsController] objectAtIndexPath:indexPath];
detailVC.testTask = task;
[[self navigationController] pushViewController:detailVC animated:YES];
}
detailControllersDict is property pointing to an NSMutableDictionary.
I have some cells in a table view that when pressed leads to a view controller that has a label that I want to change.
Example: I have cells representing the biggest citys in America, I press Los Angeles and I get sent to the view controller with the label that changes to a number representing LA. If I go back and press New York the label now displays a number representing New York.
I would guess I give the label and cells identifiers and make some kind of if/else if.
Something like this:
"if 'newyorkcell' isPressed setText 'numbercell' = "12345";"
But with real code.
Thanks in advance!
For this you'll have to make a public outlet of your label in your view controllers header file:
#interface YourViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *yourLabel;
#end
Then you can set that labels text via didSelectRow, for this you have to give your detail view controller a storyboard id (in the storyboard) to be able to access it here in code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
ViewController *detailViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"VC"];
detailViewController.yourLabel.text = [self.yourArray objectAtIndex:indexPath.row];
[self.navigationController presentViewController:detailViewController animated:YES completion:nil];
}
Instead of using if/elses you could use an array or something where you have all your data in and access it using its index.
i am kind of making app which play audio using table view
After selecting particular row,it play audio on next view,but it does not solve my problem
Since want it to stop after dismissing the view and return to table view
So i want a way to play song on next view in such a way it stops after dismissing the view
or,
if i can access to the particular row(indexpathy.row) on next view
just property synthesize the raw no or int value to the nextview like bellow..
#interface NextViewController : UIViewController{
int rowNo;
}
#property (nonatomic, assign) int rowNo;
and synthesize in .m file like bellow...
#synthesize rowNo;
and when you push to nextview at that time set this property like this
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NextViewController *objNextViewController = [[NextViewController alloc]initWithNibName:#"NextViewController" bundle:nil];
objNextViewController.rowNo = indexPath.row;
[self.navigationController pushViewController:objNextViewController animated:YES];
[NextViewController release];
}
Simply take a variable in the next view's class and pass in the value of indexpath.row to it when you are selecting the cell. Since this variable has the value of row selected, you can access this value in next view.
I am testing a simple scenario that has a UITableViewController that has the cell segue to another UIViewController. Now during that segue, I need to pass some information to the UIViewController such as name and price of an item. I wanted to test the difference between using prepareForSegue and didSelectRowAtIndexPath: to pass those 2 data points. It seems to work with the didSelectRowAtIndexPath: but not prepareForSegue. After doing some debugging:
1-I am wondering if prepareForSegue happens before any IBOutlet on the destinationViewController are initialized and so you can't effectively set them. So prepareForSegue seems to be more geared towards passing non-IBOutlet properties.
2-If I am using a regular push segue, how can I access the "segue.destinationViewController" from the didSelectRowAtIndexPath: method.
PS: When testing the prepareForSegue, I am setting the segue in IB to push and when testing the didSelectRowAtIndexPath:, I am setting the segue in IB to modal.
PS: All resemblances to real names are incidental!
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"itemDetailSegue"])
{
NSIndexPath *indexPath=[self.tableView indexPathForSelectedRow];
NSString *theItemName=[[self.itemsForSale objectAtIndex:indexPath.row]objectForKey:#"Name"];
NSString *theItemPrice=[[self.itemsForSale objectAtIndex:indexPath.row]objectForKey:#"Price"];
JuanDetailVC* juanDetailVC= (JuanDetailVC*) segue.destinationViewController;
juanDetailVC.itemName.text=theItemName;
juanDetailVC.itemPrice.text=theItemPrice;
}
}
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *theItemName=[[self.itemsForSale objectAtIndex:indexPath.row]objectForKey:#"Name"];
NSString *theItemPrice=[[self.itemsForSale objectAtIndex:indexPath.row]objectForKey:#"Price"];
JuanDetailVC* juanDetailVC= (JuanDetailVC*) self.presentedViewController;
juanDetailVC.itemName.text=theItemName;
juanDetailVC.itemPrice.text=theItemPrice;
}
Thanks
You're right in that you cannot set outlet properties in prepareForSegue. You'll have to pass the properties into another property, then in viewDidLoad of the destination view controller set the outlet's property. For example: you can't set the text of a label, you'll have to create a second property (e.g., an NSString *labelText) and in viewDidLoad set the label's text (self.label.text = self.labelText).
You don't have access to the destination view controller in tableView:didSelectRowAtIndexPath. You'll have to create the instance yourself by alloc/initing it.
So I thought I'd have a go at building my own simple app. Please go easy on me I'm new to all this! The idea is this. For iPad have a single view controller with a text box and a text field. Text box takes a title, and text field takes the body of a report. There's a button on the page to submit the report, which bundles the two texts into an object and adds it to a table view within the same view controller. I have set the view controller as a delegate with <UITableViewDelegate, UITableViewDataSource> in my header file. My table view works fine for adding items in the viewDidLoad method. But adding items from the text inputs via a UIButton connected to -(IBAction) addItem falls over with: Property 'tableView' not found on object of type 'ReportsViewController'
- (IBAction)addReportItem
{
int newRowIndex = [reports count];
ReportObject *item = [[ReportObject alloc] init];
item.title = #"A new title";
item.reportText = #"A new text";
[reports addObject:item];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:newRowIndex inSection:0];
NSArray *indexPaths = [NSArray arrayWithObject:indexPath];
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
}
I understand that I'm trying to call a method within my object but I have other method calls to tableView which work fine. i.e.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [reports count];
}
I thought this was the point of delegation. I know I'm missing something, but as I say I am new to all this and have looked everywhere for an answer before posting. What do I need to do to send my IBAction message to tableView?
Do you have a tableView instance variable setup in your .h file of the view controller?
The reason you are able to access it in the delegate and data source methods is because they are passed in as part if the methods.
You will need to add the IBOUTLET tableView ivar and connect it to the tableView in your .xib.
Or perhaps your ivar for the tableView is named something else?
Good luck.
I had the same problem.
What helped was to inherit the View Controller from UITableViewController, instead of UIViewController. Not using the protocol names in angled brackets.
The TableView is then linked to the dataSource and delegate via the storyboard (resp. InterfaceBuilder).
The parent class UITableViewController has an IBOutlet tableView defined.
MyViewController.h:
#interface MyViewController : UITableViewController