I'm creating an app using storyboards where a first screen has a list of songs in a table view and I want to pass that song to the next view (let's call it the music player). I can accomplish this, but I also need to music player to keep track of various exercise statistics while the music player is playing music.
The problem is, if I enter the music player, go back to the song list view, and then select a cell again, it creates an entirely new instance of the music player. This is problematic as it creates a new timer and I lose all my exercise statistics. I also discovered that the original music player instance still exists because the timer continues to fire.
Is there anything I can do to make sure only one instance of the music player continues to appear?
Here is my current code (in order of execution):
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
self.nextScreen = [segue destinationViewController];
}
^ This stores the next screen as a member variable. I have to do this because prepareForSeque: is called before didSelectRowAtIndexPath: and I need to set data on the next view.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
self.nextScreen.currentSong = [self.allSongs objectAtIndex:indexPath.row];
}
^This just sets the song.
I then proceed to set the next view up as needed in viewDidLoad:.
Is there a trick to fix this? If not, any suggestions how I get around this? I'm thinking one workaround is I'll have to set up some sort of singleton, I'm just not sure if that's the best option since I will need to reset the data so often.
Thanks!
You can't do this with segues. Segues (with the exception of unwind segues) always create new instances when they are performed. You can push to your new controller (assuming your embedded in a navigation controller), and in the method where you do so, only create a new instance if it's the first time.
-(IBAction)goToNext {
if (! self.next){
self.next = [[NextViewController alloc] init];
self.next.whateverProperty = whatever you want to pass;
self.next.delegate = self; // set the delegate here if you're using delegation to send info back to this controller
}
[self.navigationController pushViewController:next animated:YES];
}
You can still set up your controllers in the storyboard if you want, just don't connect them together. In that case you would instantiate them with the following instead of using alloc init:
self.next = [self.storyboard instantiateViewControllerWithIdentifier:#"next"];
Related
I am currently working on a project that requires a list of customers to be displayed in a UITableView, the associated cell then segues to a TabView to display a detailed customer record in a tabbed ui.
I have setup the story board with the required TableView and populated fine. The TabViews all setup and I have added a custom class to the main TabView controller which can take the ID (required to interrogate service and return further data) and Customer Name. I have also added a UIViewController for the first tab in which I need to get the ID value.
I can't seem to get hold of the ID or Company Name that is passed. I have tried importing the .h file of the UITabView. I know the UITabView .h file is being populated with the values as in the .m file I am using the Customer Name to update the title of the Navigation Bar. However, whenever I breakpoint on line that gets the ID in the .m file for the individual tab, it always returns nil.
I am using the following code to try and get this value:
companyTabController *headerData = [companyTabController alloc];
_companyName_lbl.text = headerData.companyName;
_companyID_lbl.text = headerData.ID;
I have tried several variations of the above and all to no avail.
You can also use NSUserDefaults to save the data, I think that is the simplest way to save the data throughout the app.
From the code you posted, the headerData is a new instance. So the companyName and the ID will be nil unless you assign some value to them.
Since, you mentioned that you are able update the navigation bar title, try using the same object for fetching the values in this controller as well. (Maybe you can use a singleton object)
If your segueing you have to use the prepareForSegue:sender: method as such:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
companyTabController *companyTC = [segue destinationViewController];
companyTC.companyName_lbl.text = headerData.companyName;
etc
}
if your not segueing you will have to instantiate it as such :
- (void) didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *selectedCell = [self.tableView cellForRowAtIndexPath:indexPath];
companyTabController *companyTC = [self.storyboard instantiateViewControllerWithIdentifier:#"CopmanyTabController"];
companyTC.companyName_lbl.text = selectedCell.textLabel.text or = headerData.companyName;
[self.navigationController pushViewController:companyTC animated:YES];
}
Still fairly new to iOS. I've managed to write a basic app to display a list/table of documents. I've included:
cell.accessoryType = UITableViewCellAccessoryDetailButton;
which display a blue i within a circle button which I guess can be used to perform some sort of action.
What I'd like to do now is, upon click, to display a subsequent screen with information about the document, buttons, add, delete functions, date, file size etc...
Is this done via segue or some other method?
Being a novice I am not sure what is/are the next step(s). If I know what steps I must take in order to get to next scree(s) I can search the net for example of how to do any given step.
Thank you for your help
The accessoryButton triggers it's own delegate method, distinct from the row selection, called:
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
Add that method to your delegate, and then do a vc transition. There are several ways to do that transition, most of the common ones are discussed here...
First, welcome to the iOS developing community!
In order to do this, I would use a segue to a detail view that you can design. When you select a row in your table using didSelectRowAtIndexPath, you would set an #property to the object in your array that was selected: self.selectedObject = self.tableviewarray objectAtIndex:indexPath.row; Then in the prepareForSegue method you can get the destination view controller and do destinationViewController.myObject = self.selectedObject; Now the detail view knows what object to display info for!
You'll need to implement the delegate method
-tableView:(UITableView*) didSelectRowAtIndexPath:(NSIndexPath*)
and handle the row tap in that callback accordingly.
An example would be like this:
-tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
YourObject *theModelForRow = [_itemList objectAtIndex:indexPath.row];
YourViewController *someNewViewController = [YourViewController viewControllerWithModel:theModelForRow];
[self.navigationController pushViewController:someNewViewController animated:YES];
}
i'm doing some testing of Core Data, let's say i have a mainViewController with a navigationBar and addButton.
Clicking on the addButton will open a detailViewController. When i press save to insert a new Object the detailVieController will close and show the table with the new data inserted.
I can think two different way to do that.
FIRST METHOD - Passing the ManagedObjectContext
In the action of the add button i create an instance of the new detailViewController and i pass the managedObjectContext to it. So will be the save button of the detailViewController that will take care of saving the context and then pop the controller.
This is the method called by the addButton in the MainViewController
-(void)addNewObject{
DetailViewController *detVC = [DetailViewController alloc]initWhit:self.managedObjectCOntext];
[self.navigationcontroller pushViewController:detVC animated:YES];
}
This method is called by the save button in the IngredientViewController
-(void)saveObject{
NSError *error;
if (![self.managedObjectContext save:&error]){
NSLog(#"Error");
}
}
SECOND METHOD - Using a delegate
In the action of addButton i create an instance of DetailViewController, i set it as delegate, so when i press the save button in the DetailViewCOntroller will call the delegate that will pass data to the main controller.
This is the method called by the addButton in the MainViewController
(void)addNewObject{
DetailViewController *detVC = [DetailViewController alloc]init];
detVC.delegate = self;
[self.navigationcontroller pushViewController:detVC animated:YES];
}
This method is called by the save button in the IngredientViewController
-(void)saveObject{
[self.delegate detailVCdidSaveObject];
}
This is the delegate implemented in the mainViewController
detailVCdidSaveObject{
NSError *error;
if (![self.managedObjectContext save:&error]){
NSLog(#"Error");
}
}
------------------------------ Passing the object
Is it best to pass raw data to the DetailViewController and create there the object or it's best to pass the instance of the object to DetailViewController that will take care of settin its data?
For Example
This way i link the object instance of the mainVC to the one DetailVC so i can easilly set its value
-(void)addObject{
DetailViewController *detailVC =[[DetailViewController alloc]init];
detailVC.delegate = self;
self.object = [NSEntityDescription insertNewObjectForEntityForName:#"Object" inManagedObjectContext:self.managedObjectContext];
detailVC.object = self.object;
[self.navigationController pushViewController:detailVC animated:YES];
}
this way i pass raw data and let the detailVC create the instance
-(void)addObject{
DetailViewController *detailVC =[[DetailViewController alloc]initWithName:#"objname"];
[self.navigationController pushViewController:detailVC animated:YES];
}
those code are just pseudocode for educational purpose. all ways works, i just want to know which do you think it's the most correct and why. thanks
I have used the first two methods and in my opinion they are both equally valid (though I personally prefer delegation). However, the third method caused problems if you give the user the option to cancel or go back in a navigation controller. If that happens, you will have an object that you never needed to create.
This sounds like a perfect use case for a NSFetchedResultsController. A NSFetchedResultsController is an object makes displaying data from core data in a UITableView a lot easier. It even tells you when the objects in core data matching a predicate change (insert, delete, update, move).
So the way I would do it is that MainViewController would have a NSFetchedResultsController that provides the data to the UITableView. When you press the add button, it would do what you have in the first method. The DetailViewController will create the new instance, set the values on it then save the managedObjectContext.
Since the MainViewController has the NSFetchedResultsController, it will automatically know that a new object have been created and it can update the UITableView to show it.
The NSFetchedResutsController documentation and the NSFetchedResutsControllerDelegate documentation show you exactly how to use it with a UITableView including code you can copy into your view controller that do the majority of the work.
The actual answer depends on your preference. In my project, I have implemented the first two methods. A definite No for the third method from my side because of same reasons as Kevin mentioned. If the user cancels the operation or some error occurs, then you will have to take care of removing the change (Perhaps write the following code in your didMoveToParentViewController method and cancel method):-
[self.managedObjectContext rollback]
Assuming of course that you do not have any other process modifying that managedObjectContext at the same time.
Now, I prefer the first two methods because :-
The first method allows me to write additional code in saveObject method. Lets say that you want to validate some properties before saving the object. These properties are only present in detailViewController. So, you cannot use a delegate in that situation without explicitly passing each and every property back to delegate function (which can get messy).
Now, assume that you are creating a object in your mainViewController and the detailViewController is only used to populate a field of the object that was created in mainViewController. In such a situation, I would use the delegate method and pass the field back to the mainViewController so that when the user saves the object in mainViewController, then the field values are saved along with it. If the user cancels mainViewController, then the field values are also not saved.
I have a very complex situation (well for me) I am trying to resolve but thus far am having trouble with it.
I will outline the structure of the application now and then explain the problem I am having.
The names I am using are made up due to sensitivity of the data I am using.
secondToLastViewController // is a UITableView on the navigation stack
lastViewController // is just a normal UIView that i want to push onto the navigation stack
RequestClass // this class dose requests to my database and passed the data back to correct classes
getInfoClass // class is used for this specific request stores the information correctly and passes it back to secondToLastViewController
So as the user initiates didSelectRowAtIndexPath inside secondToLastViewController I make a request for the data using the RequestClass
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//..
[RequestClass Getinfo:storedInfoPram];
}
now the thread shoots off to my RequestClass, which in turn queries the DB for some data which is then received and this data is passed off to my getInfoClass the reason I have done this is because there are dozens and dozens of different calls in RequestClass all doing different things, this particular request brings back alot of data I have to sort into correct object types so have created this class to do that for me.
anyway inside getInfoClass I sort everything into their correct types etc and pass this data back to secondToLastViewController in a method called recivedData, this is also where I think things are going wrong... as I create a new instance of secondToLastViewController the thing is I dont know how to pass the data back to the same secondToLastViewController that is already on the stack and was where the original request came from.
- (void) recivedData {
// do some stuff then pass data back to secondToLastViewController
SecondToLastViewController *sec = [[SecondToLastViewController alloc] init];
[sec sendGetSeriesArrays:pram1 Pram2:pram2 Pram3:pram3 Pram4:pram4 Pram5:pram5];
}
Now going back into SecondToLastViewController the thread lands in this method
- (void)sendGetSeriesArrays:pram1 Pram2:pram2 Pram3:pram3 Pram4:pram4 Pram5:pram5{
// call detailed view onto the stack
lastViewController *last = [[lastViewController alloc] initWithNibName:#"lastViewController" bundle:nil];
[self.navigationController pushViewController:last animated:YES];
}
after the thread reaches this point nothing happens... all the data is there and ready to be sent but the new view is never pushed to the controller stack.. and I think it is due to me declaring another version of secondToLastViewController when I am inside getInfoClass
what I would like to know firstly is how do I pass the recived data in sendGetSeriesArrays to the final view and secondly how do i even load the lastview onto the navigation stack?
Your observation is correct you are creating the secondToLastViewController instance again inside the getInfoClass. Dont do like that you have to use delegate/protocol approach for passing the data back to the secondToLastViewController.
Do like this
Define a protocol in getInfo class
getInfoClass.h
#protocol GetInfoClassProtocol <NSObject>
//delegate method calling after getting data
// I dont know the argument types give it properly
- (void)sendGetSeriesArrays:pram1 Pram2:pram2 Pram3:pram3 Pram4:pram4 Pram5:pram5;
#end
// declare the delegate property
#property (assign, nonatomic)id<GetInfoClassProtocol>delegate;
getInfoClass.m
- (void) recivedData {
// do some stuff then pass data back to secondToLastViewController
if ([self.delegate respondsToSelector:#selector(sendGetSeriesArrays: param2:)])
{
[self.delegate sendGetSeriesArrays:pram1 Pram2:pram2 Pram3:pram3 Pram4:pram4 Pram5:pram5];
}
}
secondToLastViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//..
RequestClass.delegate = self;
[RequestClass Getinfo:storedInfoPram];
}
Your secondToLastViewController should conform to the GetInfoClassProtocol
There are lots of ways you can accomplish this. In your revivedData function, instead of creating a new instance, you could:
1) Maintain a pointer to the navigation controller in getInfoClass, then you can get the last view controller from the view controllers on the navigation stack and use that. This will be the active instance of the view controller. There are ways to recover this from the window object, but those seem fragile and I would not recommend that approach.
2) You can pass a pointer to self from secondToLastViewController to your RequestClass getInfo call, then hold on to that and pass it back. This is probably a pain depending on the amount of code you have already.
3) You can maintain a static instance of the class if you will never have more than one secondToLastViewController. See How do I declare class-level properties in Objective-C?
I'm working with tableviews showing a hierarchy data structure. I take the first node and show the sons in the tableview and repeat it until the end of the tree. I'm doing it in this way:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if([[actualNode getSonAtIndex:0] sonsCount]>0) {
NSLog(#"New level");
actualNode = [actualNode getSonAtIndex:indexPath.row];
[self.tableView reloadData];
} else {
NSLog(#"Service");
[self performSegueWithIdentifier:#"Service" sender:NULL];
}
It is not the correct way but the problem is that I don't know how many levels has the tree so I can't create them in the Storyboard. The idea is create only one TableView for showing each level but doing it in that way I can't go back to the previous level on the NavBar like I could do if I was working with some controllers in the storyboard and I don't have animations. So, is there any way to do it? something like:
MyNextLevel *nextlevel = [MyNextLevel alloc];
[nextlevel setNode: actualNode]
Myactualtableview = nextlevel; (Here the next level is showed in the screen with animations and with the possibility to go back to the previous level)
Thanks.
This is why I don't like storyboards. It has always been possible to do what you want to do (if I'm understanding it correctly), but storyboards detract from it; they don't have the flexibility of doing things in code, which is what we always had to do back in iOS 3 and iOS 4 anyway.
Anyhow, you want to do something like this:
UITableViewController* tvc = [[MyTableViewController alloc] initWithNibName:#"MyNib" bundle:nil];
[self.navigationController pushViewController:tvc animated:YES];
Your business logic can just pick the class for MyTableViewController. Alternatively, MyTableViewController could be something flexible, where between those two lines you give it some configuration info that causes it to show the right data! (I have to admit, though, that you could do that same thing using a storyboard, configuring the table view controller in performSegue:.)