프로그래밍 방식으로 컨테이너보기를 추가하는 방법


107

Container View는 Interface Editor를 통해 스토리 보드에 쉽게 추가 할 수 있습니다. 추가되면 컨테이너보기는 플레이스 홀더보기, 임베드 된 세그먼트 및 (하위)보기 컨트롤러입니다.

그러나 프로그래밍 방식으로 컨테이너보기를 추가하는 방법을 찾을 수 없습니다. 사실, 나는 그런 클래스를 찾을 수조차 없다 UIContainerView.

Container View 클래스의 이름은 확실히 좋은 시작입니다. segue를 포함한 완전한 가이드를 많이 주시면 감사하겠습니다.

View Controller Programming Guide를 알고 있지만 Interface Builder가 Container Viewer에 대해 수행하는 방식과 동일하지 않다고 생각합니다. 예를 들어 제약 조건이 제대로 설정되면 (하위)보기가 컨테이너보기의 크기 변경에 적응합니다.


1
"제약 조건이 적절하게 설정되면 (하위)보기가 컨테이너보기의 크기 변경에 적응합니다"라고 말할 때 의미하는 것은 무엇입니까 (따라서보기 컨트롤러 포함을 수행 할 때 이것이 사실이 아님을 의미 함)? 제약 조건은 IB의 컨테이너보기를 통해 수행 했든 프로그래밍 방식으로보기 컨트롤러 포함을 통해 수행 했든 동일하게 작동합니다.
Rob

1
가장 중요한 것은 임베디드 ViewController의 수명주기입니다. 내장 된 ViewController인터페이스 빌더의 라이프 사이클은 정상이지만, 프로그램 추가 하나가 viewDidAppear, 어느 쪽 viewWillAppear(_:)도 아니다 viewWillDisappear.
DawnSong

2
@DawnSong - 제대로보기 봉쇄 호출을 할 경우 viewWillAppear와는 viewWillDisappear잘, 아이 뷰 컨트롤러라고합니다. 그렇지 않은 예가 있다면 명확하게 설명하거나 그렇지 않은 이유를 묻는 질문을 게시해야합니다.
Rob

답변:


228

스토리 보드 "컨테이너보기"는 표준 UIView개체 일뿐 입니다. 특별한 "컨테이너보기"유형은 없습니다. 실제로 뷰 계층 구조를 보면 "컨테이너 뷰"가 표준임을 알 수 있습니다 UIView.

컨테이너보기

이를 프로그래밍 방식으로 달성하려면 "뷰 컨트롤러 포함"을 사용합니다.

  • instantiateViewController(withIdentifier:)스토리 보드 개체를 호출하여 자식 뷰 컨트롤러를 인스턴스화 합니다.
  • addChild부모 뷰 컨트롤러를 호출 하십시오.
  • 뷰 컨트롤러 view를 뷰 계층 구조에 추가하고 또는 제약 조건을 적절하게 addSubview설정하십시오 frame.
  • didMove(toParent:)자식보기 컨트롤러 에서 메서드를 호출하여 부모보기 컨트롤러에 대한 참조를 전달합니다.

보기 컨테이너보기 컨트롤러를 구현Programming Guide를보기 컨트롤러 와의 섹션 "컨테이너 뷰 컨트롤러 구현" 의 UIViewController 클래스 참조 .


예를 들어, Swift 4.2에서는 다음과 같이 보일 수 있습니다.

override func viewDidLoad() {
    super.viewDidLoad()

    let controller = storyboard!.instantiateViewController(withIdentifier: "Second")
    addChild(controller)
    controller.view.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(controller.view)

    NSLayoutConstraint.activate([
        controller.view.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
        controller.view.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
        controller.view.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
        controller.view.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10)
    ])

    controller.didMove(toParent: self)
}

위의 내용은 실제로 계층 구조에 "컨테이너보기"를 추가하지 않습니다. 그렇게하려면 다음과 같이하면됩니다.

override func viewDidLoad() {
    super.viewDidLoad()

    // add container

    let containerView = UIView()
    containerView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(containerView)
    NSLayoutConstraint.activate([
        containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
        containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
        containerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
        containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
    ])

    // add child view controller view to container

    let controller = storyboard!.instantiateViewController(withIdentifier: "Second")
    addChild(controller)
    controller.view.translatesAutoresizingMaskIntoConstraints = false
    containerView.addSubview(controller.view)

    NSLayoutConstraint.activate([
        controller.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
        controller.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
        controller.view.topAnchor.constraint(equalTo: containerView.topAnchor),
        controller.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
    ])

    controller.didMove(toParent: self)
}

