我正在与拥有自己UIViewControllers的子视图作斗争.我有一个带视图的UIViewController(浅粉色)和toolbar上的两个按钮.我希望按下第一个按钮时显示蓝色视图,按下第二个按钮时显示黄色视图.如果我只想显示一个视图,应该很容易.但是蓝色视图将包含一个表,因此它需要自己的控制器.那是我的第一课.我从this SO question开始,在那里我知道我需要一个桌子控制器.

所以,我要后退一步,在这里迈出一小步.下面是我的实用程序ViewController(主视图控制器)和其他两个控制器(蓝色和黄色)的简单起点的图片.想象一下,当第一次显示实用程序ViewController(主视图)时,蓝色(默认)视图将显示在粉色视图所在的位置.用户可以点击这两个按钮来回切换,粉色视图将永远不会显示.我只想让蓝色的视图go 粉色视图的地方,黄色的视图go 粉色视图的地方.我希望这是有道理的.

简单故事板图像

我试着用addChildViewController.据我所见,有两种方法可以做到这一点:storyboardaddChildViewController中的容器视图以编程方式.我想通过编程来实现.我不想使用NavigationController或标签栏.我只想添加控制器,并在按下相关按钮时将正确的视图推入粉红色视图.

以下是我目前掌握的代码.我只想在粉色视图所在的位置显示蓝色视图.从我所看到的,我应该能够只有addChildViewController和addSubView.这个代码不是为我做的.我的困惑渐渐战胜了我.有人能帮我把蓝色视图显示在粉色视图的地方吗?

除了在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------------------------------

self .关于TVC.aboutView为零.但我把它连接到了storyboard.我还需要实例化它吗?

在此处输入图像描述

推荐答案

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

在今天的iOS中是"everything is a container view".这是你今天制作应用程序的基本方式.

应用程序可能非常简单,只有一个屏幕.但即使在这种情况下,屏幕上的每个"东西"都是一个容器视图.

就这么简单...

version notes

2020. These days you usually just load a container view from a separate storyboard, which is dead easy. It's explained at the bottom of this post. If you are new to container views, maybe familiarize with the 'classic style' ('same storyboard') container tutorial first.

2021. Updated syntax. Used SO's new '###' pretty headlines. More detail on loading from code.


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

将容器视图拖动到场景视图中.(就像拖动任何元素(如UIButton)一样.)

容器视图是图中的棕色部分.实际上是insidescene view.

enter image description here

将容器视图拖动到场景视图中时,Xcode会自动为您提供100:

  1. 在场景视图中获得容器视图,

  2. 你会得到一个全新的UIViewController,也就是101.

这两个是100和"共济会符号的东西"-解释如下!


