How to setup Container View so as to swap two children - ios

I am having what seems like a typical Container View problem in iOS. I have a ViewController with two subviews: a UISegmentedControl and a Container View. Now having placed my Container View, in the storyboard, I am not sure how to proceed. Naturally I thought my next step was to subclass UIContainerView to do all the stuff that I read in the iOS Documentation. But there is no such class as UIContainerView. So now, beyond what I was able to place in the storyboard, I am stuck. Hoping someone can help me I will posit what seems like a simple scenario.
Imagine:
One ViewController with two buttons (Cat, Dog) and a ContainerView.
When user clicks on catButton, then the ContainerView should show the CatViewController (and do similarly for dogButton)
Image that already I have the storyboard setup.
For simplicity, let CatViewController contain a single UILabel with the word CAT (and similarly for DogViewController).
also, in the storyboard, I have already created CatViewController and DogViewController as two stand-alone, unreachable, View Controllers.
So at this point, how do I proceed? Since I cannot subclass such a class as UIContainerView, what do I do?
I believe this scenario is simple enough for someone to provide an example, but if you deem it too complicated, please provide an example to yet a simpler scenario. I just want to see how a simple one is done.
P.S. I have already taken a tour here on StackOverflow, such as:
Swapping child views in a container view
and I have already read the docs at https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html#//apple_ref/doc/uid/TP40007457-CH18-SW6

