I have a TableView which describes a book with sections which represents the chapters and rows representing the verses.
A the top of this TableView I have a button in a navigation bar to allow "navigation".
The goal of this navigation button is to allow the user to easily jump to a given chapter/verse without scrolling manually (which can be very long).
When the button is pressed a tableview controller is called displaying all the available chapters of the book and when a chapter is selected another table view is called displaying a list of the available verses in the current chapter. Finally when the line is chosen the tablew view displaying the book should scroll to the given index/row.
So the idea : from the tableview representing the book I call the chapters view as modal and the verses as a push over the chapters view.
My problem is that I don't get the point of managing the delegate and dismissing from the 2nd modal view.
With 1 modal view I do things like that.
In the displayed VC (View Controller) I added the protocol and the delegate
#protocol ChapitresTableViewControllerDelegate <NSObject>
- (void)didDismissPresentedViewController;
#end
#interface ChapitresTableViewController : UITableViewController
#property (nonatomic, weak) id <ChapitresTableViewControllerDelegate> delegate;
#end
I have in the didSelectRow
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.delegate didDismissPresentedViewController];
}
in the displaying VC I add the following line
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
ChapitresTableViewController *chapitresTableViewController = segue.destinationViewController;
chapitresTableViewController.delegate = self;
}
and of course
-(void)didDismissPresentedViewController
{
[self dismissViewControllerAnimated:YES completion:nil];
}
this would just work fine if I wanted to close after the first modal VC, but what I want is to have the second one being dismissed after I click in the second. Ok I can write the protocol and stuff in the second modal VC but how do I manage to have the delegate being send to the second VC.
Hope my question is clear enough it is not so easy to explain it.
Anyone understand me and can help me ?
NOTE : I know for now I don't pass any parameters back to the delegate, which I will do later to do the scroll. For now I just want to be able to close the second view, then I will add the required stuff to pass the parameters back to the delegate
I'm sure you can do this, but rather than modal view controllers with a navigation bar, wouldn't it be easier to use a navigation controller? Then you can use popToViewController to go back as many levels as you want to a particular view controller. You can either pass the UIViewController* of the various controllers you might want to pop to, or do so programmatically: e.g. How to pop back to specify viewController from navigationController(viewControllers/stack)?
In this scenario previous views controllers are retained. The ones you pop off are released (just like the modal ones you dismiss are released), but the ones that you pushed from are retained (just like the ones you presented from in a modal world are retained).
If the book is large, though, you'll have to be sensitive to memory usage. Thus, you will probably want to handle didReceiveMemoryWarning to release the model data for the previous views in either your modal sequence or push sequence, in which case, on viewDidAppear, you'll want to see if your app had to release the memory in response to didReceiveMemoryWarning and reload it in that case. But that's the desired behavior, either way, gracefully release the pages if needed (and reload them when the particular view reappears), but keep it in memory if you can.
Finally, you might also want to contemplate using UIPageViewController. Given what you've described, I'd like consider UIPageViewController first, UINavigationController and push segues second, and the use of modal segues third.
Related
As someone who usually used separate xibs in the past I thought I'd give storyboard a go as it seemed a lot simpler to use and much easier to develop with. I've been writing an application where the essential set up is this:
At the top of all this is a UINavigationController (first level). Then I have Multiple UIViewControllers (second level) with buttons in them which you can tap to switch between the second level UIViewControllers.
However a problem occurs when I start switching between the second level UIViewControllers. I first thought this was an initialisation problem with the NSMutableArrays because in my code I have a NSTimer set to loop periodically and found when I set a breakpoint during it, when I went forward to the next timer tick event there appeared to be different instances of the same NSMutableArrays and it seemed a gamble to try and insert new values into these array with it sometimes working, sometimes not (as it may or may not insert into the correct instance).
Then, looking at the memory usage under Debug Navigator I found the issue. Each time I "switched" between the UIViewControllers a new UIViewController was being initiated, along with all new variables.
The code I am using to switch between them is
-(void) perform {
[[[self sourceViewController] navigationController] pushViewController:[self destinationViewController] animated:NO];
}
Or essentially a push segue transition. This also explains why when I tried to switch back to my view, the data on that view was lost as it is a complete new view.
Does anyone know how to switch between multiple ones of these UIViewControllers (say 5) essentially like a UITabViewController would except without the tab bar being present?
First option you can do this: You can use a tabbarcontroller for switching viewcontroller and hidden the tabbar. Then on buttonclick setthe tabbar index.
Second option you can do this: Create one more view controller and in this viewcontroller subview the all switching viewController and when you want to switch viewcontroller just bring that viewcontroller view to front by delegate.
Do you need the navigation bar and other features provided by your top level navigation controller?
If not, you could use a UIPageViewController instead.
You set up all your second level view controllers and then just have to tell the page view controller which one to display.
If you implement the associated delegate methods, it will automatically provide swipe gestures to switch between them and nice animations to get them on and off screen.
You can also get it to put a UIPageControl at the bottom showing a dot for each VC with the dot for the current VC highlighted.
In my test project I have a ViewController and a TableViewController controller embedded in a Navigation Controller. The ViewController is the main view, and the user can navigate to the TableViewController and then return back to the ViewController.
I am using a 'push' segue when going from ViewContoller>TableViewController, and the TableViewController is dismissed using [self dismissViewControllerAnimated:YES completion:nil]; when the user wishes to go back to ViewController.
In ViewController, I have a button that changes the text on a label:
-(IBAction)onButtonPress:(id)sender {
_myLabel.text = #"New Label Text";
}
When navigating to TableView, and then back to ViewController, the change in the _myLabel.text has been lost and the original text is restored. What is the best way to ensure UI data is retained when navigating between views? I might only have one label in this project, but at some point I will have many UI elements, for example, WebViews that need to keep a page loaded when the user navigates away and then comes back.
What would you suggest is the best method to implementing this?
Thanks for your time.
First of all, View is your data representation, any data is your Model. So you may store Model with its entities and values. Your controllers manage data to represent it on View or to change Model according to user actions in View.
So add some Model-object for storing _myLabel.text. And use this object for both controllers in your app.
Try to remove setting the text from the ViewWillAppear or ViewDidAppear method, put it in ViewDidLoad.
I'm developing an app that resembles the structure of a 'Choose your own adventure' book, with lots of multimedia content (photos and videos mainly). Each 'page' is a ViewController in which the user has to complete a puzzle or another task in order to go to the next one.
I'm creating a UINavigationController and I'm pushing every new ViewController on top of the stack. But I'm worried about having memory issues since there is some heavy multimedia content and I'm not popping any ViewController, 90% of the time the user can't go back to the previous ViewController, just forward to a new one.
I'd like an alternative in which every time I jump to a new ViewController the old one gets released from memory.
If you are not going to go back then you should get rid of your old UIViewController instances. You can do so after a new instance of UIViewController has been pushed to the UINavigationController stack.
One option is to just replace the navigationController.viewControllers array in the viewDidAppear or viewDidLayoutSubviews method of your view controllers being pushed to the navigationController. Means, you are going to need it in every UIViewController instance.
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
navigationController.viewControllers = #[viewController];
}
Second option is useful if you are using a main container controller that handles the pushing of your new UIViewController instances. Just implement UINavigationControllerDelegate protocol and implement navigationController:didShowViewController:animated: method as shown below.
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
navigationController.viewControllers = #[viewController];
}
It should reset your navigationControllers controller stack after a new instance has been pushed.
Obviously you can modify this logic to not remove the ones you want to go back to at some point.
The UINavigationController class allows you read and write the array of view controllers it uses to implement its stack however you like. You're not restricted to just pushing and popping the view controllers.
In your case, each time a user moves to the next page of the app you should evaluate whether or not you need to keep the last one around. If you do, you can just push it into the stack, however, if you don't, you should pull it out of the array and then use the setViewControllers:animated: method to set the view controller stack appropriately.
https://developer.apple.com/library/ios/documentation/uikit/reference/UINavigationController_Class/Reference/Reference.html
You should use Page View controller to move back and forth
https://developer.apple.com/library/ios/documentation/uikit/reference/UIPageViewControllerClassReferenceClassRef/UIPageViewControllerClassReference.html
One of the things I don't like about WYSIWYG/visual programming, is when you get to a point where you need to step outside of the box, you're left scratching your head. I'm at such a point right now along my iOS learning curve.
I have a custom UICollectionView thing. In two places (create and edit points), I need to present a list to the user to enable/disable (select/deselect) items from a list. So I go to the storyboard and whip up something like this:
In the past, following tutorials, I would control-drag a link from some control to the NavigationController show in the middle, I would tell it was a modal segue, tune a couple of methods, and get an arrow connecting the two for my efforts.
But in this case, I don't have obvious points to start that action from. I do have a + button. But it needs to do some other things first, and if all is well, then programmatically initiate the open, and somehow get notified of the state when it returns. Same for the individual cells, they might want to configure my table controller, and then open it, and be notified when it closes.
So I'm looking for a recipe of how one does this, what the key methods I should be looking for are. Apple's docs are great for reference, but I find figuring out how to do something from scratch difficult.
After creating a segue in your storyboard, you can initiate a segue any time programmatically by calling
[self performSegueWithIdentifier:#"segueID" sender:person];
Where "segueID" is a string you set for your segue in interface builder in the Identifier field in the identity inspector (right menu panel, 4th tab).
The segue does not need to be created from a control, you can just create one directly from one view controller to another. I usually do this on the right side menu by right-clicking on one view controller object and dragging to another one. This way, it acts as a segue that you can initiate programmatically any time you want.
As for getting notified when you come back to a view controller, (unless I'm misunderstanding your question) you can use either:
(void)viewWillAppear:(BOOL)animated
(void)viewDidAppear:(BOOL)animated
Create a UINavigationController programmatically with a desired view controller set as a root view controller. Here is an example of what you could put in a method invoked when user taps the plus button:
UIViewController *vc = [[UIStoryboard storyboardWithName:#"YourStoryboardName" bundle:nil] instantiateViewControllerWithIdentifier:#"YourViewControllerID"];
UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:vc];
[self presentViewController:nc
animated:YES completion:nil];
To get a state, or information about the selected items you can use Delegation and declare a protocol. Example:
#protocol YourSampleDelegate <NSObject>
- (void)didSelectItem:(NSObject *)item;
#end
Then, your view controller (the one with the plus sign) should implement this protocol:
#interface ViewController : UIViewController<YourSampleDelegate>
...
#end
#implementation ViewController
...
#pragma mark - YourSampleDelegate conformance
- (void)didSelectItem:(NSObject *)item;
{
// Do something with the item.
}
#end
You also have to create a delegate property in a view controller with collection view and set the view controller with a plus as a delegate. There are tons of examples on the Internet. I hope this shows you the right direction.
So, I followed this tutorial: http://enroyed.com/ios/how-to-pass-data-between-ios-tab-bars-using-delegate/
And the most important part of the tutorial:
- (void)viewDidLoad
{
[super viewDidLoad];
SecondViewController *svc = [self.tabBarController.viewControllers objectAtIndex:1];
svc.delegate = self; //important !! otherwise delegation will not work !
}
The problem is that even if I put it in "viewWillLoad", it still forces me to click on my tab before it initializes. How can I specify this before I click on the tab?
Edit
I have a three tab project. I used that tutorial in the link pass data from tab 1 to tab 2. The data passed is a url from a webview on tab 1 to a url on tab 2. The url gets pass when I click a link on the 1st tab.
The data does get passed, but only if I physically click on the 2nd tab first and then click back to the 1st and click on the link.
So, it appears to me that my code above only runs if I physically click on the 2nd tab.
Your problem is that - until you actually go to tab item 2, secondViewController is not fully initialised, and so there is no data to transfer from vc2 to vc1. In particular, secondViewController's view has not yet loaded, so there is no value to be had from it's slider yet, and no slider, so also no IBAction method to call to trigger the delegate method. Indeed, as the data transfer is only triggered on moving the slider in VC2, it should be fairly obvious that until you go to vc2 and move the slider, nothing is going to happen.
The example you link to uses the delegation pattern, which seems a fairly poor way to deal with your problem. The delegation pattern most comfortably fits with the scenario where there is a hierarchical relationship between delegator (owned) and delegatee (owner) ... not always, but most commonly. In a tab bar controller, the relationships are more like kindred child relationships to the tab bar controller itself.
You haven't offered enough detail in your question as to what you want to achieve, but you need to consider this:
When a tabBarController loads, all of it's child viewControllers are initialised but their views are not loaded.
This means that these methods do get called:
//if loading from a xib or in code
- (id) initWithNibName:bundle
//if loading from a storyboard
- (id) initWithCoder:
- (void) awakeFromNib
But the view loading methods (viewDidLoad, viewWillAppear etc) do not get called as the view does not get loaded unless you actually open the relevant tab.
You could solve this by putting an initialised variable into your viewController2 (in one of the init methods) and accessing that variable via property syntax from vc1. But then, you might as well just put an initialised value directly into vc1. You need to think closely about how each vc is dependent on the other, how you can decouple that dependency, and perhaps how to set up an independent data source that both vcs can access as needed. This could be a model class, or NSUserDefaults, or a property in your appDelegate... just a few of the many possible solutions.