이 후자의 패턴은 서로 다른 자식보기 컨트롤러간에 전환 할 때 매우 유용하며 한 자식의보기가 동일한 위치와 이전 자식보기에 있는지 확인하려는 경우 (즉, 배치에 대한 모든 고유 한 제약 조건은 컨테이너보기에 의해 결정됩니다. 매번 이러한 제약 조건을 재 구축 할 필요가 없습니다). 그러나 단순한보기 포함 만 수행하는 경우이 별도의 컨테이너보기에 대한 필요성은 덜 매력적입니다.


위의 예에서, 내가하고 있어요 translatesAutosizingMaskIntoConstraintsfalse제약 자신을 정의. 당신은 분명히 떠날 수 translatesAutosizingMaskIntoConstraintstrue하고 모두 설정 frame하고를 autosizingMask원하는 경우 표시 추가,보기에.


Swift 3Swift 2 변환에 대한이 답변의 이전 개정판을 참조하십시오 .


나는 당신의 대답이 완전하다고 생각하지 않습니다. 가장 중요한 것은 임베디드 ViewController의 수명주기입니다. 내장 된 ViewController인터페이스 빌더의 라이프 사이클은 정상이지만, 프로그램 추가 하나가 viewDidAppear, 어느 쪽 viewWillAppear(_:)도 아니다 viewWillDisappear.
DawnSong

또 다른 이상한 일이 포함 된 것입니다 ViewControllers는 ' viewDidAppear부모의에서 호출되는 viewDidLoad대신 중, 부모의viewDidAppear
DawnSong

@DawnSong- "하지만 프로그래밍 방식으로 추가 된 항목에는 viewDidAppear, [하지만] 둘 다 viewWillAppear(_:)없습니다 viewWillDisappear.". 는 will방법은 두 시나리오에 제대로 호출되어 나타납니다. didMove(toParentViewController:_)프로그래밍 방식으로 수행 할 때 호출해야합니다. 그렇지 않으면 호출 하지 않습니다. 등장 타이밍에 대해. 두 방법 모두 동일한 순서로 호출됩니다. 다른 점은 viewDidLoad임베딩을 사용하면 이전 parent.viewDidLoad에 로드 되었기 때문에 의 타이밍입니다 .하지만 프로그래밍 방식에서는 예상대로 parent.viewLoadLoad.
Rob

2
나는 작동하지 않는 제약에 갇혀 있었다. 내가 실종 된 것으로 밝혀졌다 translatesAutoresizingMaskIntoConstraints = false. 왜 필요한지 또는 왜 작동하는지 모르겠지만 답변에 포함 해 주셔서 감사합니다.
hasen

1
@Rob developer.apple.com/library/archive/featuredarticles/… 목록 5-1에는 "content.view.frame = [self frameForContentController];"라는 Objective-C 코드 줄이 있습니다. 그 코드에서 "frameForContentController"는 무엇입니까? 컨테이너 뷰의 프레임입니까?
Daniel Brower

24

Swift 3에서 @Rob의 답변 :

    // add container

    let containerView = UIView()
    containerView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(containerView)
    NSLayoutConstraint.activate([
        containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
        containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
        containerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
        containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
        ])

    // add child view controller view to container

    let controller = storyboard!.instantiateViewController(withIdentifier: "Second")
    addChildViewController(controller)
    controller.view.translatesAutoresizingMaskIntoConstraints = false
    containerView.addSubview(controller.view)

    NSLayoutConstraint.activate([
        controller.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
        controller.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
        controller.view.topAnchor.constraint(equalTo: containerView.topAnchor),
        controller.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
        ])

    controller.didMove(toParentViewController: self)

13

세부

  • Xcode 10.2 (10E125), Swift 5

해결책

import UIKit

class WeakObject {
    weak var object: AnyObject?
    init(object: AnyObject) { self.object = object}
}

class EmbedController {

    private weak var rootViewController: UIViewController?
    private var controllers = [WeakObject]()
    init (rootViewController: UIViewController) { self.rootViewController = rootViewController }

    func append(viewController: UIViewController) {
        guard let rootViewController = rootViewController else { return }
        controllers.append(WeakObject(object: viewController))
        rootViewController.addChild(viewController)
        rootViewController.view.addSubview(viewController.view)
    }

    deinit {
        if rootViewController == nil || controllers.isEmpty { return }
        for controller in controllers {
            if let controller = controller.object {
                controller.view.removeFromSuperview()
                controller.removeFromParent()
            }
        }
        controllers.removeAll()
    }
}

용법

class SampleViewController: UIViewController {
    private var embedController: EmbedController?

    override func viewDidLoad() {
        super.viewDidLoad()
        embedController = EmbedController(rootViewController: self)

        let newViewController = ViewControllerWithButton()
        newViewController.view.frame = CGRect(origin: CGPoint(x: 50, y: 150), size: CGSize(width: 200, height: 80))
        newViewController.view.backgroundColor = .lightGray
        embedController?.append(viewController: newViewController)
    }
}

