如何在 Objective-C 中添加一个拥有自己的 UIViewController 的子视图?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/23399061/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 21:14:46  来源:igfitidea点击:

How to add a subview that has its own UIViewController in Objective-C?

objective-cuitableviewuiviewcontrolleruicontainerview

提问by Patricia

I am struggling with subviews that have their own UIViewControllers. I have a UIViewControllerwith 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 questionwhere I learned I needed a controller for the table.

我正在努力处理具有自己的UIViewControllers. 我有一个UIViewController视图(浅粉色)和两个按钮toolbar。我希望在按下第一个按钮时显示蓝色视图,并在按下第二个按钮时显示黄色视图。如果我只想显示视图应该很容易。但是蓝色视图将包含一个表,因此它需要它自己的控制器。那是我的第一堂课。我从这个 SO question开始,在那里我了解到我需要一个桌子的控制器。

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.

所以,我要备份并在这里采取一些婴儿步骤。下面是我的实用程序ViewController(主视图控制器)和其他两个控制器(蓝色和黄色)的简单起点的图片。想象一下,当 Utility ViewController(主视图)首次显示时,蓝色(默认)视图将显示在粉红色视图所在的位置。用户将能够单击这两个按钮来回切换,并且永远不会显示粉红色视图。我只希望蓝色视图去粉红色视图所在的地方,黄色视图去粉红色视图所在的地方。我希望这是有道理的。

Simple Storyboard image

简单的故事板图像

I'm trying to use addChildViewController. From what I have seen, there are two ways to do this: The Container View in the storyboardor addChildViewControllerprogrammatically. I want to do it programmatically. I don't want to use a NavigationControlleror 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.

我正在尝试使用addChildViewController. 从我所见,有两种方法可以做到这一点:storyboard或以addChildViewController编程方式的容器视图。我想以编程方式进行。我不想使用 aNavigationController或 Tab 栏。我只想添加控制器,并在按下相关按钮时将正确的视图推入粉红色视图中。

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 addChildViewControllerand 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?

下面是我到目前为止的代码。我想要做的就是在粉红色视图所在的位置显示蓝色视图。从我所看到的,我应该能够添加addChildViewControllerSubView。这段代码不是为我做的。我的困惑越来越好。有人可以帮助我在粉红色视图所在的位置显示蓝色视图吗?

This code is not intended to do anything other than display the blue view in viewDidLoad.

除了在 viewDidLoad 中显示蓝色视图之外,此代码不打算执行任何其他操作。

IDUtilityViewController.h

IDUtilityViewController.h

#import <UIKit/UIKit.h>

@interface IDUtilityViewController : UIViewController
@property (strong, nonatomic) IBOutlet UIView *utilityView;
@end

IDUtilityViewController.m

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?

self.aboutVC.aboutView 为零。但我在storyboard. 我还需要实例化它吗?

enter image description here

在此处输入图片说明

回答by Fattie

This post dates from the early days of modern iOS. It is updated with current information and the current Swift syntax.

这篇文章可以追溯到现代 iOS 的早期。它使用当前信息和当前 Swift 语法进行更新。

In iOS today "everything is a container view". It is the basic way you make apps today.

在今天的 iOS 中,“一切都是容器视图”。这是您今天制作应用程序的基本方式。

An app may be so simple that it has just the one screen. 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...

(A) 将容器视图拖到您的场景中...

Drag a container view into your scene view. (Just as you would drag in any element such as a UIButton.)

将容器视图拖到场景视图中。(就像您将拖入任何元素(例如 UIButton)一样。)

The container view is the brown thing in this image. It is actually insideyour scene view.

容器视图是这张图片中的棕色东西。它实际上您的场景视图中。

enter image description here

在此处输入图片说明

When you drag a container view into your scene view, Xcode automatically gives you two things:

当您将容器视图拖到场景视图中时,Xcode 会自动为您提供两件事

  1. You get the container view insideyour scene view, and,

  2. you get a brand-new UIViewControllerwhich is just sitting around somewhere on the white of your storyboard.

  1. 你得到的容器视图场景视图,而且,

  2. 你会得到一个全新的UIViewController,它只是坐在你的故事板白色的某个地方

The two are connectedwith the "Masonic Symbol Thing" - explained below!

这两者是连接的解释如下-与“共济会标志的事”!



