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.
Related
I want to design an app with UI like this:
At bottom, I know it is UITabbarController. In each tab, it has many view controllers like BEAT, TOP, FUN... Each tab has different view controllers.
When scroll horizontal, can change from BEAT to TOP to FUN...
How can I design like that? What view controller should I use? It seems like UIPageController in UITabController, but with UIPageController, I don't know how to replace dots at bottom (.) by BEAT, TOP, FUN... at top.
Thanks for your help.
Alternative solution is to add UISwipeGesture(Left/Right) on view and upon swipe action you can push or pop view controllers.
In ViewController.h
#property (retain, nonatomic) UISwipeGestureRecognizer * leftSwipe;
#property (retain, nonatomic) UISwipeGestureRecognizer * rightSwipe;
In ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupSwipeGestures];
}
-(void)setupSwipeGestures
{
_leftSwipe=[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(next)];
[_leftSwipe setDirection:UISwipeGestureRecognizerDirectionLeft];
_rightSwipe =[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(previous)];
[_rightSwipe setDirection:UISwipeGestureRecognizerDirectionRight];
[self.view addGestureRecognizer:_leftSwipe];
[self.view addGestureRecognizer:_rightSwipe];
}
- (void) previous
{
// perform pop to get previous viewController
// i.e [self.navigationController popViewControllerAnimated:YES];
}
- (void) next
{
// perform push to get next viewController
// i.e [self.navigationController pushViewController:viewController animated:YES];
}
If you want to use above code in every Controller, then you may define your own viewController and paste above code in it, after that inherit all of your viewControllers where you need above functionality.
You can use XLPagerTabStrip library in each tab of your tab controller. Awesome library, even gives you the swiping feature similar to android which can enable you to swipe through individual page sections as well as keeping tab property.
I am relatively new to Xcode and have tried to find the answer by searching, without luck.
My app has 5 View Controllers, V1 through V5, which are embedded in one Tab Bar Controller. Each View Controller has a segue to one and the same Setup Menu View Controller. The Menu changes some labels on the View Controllers. I use a delegate to make sure that the View Controller that calls the Menu gets updated with the new settings when you leave the Menu. However, this allows me to modify only the labels on the View Controller that called the Menu Controller, not on the 4 other ones.
I work form a Story Board. Is there a simple way to set the UILabels on V2, V3, V4 and V5 from V1 (and vice versa), or even better, set the labels on V1 through V5 from the Menu View Controller (which is not embedded in the Tab Bar Controller)?
I have seen something that could help here, but this seems rather complicated for what I want. The label changes I need are quite simple and are all predefined. Is there a method that is called every time you switch tabs in a tabbed application? Similar to ViewDidLoad?
This sounds like a good time for NSNotificationCenter. You are going to have your MenuViewController generate a notification with the new data that should be updated in your other view controllers:
// User has updated Menu values
[[NSNotificationCenter defaultCenter] postNotificationName:#"MenuDataDidChangeStuffForLabels" object:self userInfo:#{#"newLabelValue" : labelText}];
In your V1, V2, etc. you can add subscribe to these notifications using this code in your viewDidLoad method:
- (void)viewDidLoad {
[super viewDidLoad];
// Subscribe to NSNotifications named "MenuDataDidChangeStuffForLabels"
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateLabelText) name:#"MenuDataDidChangeStuffForLabels" object:nil];
}
Any object that subscribes using that code will call the updateLabelText method anytime a notification with that name is posted by the MenuViewController. From that method you can get the new label value and assign it to your label.
- (void)updateLabelText:(NSNotification *)notification {
NSString *newText = notification.userInfo[#"newLabelValue"];
myLabel.text = newText;
}
What I would do is subclass the tab bar controller and set that as the delegate for the menu view controller. From there, you can get updated when the labels are supposed to change and then communicate with the 5 tabs and update the labels.
Alternatively, you could use NSNotifications to let all the 5 view controllers know when settings change.
Lastly, you could add the menu settings to a singleton and have all of the view controllers observe the various properties that can change.
The label changes I need are quite simple and are all predefined. Is there a method that is called every time you switch tabs in a tabbed application? Similar to ViewDidLoad?
Regarding this question, the methods you're looking for are viewWillAppear: and viewDidAppear.
Here is a very simple solution if your workflow is also simple. This method changes all the labels from the different ViewControllers directly from what you call the Menu ViewController.
Let's say you have the following situation :
The blue ViewController is of the FirstViewController class. The green ViewController is of the SecondViewController class. The labels on each of those are referenced by the properties firstVCLabel and secondVCLabel (on the appropriate class' header file). Both these ViewControllers have a "Modal" button which simply segues modally on touch up inside.
So when you clic on any of these two buttons, the orange ViewController (of ModalViewController class) is presented. This ViewController has two buttons, "Change Label" and "Back", which are linked to touch up inside IBActions called changeLabel: and back:.
Here is the code for the ModalViewController :
#import "ModalViewController.h"
#import "FirstViewController.h"
#import "SecondViewController.h"
#interface ModalViewController ()
#end
#implementation ModalViewController
// Action linked to the "Change Label" button
- (IBAction)changeLabel:(id)sender {
// Access the presenting ViewController, which is directly the TabBarController in this particular case
// The cast is simply to get rid of the warning
UITabBarController *tabBarController = (UITabBarController*)self.presentingViewController;
// Go through all the ViewControllers presented by the TabBarController
for (UIViewController *viewController in tabBarController.viewControllers) {
// You can handle each ViewController separately by looking at its class
if ([viewController isKindOfClass:[FirstViewController class]]) {
// Cast the ViewController to access its properties
FirstViewController *firstVC = (FirstViewController*)viewController;
// Update the label
firstVC.firstVCLabel.text = #"Updated first VC label from Modal";
} else if ([viewController isKindOfClass:[SecondViewController class]]) {
SecondViewController *secondVC = (SecondViewController*)viewController;
secondVC.secondVCLabel.text = #"Updated second VC label from Modal";
}
}
}
// Action linked to the "Back" button
- (IBAction)back:(id)sender {
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
For the sake of completeness, here are FirstViewController.h :
#import <UIKit/UIKit.h>
#interface FirstViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *firstVCLabel;
#end
And SecondViewController.h :
#import <UIKit/UIKit.h>
#interface SecondViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *secondVCLabel;
#end
There is no relevant code in the implementation of these classes.
Thanks a lot guys, I am impressed by your quick responses. In this particular case, viewWillAppear does the trick:
- (void)viewWillAppear:(BOOL)animated
{ [self AdaptLabels];
NSLog(#"View will appear.");
}
Every time a new tab is chosen, it updates the labels in the new View, according to a global variable set by the Menu, just before they appear. Very quick and clean. Thanks to all of you!
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
I need to pop up a quick dialog for the user to select one option in a UITableView from a list of roughly 2-5 items. Dialog will be modal and only take up about 1/2 of screen. I go back and forth between how to handle this. Should I subclass UIView and make it a UITableViewDelegate & DataSource?
I'd also prefer to lay out this view in IB. So to display I'd do something like this from my view controller (assume I have a property in my view controller for DialogView *myDialog;)
NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:#"DialogView" owner:myDialog options:nil];
myDialog = [nibViews objectAtIndex:0];
[self.view addSubview:myDialog];
problem is i'm trying to pass owner:myDialog which is nil as it hasn't been instantiated...i could pass owner:self but that would make my view controller the File's Owner and that's not how that dialog view is wired in IB.
So that leads me to think this dialog wants to be another full blown UIViewController... But, from all I've read you should only have ONE UIViewController per screen so this confuses me because I could benefit from viewDidLoad, etc. that come along with view controllers...
Can someone please straighten this out for me?
There is no such thing as a view controller being on the screen; its view is on the screen. With that said, you can present as many views as you want on the screen at once.
I would create a new view and view controller. You would not make a UIView be a UITableViewDelegate, you make a UIViewController be a UITableViewDelegate. But instead of doing that manually, instead make your new view controller a subclass of UITableViewController, if you're using iPhone OS 3.x+. You can then present this view controller modally.
You probably want to give the user a chance to cancel out of the selection. A good way to do that is to wrap your new dialog view controller in a UINavigationController and then put a "Cancel" button in the nav bar. Then use the delegate pattern to inform the parent view controller that the user has made their choice so you can pop the stack.
Here's what the code will look like inside your parent view controller, when you want to present this option dialog:
- (void)showOptionView
{
OptionViewController* optionViewController = [[OptionViewController alloc] initWithNibName:#"OptionView" bundle:nil];
optionViewController.delegate = self;
UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:optionViewController];
[self.navigationController presentModalViewController:navController animated:YES];
[navController release];
[optionViewController release];
}
Your OptionViewController .h will look like this:
#protocol OptionViewControllerDelegate;
#interface OptionViewController : UITableViewController
{
id<OptionViewControllerDelegate> delegate;
}
#property (nonatomic, assign) id<OptionViewControllerDelegate> delegate;
#end
#protocol OptionViewControllerDelegate <NSObject>
- (void)OptionViewController:(OptionViewController*)OptionViewController didFinishWithSelection:(NSString*)selection;
// or maybe
- (void)OptionViewController:(OptionViewController*)OptionViewController didFinishWithSelection:(NSUInteger)selection;
// etc.
#end
Your OptionViewController.m will have something like this:
- (void)madeSelection:(NSUInteger)selection
{
[delegate OptionViewController:self didFinishWithSelection:selection];
}
Which has a matching method back in your original view controller like:
- (void)OptionViewController:(OptionViewController*)OptionViewController didFinishWithSelection:(NSUInteger)selection
{
// Do something with selection here
[self.navigationController dismissModalViewControllerAnimated:YES];
}
There are plenty of examples throughout Apple's sample source code that follow this general pattern.
I am trying to get a popup effect and want to design the popup view in another view controller so i can use the xib to do it.
When i used the presentViewController or pushViewController and set the background to transparent, i end up seeing the Window's background color.
I tried this code to add subview to the navigation controller's view so that i can have the Info view cover the entire screen with a transparent background. I also have tab bar to cover up as well.
InfoVC *vc = [[InfoVC alloc] initWithNibName:#"InfoVC" bundle:nil];
[self.navigationController.view addSubview:vc.view];
My problem is inside my InfoVC when i try to dismiss it, the app will crash with some EXC_BAD_ACCESS message:
[self.view removeFromSuperview];
EDIT:
I found a way to stop it crashing but setting the InfoVC as a property in the MainVC. I think the reason for crash is when i call "self.view" in the action inside the InfoVC, it doesn't know that self is the InfoVC inside MainVC.
InfoVC *vc = [[InfoVC alloc] initWithNibName:#"InfoVC" bundle:nil];
[self.navigationController.view addSubview:vc.view];
No no no no. Never never do that.
There is an elaborate dance that you must traverse in order to put a view controller's view inside another view controller's view (or remove it afterwards) if it doesn't come with built-in facilities for doing this (the way a UISplitViewController does, or the way a navigation controller manages the views of the view controllers that are pushed and popped within it).
Read up on customer container controllers. One of the examples from my book is here:
https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/ch19p556containerController/p476containerController/ViewController.m
Shouldn't you be using the following to remove the view from its superview?
[vc.view removeFromSuperview];
You can never have a UIView remove it's subviews, the subviews themselves must remove themselves from it's superview. You can easily loop through subviews and have them removed like so
for (UIView *view in vc.view.subviews) {
[view removeFromSuperview];
}
Docs for reference:
http://developer.apple.com/library/ios/#documentation/uikit/reference/uiview_class/uiview/uiview.html
After a "modally" presented view controller has appeared the views under the now presented view controller will be removed; this saves memory, and eases rendering. In your case, though, you also end up seeing the window behind the "modally" presented view.
The natural, and seemingly logical, next step is to simply take one view controller's view and cram it into another. However, as you have discovered, this is problematic. With the newly inserted view safely retained by the view hierarchy it is safe, but the new view controller is not so lucky, it is quickly deallocated. So when this new view tries to contact its controller you will get an EXC_BAD_ACCESS and crash. One workaround, again as you have found, is to simply have the original view controller keep a strong reference to the new view controller. And this can work... badly. There's still a good chance you will get an UIViewControllerHierarchyInconsistencyException.
Of course if you simply want to add a small view you create in IB you don't need to use a view controller as the "File's Owner" and there are many examples of creating an instance of a view from a xib file.
The more interesting question here is, "How would/does apple do it?" Apple consistently says that a view controller is the correct controller for an encapsulated unit of work. For example, their TWTweetComposeViewController, you present it, and it seems to float. How?
The first way of accomplishing this that comes to my mind is to have a clear background that isn't clear. That is, create an image of the screen before the presented view controller appears and set that as the background before the presenting view is removed. So for example(Explanation to follow):
QuickSheetViewController.xib
QuickSheetViewController.h
#import <UIKit/UIKit.h>
#interface QuickSheetViewController : UIViewController
- (IBAction)dismissButtonPressed:(id)sender;
#end
QuickSheetViewController.m
#import "QuickSheetViewController.h"
#import <QuartzCore/QuartzCore.h>
#implementation QuickSheetViewController {
UIImage *_backgroundImage;
}
-(void)renderAndSaveBackgroundImageFromVC:(UIViewController *)vc{
UIGraphicsBeginImageContext(vc.view.bounds.size);
[vc.view.layer renderInContext:UIGraphicsGetCurrentContext()];
_backgroundImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
// save an image of the current view, and set our background to clear so we can see the slide-in.
[self renderAndSaveBackgroundImageFromVC:self.presentingViewController];
self.view.backgroundColor = [UIColor clearColor];
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
// Time to use our saved background image.
self.view.backgroundColor = [UIColor colorWithPatternImage:_backgroundImage];
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
// Set our background to clear so we can see the slide-out.
self.view.backgroundColor = [UIColor clearColor];
}
- (IBAction)dismissButtonPressed:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
The majority of this example hinges upon the renderAndSaveBackgroundImageFromVC: method. In which, we create a graphics context render the view we are about to cover into it, and then create a UIImage to later (in viewDidAppear) use as a background.
Now simply use it like:
QuickSheetViewController *newVC = [[QuickSheetViewController alloc] initWithNibName:nil bundle:nil];
[self presentViewController:newVC animated:YES completion:nil];
You will see through the background just long enough for the animation to happen, then we use our saved image to hide the removal of the presenting view.