전체 샘플

ViewController

import UIKit

class ViewController: UIViewController {

    private var embedController: EmbedController?
    private var button: UIButton?
    private let addEmbedButtonTitle = "Add embed"

    override func viewDidLoad() {
        super.viewDidLoad()

        button = UIButton(frame: CGRect(x: 50, y: 50, width: 150, height: 20))
        button?.setTitle(addEmbedButtonTitle, for: .normal)
        button?.setTitleColor(.black, for: .normal)
        button?.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        view.addSubview(button!)

        print("viewDidLoad")
        printChildViewControllesInfo()
    }

    func addChildViewControllers() {

        var newViewController = ViewControllerWithButton()
        newViewController.view.frame = CGRect(origin: CGPoint(x: 50, y: 150), size: CGSize(width: 200, height: 80))
        newViewController.view.backgroundColor = .lightGray
        embedController?.append(viewController: newViewController)

        newViewController = ViewControllerWithButton()
        newViewController.view.frame = CGRect(origin: CGPoint(x: 50, y: 250), size: CGSize(width: 200, height: 80))
        newViewController.view.backgroundColor = .blue
        embedController?.append(viewController: newViewController)

        print("\nChildViewControllers added")
        printChildViewControllesInfo()
    }

    @objc func buttonTapped() {

        if embedController == nil {
            embedController = EmbedController(rootViewController: self)
            button?.setTitle("Remove embed", for: .normal)
            addChildViewControllers()
        } else {
            embedController = nil
            print("\nChildViewControllers removed")
            printChildViewControllesInfo()
            button?.setTitle(addEmbedButtonTitle, for: .normal)
        }
    }

    func printChildViewControllesInfo() {
        print("view.subviews.count: \(view.subviews.count)")
        print("childViewControllers.count: \(childViewControllers.count)")
    }
}

ViewControllerWithButton

import UIKit

class ViewControllerWithButton:UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    private func addButon() {
        let buttonWidth: CGFloat = 150
        let buttonHeight: CGFloat = 20
        let frame = CGRect(x: (view.frame.width-buttonWidth)/2, y: (view.frame.height-buttonHeight)/2, width: buttonWidth, height: buttonHeight)
        let button = UIButton(frame: frame)
        button.setTitle("Button", for: .normal)
        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        view.addSubview(button)
    }

    override func viewWillLayoutSubviews() {
        addButon()
    }

    @objc func buttonTapped() {
        print("Button tapped in \(self)")
    }
}

결과

여기에 이미지 설명 입력 여기에 이미지 설명 입력 여기에 이미지 설명 입력


1
이 코드를 사용 tableViewController하여 a 를 추가 viewController했지만 전자의 제목을 설정할 수 없습니다. 그렇게 할 수 있는지 모르겠습니다. 이 질문 을 게시 했습니다 . 당신이 그것을 보면 당신이 좋다.
mahan

12

다음은 신속한 5의 코드입니다.

class ViewEmbedder {
class func embed(
    parent:UIViewController,
    container:UIView,
    child:UIViewController,
    previous:UIViewController?){

    if let previous = previous {
        removeFromParent(vc: previous)
    }
    child.willMove(toParent: parent)
    parent.addChild(child)
    container.addSubview(child.view)
    child.didMove(toParent: parent)
    let w = container.frame.size.width;
    let h = container.frame.size.height;
    child.view.frame = CGRect(x: 0, y: 0, width: w, height: h)
}

class func removeFromParent(vc:UIViewController){
    vc.willMove(toParent: nil)
    vc.view.removeFromSuperview()
    vc.removeFromParent()
}

class func embed(withIdentifier id:String, parent:UIViewController, container:UIView, completion:((UIViewController)->Void)? = nil){
    let vc = parent.storyboard!.instantiateViewController(withIdentifier: id)
    embed(
        parent: parent,
        container: container,
        child: vc,
        previous: parent.children.first
    )
    completion?(vc)
}

}

용법

@IBOutlet weak var container:UIView!

ViewEmbedder.embed(
    withIdentifier: "MyVC", // Storyboard ID
    parent: self,
    container: self.container){ vc in
    // do things when embed complete
}

스토리 보드가 아닌 뷰 컨트롤러와 함께 다른 임베드 기능을 사용하십시오.


2
훌륭한 클래스이지만 동일한 마스터 뷰 컨트롤러 내에 2 개의 viewController를 포함해야하는데, removeFromParent호출로 인해이를 방지 할 수 있습니다.이를 허용하도록 클래스를 어떻게 수정 하시겠습니까?
GarySabo

화려한 :) 감사합니다
Rebeloper

좋은 예이지만 여기에 전환 애니메이션을 추가하려면 어떻게해야합니까 (임베딩, 자식 뷰 컨트롤러 교체)?
Michał Ziobro
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.