I am building an app using container views.
I have been browsing the internet to find examples on how to use it properly but, unfortunately, I found very few examples and, so far, none of them use storyboards the way I intend to do.
Here is a picture of my storyboard:
The code I wrote is this:
(FirstWinViewController.m)
#import "FirstWinViewController.h"
#import "ContainerClassViewController.h"
#interface FirstWinViewController ()
#end
#implementation FirstWinViewController
- (IBAction)clickOne:(id)sender {
ContainerClassViewController *viewContained = [[self.childViewControllers[0] viewControllers] objectAtIndex:0];
[viewContained gotoSegue:1];
}
- (IBAction)clickTwo:(id)sender {
ContainerClassViewController *viewContained = [[self.childViewControllers[0] viewControllers] objectAtIndex:0];
[viewContained gotoSegue:2];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
(ContainerClassViewController.m)
#import "ContainerClassViewController.h"
#interface ContainerClassViewController ()
#end
#implementation ContainerClassViewController
-(void)gotoSegue:(int)umOuDois {
switch (umOuDois) {
case 1:
[self.navigationController popToRootViewControllerAnimated:NO];
[self performSegueWithIdentifier:#"seguePush1" sender:nil];
break;
case 2:
[self.navigationController popToRootViewControllerAnimated:NO];
[self performSegueWithIdentifier:#"seguePush2" sender:nil];
break;
default:
break;
}
}
It is working exactly the way I want.
My questions are:
. Is it right according to Apple's rules?
. Is this approach using more memory, leaving trash or using more resources than the examples that create Container views by code and use AppDelegate to manage them?
Thanks in advance.
I'm confused about both your question and #JoeBlow's answer.
Container views do magic for you starting with iOS 6. You drag a container view onto your form in IB, and then control-drag from the container view to another VC. IB offers to create an embed segue for you. You give it an identifier, and then the ebed segue causes the child view controller to be loaded and installed as a child at the time your parent view controller is loaded.
Your parent's prepareForSegue method fires at the time the child is loaded, and that gives you an opportunity to save a pointer to the child, install the parent as a delegate of the child, or whatever other setup you need to do.
I have a sample project on github that demonstrates this using 2 container views, each of which embed table view controllers. The table view controllers and their parents communicate back and forth using simple protocols I defined.
You can see the project at this link: https://github.com/DuncanMC/test
Related
I'm developing an application which will work based on maps. So once user opens MapViewController then I will load some data every 5 seconds.
I'm using navigation controller(Push view controller).
So every time when user goes to MapViewController viewdidload method calling. I don't want like that.
That's why I'm trying to avoid viewdidload method like tabbarcontroller.
Is there any way to achieve this?
viewDidLoad is getting called because your MapViewController is getting deallocated when you pop it off of the top of your navigation controller. When you recreate the view controller, it's getting allocated all over again, and the view loads again. If you keep a reference to MapViewController in the class containing your navigation controller, then ARC will not deallocate the object, and you can use this reference to push it back onto the stack so viewDidLoad will not get called again.
Edit: Adding code for reference:
MapViewContoller *mapViewController; // declared globally so there's a strong reference.
- (void) openMapViewController {
if (!mapViewController) {
mapViewController = [self.storyboard instantiateViewControllerWithIdentifier: MapViewControllerID];
}
[self.navigationController pushViewController: mapViewController, animated: YES];
}
Try this
-(void)clickForPush{
// declarre viewcontroller e.g 'saveRecipeVC' instance globally in interface
if (!saveRecipeVC) {
saveRecipeVC = [self.storyboard instantiateViewControllerWithIdentifier:SaveRecipeVCID];
}
[self.navigationController pushViewController:saveRecipeVC animated:YES];
}
viewDidLoad is intended to use when,not possible or efficient to configure 100% of an interface in a XIB. Sometimes, a particular property you wish to set on a view isn't available in a XIB. Sometimes, you use auto layout, and you realize that the editor for that is actually worse than writing auto layout code. Sometimes, you need to modify an image before you set it as the background of a button.
If you dont want to do these things make your viewDidLoad empty. Than avoiding. Or
Add code conditionaly into your viewDidLoad.
(void)viewDidLoad {
[super viewDidLoad];
if(condition) {
// put your code here
}
}
How can I access Tab VC from rightmost VC(black)? I tried to use parentViewController from it but got nil.
I'm not a great fan of Containers, they really slow down the storyboard management in XCode.
You should be able to achieve the same result by turning all containers in simple views with a common IBOutlet to some kind of BaseViewController (you should always extend your custom BaseViewController instead of UIViewController in your classes, it gives you more flexibility for common features. Maybe you're already doing it :) ).
Then you can create a custom segue class with a perform method like this
-(void) perform {
BaseViewController* source = (BaseViewController*) self.sourceViewController;
UIViewController* destination = self.destinationViewController
[source.containerView addSubview:destination];
[source addChildViewController:destination];
//Custom code for properly center the destination view in the container.
//I usually use FLKAutolayout for autolayout projects with something like this
//[destination.view alignToView:source.view];
}
Draw a manual segue for the parent view controller to the "contained" view controller an give it a common identifier (something like "containerSegue").
Then in each view container view controller viewDidLoad method add:
[self performSegueWithIdentifier:#"containerSegue" sender:self];
and you should be in the same situation as before.
The only difference is that you can tweak the CustomSegue by adding custom properties and configuration for destination view controller. And, thanks to addChildViewController, your child VC should now have a parentViewController.
And, most of all, your storyboard should be REALLY smoother and faster to load in XCode.
Try this in rootViewController,
rootViewController.h
#interface rootViewController: UIViewController
{
}
+ (UIViewController *) sharedRootViewController;
#end
rootViewController.m
#import "rootViewController.h"
#implementation rootViewController
+ (UIViewController *) sharedRootViewController
{
return (UIViewController *)((UIWindow *)[[[UIApplication sharedApplication] windows] objectAtIndex:0]).rootViewController;
}
- (void) viewDidLoad
{
}
.
.
.
#end
in my app, there is a chatting service. when the user wants to message someone new, he press on "new chat" and a Table View Controller is shown to select from the list of friends. i'm using unwind segues in order to go back to the previous view controller and share the data between the two view controllers (mainly the data will be the friend to have a chat with). the unwind segue works perfectly, however in my app, when the user goes back to the main VC, i fire a new segue in order to go to another VC directly where the user has the chat; and this isn't working. All segues are well connected and i tried NsLog in every corner and it's entering there and even the prepareForSegue: is being accessed. i tried putting an NsTimer, thought there might be some technical conflict, and it didn't work. i change the segue to the chat VC to a modal and now it's giving me this error:
Warning: Attempt to present on whose view is not in the window hierarchy!
i can access this VC in other ways and it's in the hierarchy. my questions are: what could be wrong? does unwind segues alter the windows hierarchies ?
PICTURE:
to present the problem more visually, the main VC i'm talking about is on the bottom left connected to a navigation view controller. when a user presses new chat, the two VC on the top right are presented (one to choose between friends or group, the other to show the friends/ groups). so when a friend is selected for say from the top right VC i should unwind segue to the main VC. as you can see from the main VC there is other segues. non of them can work if i do unwind segue and they do work if i operate normally.
The reason it is not working and is giving you that error is because things arent happening in the order you think they are.
When the unwind happens the view which is visible is not dismissed yet, therefore you are trying to perform a segue on a view which is in fact not in that hierarchy like the error says, take this for example, placing NSLog statements in the final view and then in the unwind method in your main view controller you can see the following:
2013-11-27 14:51:10.848 testUnwindProj[2216:70b] Unwind
2013-11-27 14:51:10.849 testUnwindProj[2216:70b] Will Appear
2013-11-27 14:51:11.361 testUnwindProj[2216:70b] View Did Disappear
Thus the unwind in the main view is getting called, the view will appear (your main view controller), and then your visible view is dismissed. This could be a simple fix:
#interface ViewController () {
BOOL _unwindExecuted;
}
#end
#implementation ViewController
- (void)viewWillAppear:(BOOL)animated
{
NSLog(#"Will Appear");
if (_unwindExecuted) {
_unwindExecuted = NO;
[self performSegueWithIdentifier:#"afterunwind" sender:self];
}
}
- (IBAction)unwind:(UIStoryboardSegue *)segue
{
NSLog(#"Unwind");
_unwindExecuted = YES;
}
Don't use timers or delays to try and anticipate when a view may exist.
Instead, use calls like: - (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
The completion block will let you know when you've arrived back at the main VC. Alternatively, look at the various calls associated with segues so that you know precisely when you can perform operations on the new window.
If all else fails, there's always UIViewController viewDidAppear.
This is a common problem for the view controller that is handling the unwinding, because during unwind, that view controller is likely to not be in the window hierarchy.
To solve, I added a property segueIdentifierToUnwindTo to coordinate the unwinding.
This is similar to the answer by JAManfredi, but extending it to be able to segue to any view controllers.
#interface FirstViewController ()
// Use this to coordinate unwind
#property (nonatomic, strong) NSString *segueIdentifierToUnwindTo;
#end
#implementation FirstViewController
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// Handle unwind
if (_segueIdentifierToUnwindTo) {
[self performSegueWithIdentifier:_segueIdentifierToUnwindTo sender:self];
self.segueIdentifierToUnwindTo = nil; // reset
return;
}
// Any other code..
}
// Example of an unwind segue "gotoLogin"
- (IBAction)gotoLogin:(UIStoryboardSegue*)sender {
// Don't perform segue directly here, because in unwind, this view is not in window hierarchy yet!
// [self performSegueWithIdentifier:#"Login" sender:self];
self.segueIdentifierToUnwindTo = #"Login";
}
#end
I also shared how I use unwind segues with a FirstViewController on my blog: http://samwize.com/2015/04/23/guide-to-using-unwind-segues/
okay solved this, the problem is that the view wasn't there yet and i had to delay the process by this:
[self performSelector:#selector(fireNewConv) withObject:nil afterDelay:1];
However, something interesting is that i tried delaying by NSTimer but it didn't work.
I am working on view A (createExerciseViewController) that adds view B (createRoutinePopupViewController) after clicking a UIButton.
This part works fine and the view is added fine.
Then inside view B (createRoutinePopupViewController) I have another UIButton. When I click this UIButton then the app crashes and all i get as an error is (lldb) and the NSLog is not executed.
but then sometimes and only sometimes it all gets executed fine after several crashes...
I am quite new to the iOS dev world and I have no idea what I could be doing wrong.
All UIButton method are strong
Does anyone know why this could be happening?
I think the issue could be in how i am inserting the subview and handling the whole subview??
A ---- createExerciseViewController.m
#import "createExerciseViewController.h"
#import "createExercisePopupViewController.h"
#import "createRoutinePopupViewController.h"
// ....more code
- (IBAction)createRoutine:(id)sender {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
[self.view addSubview:[[storyboard instantiateViewControllerWithIdentifier:#"createRoutinePopupView"] view]];
}
this is UIViewController
B ---- createRoutinePopupViewController.m
#import "createRoutinePopupViewController.h"
#import "createExerciseViewController.h"
#import "User.h"
#import "Routine.h"
- (IBAction)createRoutine:(UIButton *)sender {
NSLog(#"Please dont crash");
}
You shouldn't be creating view controllers just to add their views to another view controller's view willy-nilly. You need to tell the system that you're moving views from one controller to another, so that it can do its housekeeping. If you don't do this, one view controller ends up owning a view that's being presented by another view controller, so events and touches etc get confused. This may be what's causing the crash.
iOS now provides a 'container view controller' mechanism to manage this situation, whereby you tell the system that you're moving a view from one controller to another.
From Apple's doc:
The goal of implementing a container is to be able to add another view
controller’s view (and associated view hierarchy) as a subtree in your
container’s view hierarchy. The child remains responsible for its own
view hierarchy, save for where the container decides to place it
onscreen. When you add the child’s view, you need to ensure that
events continue to be distributed to both view controllers. You do
this by explicitly associating the new view controller as a child of
the container.
In practice, it's simpler than it sounds. Try something like this in createExerciseViewController.m:
- (IBAction)createRoutine:(id)sender {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
CreateRoutinePopupViewController* popupController = [storyboard instantiateViewControllerWithIdentifier:#"createRoutinePopupView"];
//Tell the operating system the CreateRoutine view controller
//is becoming a child:
[self addChildViewController:popupController];
//add the target frame to self's view:
[self.view addSubview:popupController.view];
//Tell the operating system the view controller has moved:
[popupController didMoveToParentViewController:self];
}
Try this:
CreateRoutinePopupViewController *popUp = [[storyboard instantiateViewControllerWithIdentifier:#"createRoutinePopupView"];
[self presentModalViewController:popUp Animated:YES];
I am making my first iOS application, it is for the iPad. It is a memorization game. It has cover page with a couple of options and depending on the option you choose it sends you different page/view. Throughout the application, the user will be traveling through different pages/views. The entire interface for the application will be custom made, so i want have the navigation bars or anything. I am using xCode 3.2.5. I have created the views in the interface builder. And I have attached the cover page to the app, so after the splash page it appears.
How do I go about switching between views?
Thanks for any help you can give me.
Edit 1:
Here is some code that I think is pertinent
This is the AppDelegate.m file, I left out the methods I did not edit
#synthesize coverController=_coverController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
cover *aCoverController = [[cover alloc] initWithNibName:#"cover" bundle:nil];
self.coverController = aCoverController;
// Or, instead of the line above:
// [self setcover:aCoverController];
[aCoverController release];
self.window.rootViewController = self.coverController;
[self.window makeKeyAndVisible];
return YES;
}
- (void)dealloc {
[managedObjectContext_ release];
[managedObjectModel_ release];
[persistentStoreCoordinator_ release];
[_coverController release];
[window release];
[super dealloc];
}
Ok. 1stly can you be a little more clearer about what you want.
From what I got was, you are not looking to navigate in/out of controllers, you just have few views prepared for your RootViewController, and then you want to switch between them.
Navigation controller is used when you have a sequential flow of views, as in moving from view1 'leads to' view2, and so on. eg- a contactsBook-->contactDetails-->editContact--> so on ..
But it feels, in your case, the views/pages are separate and have no connection whatsoever, so there wont be any sequential flow, but a random flow of say view1-->view5-->view2--> ..
If that is the case, if you have already build the views, you just need to connect each of them with their parentController(coverController in your case).
Simplest way would be - lets say you have 3 views, view1 view2 view3, each having 1 or more buttons to switch b/w views.
1 way would be to have a reference of the coverController, in each of the views. There are more elegant methods possible, but this 1 will be the easiest to understand and implement.
So, in view1.h(add these) :
import "cover.h"
#class cover;
#interface view1 : UIView {
cover *coverController;
}
#property(nonatomic, assign)cover *coverController;
#end
And in cover.h, add
import "view1.h"
#class view1;
#interface cover : UIViewController{
IBOutlet view1 *firstView;
}
#property(nonatomic, retain) IBOutlet view1 *firstView;
#end
Finally in cover.m, add
#implementation cover
#synthesize view1;
and in 'viewDidLoad' method in cover.m, add 2 lines
self.view1.frame = CGRectMake(0,0,768,1024); //set whatever frame you want
self.view1.coverController = self; //concept of reference-paring
And done.
in the view1ButtonPressed method of view1 -
-(IBAction)view1ButtonPressed{
// remove the current view from the superview
[self removeFromSuperView];
//go to superView, to load anotherview
[coverController view1ButtonWasPressed];
}
in cover.m :
-(void)view1ButtonWasPressed{
//after doing the same process for view2
[self.view addSubview:view2];
}
If you have made the correct connections, in you nib files, you ll achieve what you set out to do.
Concept is simple, what we are doing is - on click on the button, we remove the current view from superview, go to the super view itself(which is the controller's view only), and add as a subview some other view of our choice.
There is only 1 controller, and many views, and we are switching in b/w those views.
You should use a navigation controller, it's the simplest way of structuring an app with multiple views. There's nothing that says you have to show the navigation bar and you can create custom buttons which push and pop views on and off of the stack.
I've been playing with different ways of doing this and it's just not worth the effort. I strongly recommend the navigation controller.
This tutorial helped me get my head round it, but try googling to find what works best for you.