I think is better use an UISegmentedControl instead two UIButtons.
The container view subviews (_vwContainer.subviews) contains initially the CatViewController's view, automatically instantiated.
// ViewController.m
#import "ViewController.h"
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UIView *vwContainer;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_vwContainer.clipsToBounds = YES;
}
- (IBAction)onSegmentValueChanged:(UISegmentedControl *)sender {
NSLog(#"Value changed to: %zd",sender.selectedSegmentIndex);
NSLog(#"BEFORE: self.childViewControllers: %#",self.childViewControllers);
NSLog(#"BEFORE: _vwContainer.subviews: %#",_vwContainer.subviews);
// set oldVC & newVC
UIViewController *oldVC = self.childViewControllers.firstObject;
NSString *strIdNewVC;
switch (sender.selectedSegmentIndex) {
case 0: strIdNewVC = #"catVC"; break;
default: strIdNewVC = #"dogVC";
}
UIViewController *newVC = [self.storyboard instantiateViewControllerWithIdentifier:strIdNewVC];
//
[oldVC willMoveToParentViewController:nil];
[self addChildViewController:newVC];
// Prepare animation transition, for example left to right
newVC.view.frame = oldVC.view.frame;
CGPoint pntEnd = oldVC.view.center;
CGPoint pntInit = pntEnd;
pntInit.x += oldVC.view.frame.size.width;
newVC.view.center = pntInit;
[self transitionFromViewController:oldVC toViewController:newVC
duration:0.25 options:0
animations:^{
newVC.view.center = pntEnd;
} completion:^(BOOL finished) {
[oldVC removeFromParentViewController];
[newVC didMoveToParentViewController:self];
NSLog(#"AFTER: self.childViewControllers: %#",self.childViewControllers);
NSLog(#"AFTER: _vwContainer.subviews: %#",_vwContainer.subviews);
}];
}
#end

Related

Initializing and switching between two UICollectionViews

So I have two UICollectionViews that have different types of layouts, different types of cells, and different data in the cells, hence I have decided to make two separate views rather than dealing with keeping track of all of their data.
I made almost all the UI in the Interface editor, so I have .xib files for both my UICollectionViews (lets call them NewsCollectionView and ExploreCollectionView) and all of my cells. The two .xib files are connected to their controllers through the FilesOwner property in the Interface Builder.
I also have a main view (lets call it HomeViewController) with a tabBar which I want to use to switch between my two UICollectionViews. My first question is simple, how do I initialize one of my UICollectionViews from the NIB?
Here is how I imagined it (in my HomeViewController)
#property (strong, nonatomic) NewsCollectionView *newsCollectionView;
#property (strong, nonatomic) ExploreCollectionView *exploreCollectionView;
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *arrayOfViews = [[NSBundle mainBundle] loadNibNamed:#"NewsCollectionView" owner:self options:nil];
self.newsCollectionView = [arrayOfViews objectAtIndex:0];
[self.view addSubview:self.newsCollectionView];
The result of that code is a message stating "Can't add self as subview".
My second question is about switching between the two collectionViews. I imagine it will go something like this:
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
if(item == newsEventsTab)
{
[UIView transitionFromView:self.exploreCollectionView
toView:self.newsCollectionView
duration:0.25
options:UIViewAnimationTransitionFlipFromRight
completion:nil];
}
if(item == exploreTab)
{
[UIView transitionFromView:self.newsCollectionView
toView:self.exploreCollectionView
duration:0.25
options:UIViewAnimationTransitionFlipFromRight
completion:nil];
}
}
However I have no idea if that would work because I can't even get the views to show up yet. Please help me out, thanks!!
I ended up accomplishing my task by creating a containerView inside my HomeViewController, and connecting it via IBOutlet.
In order to set up my collection views I did this:
self.newsCollectionView = [[NewsCollectionView alloc] initWithNibName:#"NewsCollectionView" bundle:nil];
self.newsCollectionView.view.frame = self.collectionContainer.bounds;
self.exploreCollectionView = [[ExploreCollectionView alloc] initWithNibName:#"ExploreCollectionView" bundle:nil];
self.exploreCollectionView.view.frame = self.collectionContainer.bounds;
[self addChildViewController:self.newsCollectionView];
[self addChildViewController:self.exploreCollectionView];
[self.collectionContainer addSubview:self.newsCollectionView.view];
[self.newsCollectionView didMoveToParentViewController:self];
[self.exploreCollectionView didMoveToParentViewController:self];
The view transition is as I described in my original post, except you have to reference the actual view instead of the controller (so self.newsCollectionView.view instead of just self.newsCollectionView).
Works like a charm.

xcode storyboard Container View - How do I access the viewcontroller

I'm trying to use storyboard and get things working properly. I've added a a Container View to one of my existing views. When I try to add a reference to this in my view controller .h file (ctrl-drag), I get a IBOutlet UIView *containerView. How do I get a reference to the container view's view controller instead? I need the container view controller so I can set it's delegate to my view's controller so they can "talk" to each other.
I have my story board setup as:
And its referenced in my .h file as:
Notice in the .h that is is a UIView, not my InstallViewController for the view. How do I add a reference to the view controller? I need to be able to set its delegate.
There is another solution by specifying an identifier for the embed segue(s) and retrieve the corresponding view controllers in method prepareForSegue:
The advantage of this way is that you needn't rely on a specific order in which your child view controllers are added due to the fact that each child view controller is embedded via an unique segue identifier.
Update 2013-01-17 - Example
- (void) prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
// -- Master View Controller
if ([segue.identifier isEqualToString:c_SegueIdEmbedMasterVC])
{
self.masterViewController = segue.destinationViewController;
// ...
}
// -- Detail View Controller
else if ([segue.identifier isEqualToString:c_SegueIdEmbedDetailVC])
{
self.detailViewController = segue.destinationViewController;
// ...
}
}
c_SegueIdEmbedMasterVC & c_SegueIdEmbedDetailVC are constants with the corresponding ID of the segue IDs defined in the storyboard.
When you add a container view the xcode calls the UIViewController method addChildViewController:
In your case, you can get the container ViewController looking for it on the SplashViewController's list of childViewControllers, something like this:
for (UIViewController *childViewController in [self childViewControllers])
{
if ([childViewController isKindOfClass:[InstallViewController class]])
{
//found container view controller
InstallViewController *installViewController = (InstallViewController *)childViewController;
//do something with your container view viewcontroller
break;
}
}
I had the same doubt yesterday :)
The answer of Vitor Franchi is correct but could be more performant and convenient. Especially when accessing the child view controller several times.
Create a readonly property
#interface MyViewController ()
#property (nonatomic, weak, readonly) InstallViewController *cachedInstallViewController;
#end
Then create a convenient getter method
- (InstallViewController *)installViewController
{
if (_cachedInstallViewController) return _cachedInstallViewController;
__block InstallViewController *blockInstallViewController = nil;
NSArray *childViewControllers = self.childViewControllers;
[childViewControllers enumerateObjectsUsingBlock:^(id childViewController, NSUInteger idx, BOOL *stop) {
if ([childViewController isMemberOfClass:InstallViewController.class])
{
blockInstallViewController = childViewController;
*stop = YES;
}
}];
_cachedInstallViewController = blockInstallViewController;
return _cachedInstallViewController;
}
From now on access the child view controller that way
[self.installViewController doSomething];
UIView* viewInsideOfContainer = installerView.subviews[0];
Will give you the UIView inside of the UIViewController that your controller UIView references. You can cast the subview to any type that inherits from UIView.
If the nib is loaded it will call addChildViewController as part of the initialisation process
so a performant solution could be also to overwrite
- (void)addChildViewController:(UIViewController *)childController
there you can catch your childController e.g. by comparing its Class and assign it to a property / ivar
-(void)addChildViewController:(UIViewController *)childController
{
[super addChildViewController:childController];
if([childController isKindOfClass:[InstallViewController class]])
{
self.installViewController = (InstallViewController *)childController;
}
}
This will save your from iterating trough the childViewControllers.

Showing a "what's this?" message when a UITableViewController is blank

My app contains several UITableViewControllers which don't necessarily have content in all circumstances. For example, a Drafts screen is empty if the user doesn't have any drafts.
In cases like that, I'd like to show a brief message explaining what the screen is for, something like this screen from the built-in Photos app:
What is the best way to get a descriptive view on screen? I can't subclass UIViewController directly, as I'm depending on some iOS 6 functionality that's tied specifically to UITableViewController, so as far as I can tell, I have to display this view inside a UITableView. Any suggestions?
Subclass UITableViewController, and then in -viewDidAppear: or some other similarly appropriate place, check if the number of cells in the table will be zero. If so, add this overlay; if not, ensure the overlay is removed. Sample code below:
#interface MyTableViewController : UITableViewController
...
#property (nonatomic, weak) UIImageView *informativeOverlayImageView;
...
#end
#implementation MyTableViewController
...
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Just for an example - you'll have your own logic for determining if there will be zero rows.
if (self.myDataModel.items.count == 0 &&
!self.informativeOverlayImageView.superview)
{
if (!self.informativeOverlayImageView)
{
self.informativeOverlayImageView = [[UIImageView alloc] initwithImage:[UIImage imageNamed:#"someImageName"]];
[self.informativeOverlayImageView sizeToFit];
}
[self.view addSubview:self.informativeOverlayImageView];
}
else if (self.myDataModel.items.count > 0 &&
self.informativeOverlayImageView.superview)
{
[self.informativeOverlayImageView removeFromSuperview];
[self.tableView reloadData]; // Add animations to taste.
}
}
...
#end
Hope this helps!

Update UIViewController after Dismissing Modal Segue

I am currently designing the structure for my first iPhone game and ran into a problem. Currently, I have a 'MenuViewController' that allows you to pick the level to play and a 'LevelViewController' where the level is played.
A UIButton on the 'MenuViewController' triggers a modal segue to the 'LevelViewController'.
A UIButton on the 'LevelViewController' triggers the following method to return to the 'MenuViewController':
-(IBAction)back:(id)sender //complete
{
[self dismissModalViewControllerAnimated:YES];
}
The problem is, I have a UILabel on the menu page that prints the number of total points a player has. Whenever I go back to the menu from the level, I want this label to automatically update. Currently, the label is defined programmatically in the 'MenuViewController':
-(void)viewDidLoad {
[super viewDidLoad];
CGRect pointsFrame = CGRectMake(100,45,120,20);
UILabel *pointsLabel = [[UILabel alloc] initWithFrame:pointsFrame];
[pointsLabel setText:[NSString stringWithFormat:#"Points: %i", self.playerPoints]];
[self.pointsLabel setTag:-100]; //pointsLabel tag is -100 for id purposes
}
self.playerPoints is an integer property of MenuViewController
Is there a way I could update the label? Thanks ahead of time!
This is a perfect case for delegation. When the LevelViewController is done, it needs to fire off a delegate method which is handled in the MenuViewController. This delegate method should dismiss the modal VC and then do whatever else you need it to do. The presenting VC should normally handled the dismissal of modal views it presents.
Here is a basic example of how to implement this:
LevelViewController.h (Above the Interface declaration):
#protocol LevelViewControllerDelegate
-(void)finishedDoingMyThing:(NSString *)labelString;
#end
Same file inside ivar section:
__unsafe_unretained id <LevelViewControllerDelegate> _delegate;
Same File below ivar section:
#property (nonatomic, assign) id <LevelViewControllerDelegate> delegate;
In LevelViewController.m file:
#synthesize delegate = _delegate;
Now in the MenuViewController.h, #import "LevelViewController.h" and declare yourself as a delegate for the LevelViewControllerDelegate:
#interface MenuViewController : UIViewController <LevelViewControllerDelegate>
Now inside MenuViewController.m implement the delegate method:
-(void)finishedDoingMyThing:(NSString *)labelString {
[self dismissModalViewControllerAnimated:YES];
self.pointsLabel.text = labelString;
}
And then make sure to set yourself as the delegate for the LevelViewController before presenting the modal VC:
lvc.delegate = self; // Or whatever you have called your instance of LevelViewController
Lastly, when you are done with what you need to do inside the LevelViewController just call this:
[_delegate finishedDoingMyThing:#"MyStringToPassBack"];
If this doesn't make sense, holler and I can try to help you understand.
Make a property self.pointsLabel that points to the UILabel, then you can just call something like [self.pointsLabel setText:[NSString stringWithFormat:#"Points: %i", self.playerPoints]]; to update the label with the new score
In your modal view header file, add the property:
#property (nonatomic,assign) BOOL updated;
Then in your main view controller, use didViewAppear with something like:
-(void)viewDidAppear:(BOOL)animated{
if (modalView.updated == YES) {
// Do stuff
modalView.updated = NO;
}
}
Where "modalView" is the name of that UIViewController that you probably alloc/init there.
Add more properties if you want to pass more info, like what level the user picked.

iOS - passing Sender (button) name to addSubview

I have a main view with 3 buttons. Clicking on any of the buttons adds a SubView.
The buttons have different titles and are all linked to IBAction "switchView"
The "switchView" code is below.
- (IBAction)switchView:(id)sender{
secondView *myViewController = [[secondView alloc] initWithNibName:#"secondView" bundle:nil];
[self.view addSubview:myViewController.view];
}
The "secondView" loads up correctly and everything works well.
The problem is I want to be able to know which button was the Sender.
I don't want to create 3 subviews, one for each button. The code and XIB would be absolutely the same>
The only difference would be a variable that I would like to set up in the second view (viewDidLoad method) depending on who is the Sender (which button was clicked)
Is this possible? Or I would need to create 3 subViews - one for each button?
Your help is greatly appreciated!
You can identify different buttons with the tag property.
e.g. with your method:
-(IBAction)switchView:(id)sender {
UIButton *button = (UIButton*)sender;
if (button.tag == 1) {
//TODO: Code here...
} else if (button.tag == 2) {
//TODO: Code here...
} else {
//TODO: Code here...
}
}
The tag property can be set via the InterfaceBuilder.
Hope this helps.
I think you can solve in 2 ways:
Create a property like:
#property (nonatomic, strong) IBOutlet UIButton *button1, *button2, *button3;
in your viewcontroller and link the buttons to them as referencing outlet on the XIB.
Give a different tag to each button on your xib and ask for the tag of the sender with UIButton *b=(UIButton*)sender; b.tag; like Markus posted in detail.
Solving my problem it all came down to transferring data between the mainView and subView.
In my mainView.h I declared an NSString and its #property
...
NSString *btnPressed;
}
#property(nonatomic, retain) NSString *btnPressed;
...
then in my mainView.m inside the switchView method I did this:
- (IBAction)switchView:(id)sender{
secondView *myViewController = [[secondView alloc] initWithNibName:#"secondView" bundle:nil];
btnPressed = [NSString stringWithFormat:#"%i", [sender tag]];
[myViewController setBtnPressed:self.btnPressed];
[self.view addSubview:myViewController.view];
}
This line in the code above actually takes care of transferring the data to the newly created subView:
[myViewController setBtnPressed:self.btnPressed];
Then in my secondView.h I declare exactly the same NSString *btnPressed and its #property (though this a completely different object than the one declared in main)
Then in my secondView.m I get the value of the button pressed I'm interested in.
- (void)viewDidLoad {
[super viewDidLoad];
int theValueOfTheButtonPressed = [self.btnPressed intValue];
}
This works well.
Don't forget to #synthesize btnPressed; as well as [btnPressed release]; in both mainView.m and secondView.m

Resources