How to add a subview that has its own UIViewController in Objective
I am struggling with subviews that have their own UIViewControllers
. I have a UIViewController
with a view (light pink) and two buttons on a toolbar
. I want blue view to display when the first button is pressed and the yellow view to display with the second button is pressed. Should be easy if I just wanted to display a view. But the blue view will contain a table, so it needs it's own controller. That was my first lesson. I started off with this SO question where I learned I needed a controller for the table.
So, I am going to back up and take some baby steps here. Below is a picture of a simple starting point with my Utility ViewController
(the main view controller) and the other two controllers (blue and yellow). Imagine that when the Utility ViewController
(the main view) is first displayed the blue (default) view will be displayed where the pink view is located. Users will be able to click the two buttons to go back and forth and the pink view will NEVER be displayed. I just want the blue view to go where the pink view is and the yellow view to go where the pink view is. I hope this makes sense.
I'm trying to use addChildViewController
. From what I have seen, there are two ways to do this: The Container View in the storyboard
or addChildViewController
programmatically. I want to do it programmatically. I don't want to use a NavigationController
or a Tab bar. I just want to add the controllers and shove the correct view into the pink view when the associated button is pressed.
Below is the code I have so far. All I want to do is display the blue view where the pink view is. From what I have seen I should be able to just addChildViewController
and addSubView. This code is not doing that for me. My confusion is getting the better of me. Can somebody help me get the blue view displayed where the pink view is?
This code is not intended to do anything other than display the blue view in viewDidLoad.
IDUtilityViewController.h
#import <UIKit/UIKit.h>
@interface IDUtilityViewController : UIViewController
@property (strong, nonatomic) IBOutlet UIView *utilityView;
@end
IDUtilityViewController.m
#import "IDUtilityViewController.h"
#import "IDAboutViewController.h"
@interface IDUtilityViewController ()
@property (nonatomic, strong) IDAboutViewController *aboutVC;
@end
@implementation IDUtilityViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.aboutVC = [[IDAboutViewController alloc]initWithNibName:@"AboutVC" bundle:nil];
[self addChildViewController:self.aboutVC];
[self.aboutVC didMoveToParentViewController:self];
[self.utilityView addSubview:self.aboutVC.aboutView];
}
@end
--------------------------EDIT------------------------------
The self.aboutVC.aboutView is nil. But I wired it up in the storyboard
. Do I still need to instantiate it?
This post usually has the latest syntax, such as Swift 4 currently.
In iOS today "everything is a container view" . It is the basic way you make apps today.
An app may be so simple that it has just the one view. But even in that case, each "thing" on the screen is a container view.
It's this easy...
(A) Drag a container view in to your scene...
Drag a container view into your scene view. (Just as you would drag in, say, a UIButton.)
The container view is the brown thing in this image. It is actually inside your scene view.
When you drag a container view into your scene view, Xcode automatically gives you two things :
You get the container view inside your scene view , and,
you get a brand-new UIViewController
which is just sitting around somewhere on the white of your storyboard .
The two are connected with the "Masonic Symbol" thing - explained below!
(B) Click on that new view controller (the new thing Xcode made for you somewhere on the white area, not the thing inside your scene) ... and, change the class!
It's really that simple.
You're done.
Here's the same thing explained visually.
Notice the container view at (A)
.
Notice the controller at (B)
.
Click on B. (That's B - not A!)
Go to the inspector at the top right. Notice it says "UIViewController"
[ ][3]
Change it to your own custom class, which is a UIViewController.
So, I have a Swift class Snap
which is a UIViewController
.
So where it says "UIViewController" in the Inspector I typed in "Snap".
(As usual, Xcode will auto-complete "Snap" as you start typing "Snap...".)
That's all there is to it - you're done.
How to change the container view - say, to a table view.
So when you click to add a container view, Apple automatically gives you a linked view controller, sitting on the storyboard.
As it happens (2017): it makes it a UIViewController
by default.
That's silly: it should ask which type you need. For example, often you need a table view. Here's how to change it to something different:
At the time of writing, Xcode gives you a UIViewController
by default. Let's say you want a UICollectionViewController
instead:
(i) Drag a container view in to your scene. Look at the UIViewController on the storyboard which Xcode gives you by default.
(ii) Drag a new UICollectionViewController
to anywhere on the main white area of the storyboard.
(iii) Click the container view inside your scene. Click the connections inspector. Notice there is one "Triggered Segue". Mouse over the "Triggered Segue" and notice that Xcode highlights all of the unwanted UIViewController.
(iv) Click the "x" to actually delete that Triggered Segue.
(v) Drag from that Triggered Segue (viewDidLoad is the only choice). Drag across the storyboard to your new UICollectionViewController. Let go and a pop-up appears. You must select embed .
(vi) Simply delete all of the unwanted UIViewController. You're done.
Short version: delete the unwanted UIViewController. Put a new UICollectionViewController
on the storyboard. Control-drag from: the container view's Connections - Trigger Segue - viewDidLoad, to, your new controller. Be sure to select "embed" on the popup.
Entering the text identifier...
You will have one of these "square in a square" Masonic symbol things: it is on the "bendy line" connecting your container view with the view controller.
The "masonic symbol" thing is the segue.
Select the segue by clicking on the "masonic symbol" thing.
Look to your right.
You MUST type in a text identifier for the segue.
You decide on the name. It can be any text string. A sensible choice is often "segueClassName".
If you follow that pattern, all your segues will be called segueClockView, seguePersonSelector, segueSnap, segueCards and so on.
Next, where do you use that text identifier?
How to connect 'to' the child controller...
Then, do the following, in code, in the ViewController of the whole scene.
Let's say you have three container views in the scene. Each container view holds a different controller, say "Snap", "Clock" and "Other".
Latest Swift3 syntax (2017)
var snap:Snap?
var clock:Clock?
var other:Other?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "segueSnap")
{ snap = (segue.destination as! Snap) }
if (segue.identifier == "segueClock")
{ clock = (segue.destination as! Clock) }
if (segue.identifier == "segueOther")
{ other = (segue.destination as! Other) }
}
It's that simple. You connect a variable to refer to the controllers, using the prepareForSegue
call.
How to connect in the 'other direction', up to the parent...
Say you're "in" the controller which you have put in a container view ("Snap" in the example).
It can be a confusing to get to the "boss" view controller above you ("Dash" in the example). Fortunately, it is this simple:
// Dash is the overall scene.
// Here we are in Snap. Snap is one of the container views inside Dash.
class Snap {
var myBoss:Dash?
override func viewDidAppear(_ animated: Bool) { // MUST be viewDidAppear
super.viewDidAppear(animated)
myBoss = self.parent as? Dash
}
Critical: Only works from viewDidAppear
or later. Will not work in viewDidLoad
.
You're done.
Important: that only works for container views.
Important advanced tip: Don't forget, that only works for container views.
These days with storyboard identifiers, it's commonplace to just pop new views on the screen (rather as in Android development). So, let's say the user wants to edit something...
// let's just pop a view on the screen.
// this has nothing to do with container views
//
let e = ...instantiateViewController(withIdentifier: "Edit") as! Edit
e.modalPresentationStyle = .overCurrentContext
self.present(e, animated: false, completion: nil)
When using a container view, IT IS GUARANTEED that Dash will be the parent view controller of Snap.
However that is NOT NECESSARILY THE CASE when you use instantiateViewController.
Very confusingly, in iOS the parent view controller is not related to the class which instantiated it. (It might be the same, but usually it is not the same.) The self.parent
pattern is only for container views.
(For a similar result in the instantiateViewController pattern, you have to use a protocol and a delegate, remembering that the delegate will be a weak link.)
prepareForSegue poorly named...
It's worth noting that "prepareForSegue" is a really bad name!
"prepareForSegue" is used for two purposes: loading container views, and, segueing between scenes.
But in practice, you very rarely segue between scenes! Whereas almost every app has many, many, container views as a matter of course.
It would make far more sense if "prepareForSegue" was called something like "loadingContainerView".
More than one...
A common situation is: You have a small area on the screen, where you want to show one of a number of different view controllers. For example, one of four widgets.
The simplest way to do this: just have four different container views all sitting in the same identical area . In your code, simply hide all four and turn on the one you want visible.
On the storyboard, have one empty "holder" UIView, which simply holds the four container views. You can then size or move all four at once by sizing or moving the "holder". In your code, simply have four UIView
outlets, one for each of the container views. Copy and paste the code above, "How to connect to the child controller", to connect the four contained view controllers.
Note - Storyboard References arrive!
As SimplGy points out below "iOS 9's Storyboard References make container views even more awesome. You can define your reusable view (controller) wherever you like and reference it from any container view in multiple, modular storyboards."
Note too that - rather confusingly - often today you just don't bother with container views!
You simply instantiateViewController#withIdentifier
in many situations.
But note the "gotchya" about .parent
explained above. The whole point of container views is you are instantly and simply assured of the parent-chain.
If you go with instantiateViewController#withIdentifier
using a storyboard reference, you have to mess around with a protocol and a delegate (remembering that the delegate will be a weak link). But then you can use it "on the fly" flexibly anywhere.
In contrast using a "fixed", so to speak, container view is extremely simple, and you instantly hook up between parent and child as explained above.
I see two problems. First, since you're making the controllers in the storyboard, you should be instantiating them with instantiateViewControllerWithIdentifier:
, not initWithNibName:bundle:
. Second, when you add the view as a subview, you should give it a frame. So,
- (void)viewDidLoad
{
[super viewDidLoad];
self.aboutVC = [self.storyboard instantiateViewControllerWithIdentifier:@"aboutVC"]; // make sure you give the controller this same identifier in the storyboard
[self addChildViewController:self.aboutVC];
[self.aboutVC didMoveToParentViewController:self];
self.aboutVC.view.frame = self.utilityView.bounds;
[self.utilityView addSubview:self.aboutVC.aboutView];
}
链接地址: http://www.djcxy.com/p/2520.html
上一篇: CSS默认边框颜色