(B) Click on that new view controller. (So that's the new thing Xcode made for you somewhere on the white area, not the thing inside your scene.) ... and, change the class!

(B) 单击那个新的视图控制器。(所以这是 Xcode 在白色区域的某个地方为您制作的新东西,而不是您场景中的东西。)...并且,更改类!

It's really that simple.

真的就是这么简单。

You're done.

你完成了。



Here's the same thing explained visually.

这是同样的事情在视觉上解释。

Notice the container viewat (A).

注意容器视图(A)

Notice the controllerat (B).

注意控制器(B)

shows a container view and the associated view controller

显示一个容器视图和关联的视图控制器

Click on B. (That's B - not A!)

单击 B。(那是 B - 不是 A!)

Go to the inspector at the top right. Notice it says "UIViewController"

转到右上角的检查员。注意它说“UIViewController”

enter image description here

在此处输入图片说明

Change it to your own custom class, which is a UIViewController.

将其更改为您自己的自定义类,即 UIViewController。

enter image description here

在此处输入图片说明

So, I have a Swift class Snapwhich is a UIViewController.

所以,我有一个 Swift 类Snap,它是一个UIViewController.

enter image description here

在此处输入图片说明

So where it says "UIViewController" in the Inspector I typed in "Snap".

所以它在检查器中显示“UIViewController”的地方我输入了“Snap”。

(As usual, Xcode will auto-complete "Snap" as you start typing "Snap...".)

(像往常一样,当您开始输入“Snap...”时,Xcode 会自动完成“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.

因此,当您单击以添加容器视图时,Apple 会自动为您提供一个链接的视图控制器,位于故事板上。

Currently (2019) it happens to make it a UIViewControllerby default.

目前(2019 年)它恰好UIViewController是默认设置。

That's silly: it should ask which type you need. For example, often you need a table view.

这很愚蠢:它应该询问您需要哪种类型。例如,你经常需要一个表格视图。

Here's how to change itto something different:

以下是如何将其更改为不同的内容:

At the time of writing, Xcode gives you a UIViewControllerby default. Let's say you want a UICollectionViewControllerinstead:

(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 UICollectionViewControllerto 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 overthe "Triggered Segue" and notice that Xcode highlightsall of the unwanted UIViewController.

(iv) Click the "x" to actually deletethat Triggered Segue.

(v) DRAGfrom that Triggered Segue (viewDidLoad is the only choice). Drag across the storyboard to your new UICollectionViewController. Let go and a pop-up appears. You mustselect embed.

(vi) Simply deleteall of the unwanted UIViewController. You're done.

在撰写本文时,XcodeUIViewController默认为您提供了一个。假设你想要一个UICollectionViewController

(i) 将容器视图拖到您的场景中。查看Xcode默认为您提供的故事板上的UIViewController。

(ii) 将 newUICollectionViewController拖到情节提要的主要白色区域上的任何位置。

(iii) 单击场景内的容器视图。单击连接检查器。请注意,有一个“触发转场”。 将鼠标悬停在“Triggered Segue”上并注意 Xcode突出显示了所有不需要的 UIViewController。

(iv) 单击“x”以实际删除该 Triggered Segue。

(ⅴ)DRAG从触发Segue公司(viewDidLoad中是唯一的选择)。拖过故事板到你的新 UICollectionViewController。放手,会出现一个弹出窗口。您必须选择嵌入

(vi) 只需删除所有不需要的 UIViewController。你完成了。

Short version:

精简版:

  • delete the unwanted UIViewController.

  • Put a new UICollectionViewControlleranywhere on the storyboard.

  • Control-dragfrom the container view'sConnections - Trigger Segue - viewDidLoad, to, your new controller.

  • Be sure to select "embed" on the popup.

  • 删除不需要的 UIViewController。

  • UICollectionViewController在故事板上的任何地方放置一个新的。

  • 容器视图的Connections - Trigger Segue - viewDidLoad按住 Control 键拖动到你的新控制器。

  • 请务必在弹出窗口中选择“嵌入”。

It's that easy.

就这么简单。



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.

“共济会符号”的东西是segue

enter image description here

在此处输入图片说明

Select the segue by clicking onthe "masonic symbol" thing.

通过点击选择SEGUE的“共济会标志”的事情。

Look to your right.

看看你的右边。

You MUSTtype in a text identifierfor the segue.

必须为 segue输入文本标识符

You decide on the name. It can be any text string.A good choice is often "segueClassName".

您决定名称。它可以是任何文本字符串。一个不错的选择通常是“segueClassName”。

If you follow that pattern, all your segues will be called segueClockView, seguePersonSelector, segueSnap, segueCards and so on.

如果你遵循这个模式,你所有的 segue 都将被称为 segueClockView、seguePersonSelector、segueSnap、segueCards 等等。

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.

然后,在代码中,在整个场景的 ViewController 中执行以下操作。

Let's say you have three container views in the scene. Each container view holds a different controller, say "Snap", "Clock" and "Other".

假设场景中有三个容器视图。每个容器视图都有一个不同的控制器,比如“Snap”、“Clock”和“Other”。

Latest syntax

最新语法

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 prepareForSeguecall.

就这么简单。使用prepareForSegue调用连接一个变量以引用控制器。



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).

假设您“在”放置在容器视图中的控制器(示例中的“Snap”)。

It can be a confusing to get to the "boss" view controller above you ("Dash" in the example). Fortunately, it is this simple:

到达您上方的“老板”视图控制器(示例中的“Dash”)可能会令人困惑。幸运的是,这很简单:

// 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 = parent as? Dash
}

Critical:Only works from viewDidAppearor later. Will not work in viewDidLoad.

关键:仅适用于viewDidAppear或以后。不会在viewDidLoad.

You're done.

你完成了。



Important: that onlyworks for container views.

重要提示:这适用于容器视图。

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...

现在有了故事板标识符,在屏幕上弹出新视图是司空见惯的(而不是在 Android 开发中)。所以,假设用户想要编辑一些东西......

    // 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 GUARANTEEDthat Dash will be the parent view controller of Snap.

使用容器视图时,保证Dash 将是 Snap 的父视图控制器。

However that is NOT NECESSARILY THE CASEwhen you use instantiateViewController.

然而,当您使用instantiateViewController 时,情况并非如此

Very confusingly, in iOS the parent view controller is not relatedto the class which instantiated it. (It mightbe the same, but usually it is not the same.) The self.parentpattern is onlyfor container views.

非常令人困惑的是,在 iOS 中,父视图控制器实例化它的类无关。(可能相同,但通常不相同。)该self.parent模式适用于容器视图。

(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.)

(对于instantiateViewController 模式中的类似结果,您必须使用协议和委托,记住委托将是一个薄弱环节。)

Note though that these days it's pretty easy to dynamically load a container view from another storyboard - see last section below. It's often the best way.

请注意,如今从另一个故事板动态加载容器视图非常容易 - 请参阅下面的最后一节。这往往是最好的方法。



prepareForSegue poorly named...

prepareForSegue 名字不好...

It's worth noting that "prepareForSegue" is a really bad name!

值得注意的是,“prepareForSegue”是一个非常糟糕的名字!

"prepareForSegue" is used for two purposes: loading container views, and, segueing between scenes.

“prepareForSegue”有两个用途:加载容器视图,以及场景之间的转场。

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 more sense if "prepareForSegue" was called something like "loadingContainerView".

如果将“prepareForSegue”称为“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 viewsall sitting inside the same identical area. In your code, simply hide all four and turn on the one you want visible.

最简单的方法是:让四个不同的容器视图都位于同一个区域内。在您的代码中,只需隐藏所有四个并打开您想要可见的一个。

Easy.

简单。



Container views "from code" ...

容器视图“来自代码”......

... dynamically load a Storyboard in to a container view.

... 将 Storyboard 动态加载到容器视图中。

2019 Syntax

2019 语法

Say you have a storyboard file "Map.storyboard", storyboard ID is "MapID", and the storyboard is a view controller for your Mapclass.

假设您有一个故事板文件“Map.storyboard”,故事板 ID 是“MapID”,故事板是您Map班级的视图控制器。

let map = UIStoryboard(name: "Map", bundle: nil)
           .instantiateViewController(withIdentifier: "MapID")
           as! Map

Have an ordinary UIView in your main scene:

在你的主场景中有一个普通的 UIView:

@IBOutlet var dynamicContainerView: UIView!

Apple explain herethe four things you have to do to add a dynamic container view

苹果在这里解释了添加动态容器视图必须做的四件事

addChild(map)
map.view.frame = dynamicContainerView.bounds
dynamicContainerView.addSubview(map.view)
map.didMove(toParent: self)

(In that order.)

(以该顺序。)

And to remove that container view:

并删除该容器视图:

map.willMove(toParent: nil)
map.view.removeFromSuperview()
map.removeFromParent()

(Also in that order.) That's it.

(也是按这个顺序。)就是这样。

(Note however in that example, the dynamicContainerViewis simply a fixed view. It does not change or resize. This assumes your app never rotates or anything else. Usually, you would have to add the four usual constraints to simply keep the map.view inside dynamicContainerView, as it resizes.)

(但是请注意,在该示例中,dynamicContainerView它只是一个固定视图。它不会改变或调整大小。这假设您的应用程序从不旋转或其他任何东西。通常,您必须添加四个常用约束以简单地将 map.view 保留在里面dynamicContainerView,因为它会调整大小。)

回答by rdelmar

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,

我看到两个问题。首先,由于您要在情节提要中制作控制器,因此您应该使用instantiateViewControllerWithIdentifier:而不是来实例化它们initWithNibName:bundle:。其次,当您将视图添加为子视图时,您应该给它一个框架。所以,

- (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];
}