(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!

就这么简单.

你完了.


下面是同样的事情的视觉解释.

请注意(A)处的101.

请注意(B)处的101.

shows a container view and the associated view controller

点击B(那是B,不是A!)

go 找右上角的判断员.注意上面写着"UIViewController"

enter image description here

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

enter image description here

所以,我有一个Swift Snap级,也就是UIViewController级.

enter image description here

我在Inspector中输入了"Snap",上面写着"UIViewController".

(和往常一样,当您开始键入"Snap…"时,Xcode将自动完成"Snap".)

这就是全部——你完了.


如何将容器视图更改为表视图.

因此,当你点击添加容器视图时,苹果会自动给你一个链接视图控制器,它位于故事板上.

目前(2019年),它碰巧默认为UIViewController.

这很愚蠢:它应该问你需要哪种类型.例如,通常需要一个表视图.

以下是如何让change it人成为不同的人:

在 compose 本文时,Xcode默认为UIViewController.假设你想要一个UICollectionViewController:

(i) 将容器视图拖动到场景中.看看故事板上的UIViewController,Xcode默认为您提供了它.

(ii)将新的UICollectionViewController拖动到故事板主白色区域的任意位置.

(iii)单击场景中的容器视图.单击连接判断器.注意这里有一个"触发序列".Mouse over"触发Segue"并注意到Xcode highlights包含了所有不需要的UIViewController.

(iv)点击"x"以实际触发Segue.

(v) 从触发的序列中 Select DRAG(viewDidLoad是唯一的 Select ).在情节提要上拖动到新的UICollectionViewController.放手,一个弹出窗口出现.你must Select embed.

(vi)只需delete个不需要的UIViewController.你完了.

简短版本:

  • 删除不需要的UIViewController.

  • 在故事板的任何地方放一个新的UICollectionViewController.

  • container view's个连接-触发器序列-viewDidLoad到您的新控制器.

  • 确保在弹出窗口中 Select "嵌入".

就这么简单.


正在输入文本标识符...

您将拥有以下"square in a square"个共济会符号中的一个:它位于连接容器视图和视图控制器的"弯曲线"上.

"共济会符号"是segue.

enter image description here

点击on"共济会符号"来 Select 乐段.

向右看.

你可以为segue输入一个text identifier.

你决定名字.It can be any text string.一个好的 Select 通常是"segueClassName".

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

接下来,在哪里使用文本标识符?


如何连接到子控制器...

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

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

最新语法

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

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


如何在"另一个方向"连接到家长...

假设您"在"已放入容器视图的控制器中(示例中为"快照").

到达您上方的"boss"视图控制器(示例中的"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:只适用于viewDidAppear或更高版本.不会在viewDidLoad工作.

你完了.


Important: that only works for container views.

提示:别忘了,这只适用于容器视图.

如今,有了情节提要标识符,在屏幕上弹出新视图是很常见的(就像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)

使用容器视图时,该破折号将成为Snap的父视图控制器.

然而,当您使用InstanceEviewController时,这是NOT NECESSARILY THE CASE.

非常令人困惑的是,在iOS中,父视图控制器与实例化它的类是not related.(可能是一样的,但通常是不一样的.)对于容器视图,self.parent模式是only.

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

不过请注意,现在从另一个故事板动态加载容器视图非常容易——请参见下面的最后一节.这通常是最好的方式.


prepareForSegue名字不好...

值得注意的是,"prepareForSegue"是really bad name!

"prepareForSegue"用于两个目的:加载容器视图,以及在场景之间进行分段.

但在实践中,几乎每个应用程序都有很多容器视图,这是理所当然的.

如果"prepareForSegue"被称为"loadingContainerView"之类的话,那就更有意义了.


不止一个...

常见的情况是:屏幕上有一个小区域,您希望在其中显示多个不同视图控制器中的一个.例如,四个小部件中的一个.

最简单的方法是:让four different container views人都坐在same identical area人的座位上.在你的代码中,只需隐藏所有四个,然后打开你想要的一个.

容易的


"来自代码"的容器视图...

... 将情节提要动态加载到容器视图中.

2019+语法

假设你有一个故事板文件"Map.storyboard",故事板ID是"MapID",故事板是Map类的视图控制器.

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

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

@IBOutlet var dynamicContainerView: UIView!

Apple here解释了添加动态容器视图需要做的四件事

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

(按顺序排列.)

要删除该容器视图,请执行以下操作:

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

(也是按照这个顺序.)就这样.

但是请注意,在该示例中,dynamicContainerView只是一个固定视图.它不会改变或调整大小.这只有在你的应用程序从不旋转或其他任何情况下才有效.通常,您必须添加四个常见的约束才能简单地保留 map .dynamicContainerView内部的视图,在其调整大小时.事实上,这是任何iOS应用程序都需要的"世界上最方便的扩展",

extension UIView {
    
    // it's basically impossible to make an iOS app without this!
    
    func bindEdgesToSuperview() {
        
        guard let s = superview else {
            preconditionFailure("`superview` nil in bindEdgesToSuperview")
        }
        
        translatesAutoresizingMaskIntoConstraints = false
        leadingAnchor.constraint(equalTo: s.leadingAnchor).isActive = true
        trailingAnchor.constraint(equalTo: s.trailingAnchor).isActive = true
        topAnchor.constraint(equalTo: s.topAnchor).isActive = true
        bottomAnchor.constraint(equalTo: s.bottomAnchor).isActive = true
    }
}

因此,在任何真正的应用程序中,上面的代码都是:

addChild(map)
dynamicContainerView.addSubview(map.view)
map.view.bindEdgesToSuperview()
map.didMove(toParent: self)

(有些人甚至做了一个扩展名.addSubviewAndBindEdgesToSuperview(),以避免那里出现一行代码!)

提醒订单必须

  • 加上子元素
  • 添加实际视图
  • 打电话给didMove

移除其中一个?

您已经向支架动态添加了map个,现在您想要删除它.正确且唯一的顺序是:

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

通常情况下,你会有一个holder视图,你想交换不同的控制器.所以:

var current: UIViewController? = nil
private func _install(_ newOne: UIViewController) {
    if let c = current {
        c.willMove(toParent: nil)
        c.view.removeFromSuperview()
        c.removeFromParent()
    }
    current = newOne
    addChild(current!)
    holder.addSubview(current!.view)
    current!.view.bindEdgesToSuperview()
    current!.didMove(toParent: self)
}

Objective-c相关问答推荐

恢复 iOS7 之前的 UINavigationController pushViewController 动画

NSDateFormatter,我做错了什么还是这是一个错误?

应用程序终止时处理推送通知

如何使用 NSFileManager 删除目录及其内容

声明变量id和NSObject *有什么区别?

NSInternalInconsistencyException: '无效参数不满足: !stayUp || CLClientIsBackgroundable(内部->fClient)'

Facebook 的 UIActivityViewController 不显示默认文本

Objective-C 方法中的静态变量是否跨实例共享?

使用情节提要的条件转场

使用正则表达式在 Xcode 中查找/替换

使用 Objective-C 查找 IMEI 号码

如何以编程方式移动 UIScrollView 以集中在键盘上方的控件中?

检测 iOS 应用程序是否在调试器中运行

如何在 Cocoa 中获取 NSArray 的第一个 x 元素?

使用 NSLog 打印 NSData

Modal segue,导航栏消失

iOS是静态框架还是动态框架?

为什么 detailTextLabel 不可见?

iPhone - dequeueReusableCellWithIdentifier 用法

如何在对象内使用 objc_setAssociatedObject/objc_getAssociatedObject?