one timer per detail view controller - iphone

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.

Related

How to prevent TableViewCell from duplicating during recycle?

So my situation is pretty unique. I have a to-do list app with a bunch of tasks. Each task has a UITableViewCell. After each table view cell is tapped, it creates a view controller with the task at that row's index path's property. These view controllers are all stored in a NSDictionary. This is the code representation of what I just said:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
DetailViewController *detailVC;
if (![self.detailViewsDictionary.allKeys containsObject:indexPath]){
detailVC = [[DetailViewController alloc]initWithNibName:#"DetailViewController" bundle:nil];
[self.detailViewsDictionary setObject:detailVC forKey:indexPath];
detailVC.context = self.managedObjectContext;
}else{
detailVC = self.detailViewsDictionary[indexPath];
}
Tasks *task = [[self fetchedResultsController] objectAtIndexPath:indexPath];
detailVC.testTask = task;
[[self navigationController] pushViewController:detailVC animated:YES];
NSLog(#"%#", self.detailViewsDictionary);
}
So this method of creating unique view controllers and storing them with a certain key almost always works. The problem arises when I delete or move the view controllers:
I was under the impression that a cell's index path gets recycled as you scroll down (dequeue). Doesn't that mean marking each cell with a number identifier would result in multiple cells for the same identifier?
Also, if you stored each view controller with a indexPath key, how do you make sure the key isn't set to two view controllers..? For example. Let's say you have 4 cells, which means 4 view controllers. You delete cell 3. Cell 4 moves down to cell 3s spot. You create a new cell which goes to spot 4. Now you have two controllers with the same indexPath key! How do you avoid this?? It's screwing up my app right now because tasks that have already been moved are loading their properties in the wrong view controller/cell!
I was suggested this to solve the problem before: "You maintain an NSMutableArray that "shadows" the contents of the table." However, I don't understand what this means/how to implement it.
You can use a technique we used to use on old databases. You store an NSInteger as a class var, and use that to assign a unique id to each of the cells as you create them. As you create each cell, you increment the unique id. Like this:
in your interface:
#property (nonatomic, assign) NSUInteger nextUniqueId;
then in - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
cell.tag = self.nextUniqueId++;
and then track those tags in your viewControllers. Just give them an assignable property, or customize the init to include the id.
Better to set the tag value for each row in cell for row at index path method.Store that tag value globally,and use that tag in did select row method.

Xcode/Obj-c - Segue pushes new instance

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.

Change label text depending on what Table View Cell was pressed (Storyboard)

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 want to get indexpath.row on next view after tableview or any other alternative

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.

self.tableView insertRowsAtIndexPaths from within tableView delegate

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