วิธีเพิ่มมุมมองคอนเทนเนอร์โดยใช้โปรแกรม


108

คุณสามารถเพิ่มมุมมองคอนเทนเนอร์ลงในสตอรีบอร์ดได้อย่างง่ายดายผ่านตัวแก้ไขอินเทอร์เฟซ เมื่อเพิ่มแล้วมุมมองคอนเทนเนอร์จะเป็นมุมมองตัวยึดการทำต่อแบบฝังและตัวควบคุมมุมมอง (ลูก)

อย่างไรก็ตามฉันไม่สามารถหาวิธีเพิ่มมุมมองคอนเทนเนอร์แบบเป็นโปรแกรมได้ อันที่จริงฉันไม่พบคลาสที่มีชื่อUIContainerViewหรือมากกว่านั้นด้วยซ้ำ

ชื่อคลาสของ Container View เป็นการเริ่มต้นที่ดีอย่างแน่นอน คำแนะนำที่สมบูรณ์รวมถึงการทำต่อจะได้รับการชื่นชมมาก

ฉันรู้จัก View Controller Programming Guide แต่ฉันไม่ถือว่ามันเหมือนกับที่ Interface Builder ทำกับ Container Viewer ตัวอย่างเช่นเมื่อตั้งค่าข้อ จำกัด อย่างถูกต้องมุมมอง (ลูก) จะปรับให้เข้ากับการเปลี่ยนแปลงขนาดในมุมมองคอนเทนเนอร์


1
คุณหมายถึงอะไรเมื่อคุณพูดว่า "เมื่อกำหนดข้อ จำกัด อย่างถูกต้องมุมมอง (ลูก) จะปรับให้เข้ากับการเปลี่ยนแปลงขนาดในมุมมองคอนเทนเนอร์" (ซึ่งหมายความว่าสิ่งนี้ไม่เป็นความจริงเมื่อคุณดูการควบคุมตัวควบคุม) ข้อ จำกัด จะทำงานเหมือนกันไม่ว่าคุณจะทำผ่านมุมมองคอนเทนเนอร์ใน IB หรือดูการควบคุมตัวควบคุมโดยใช้โปรแกรม
Rob

1
สิ่งที่สำคัญที่สุดคือViewControllerวงจรชีวิตที่ฝังอยู่ ที่ฝังตัวViewControllerของวงจรชีวิตโดย Interface Builder เป็นปกติ แต่อย่างใดอย่างหนึ่งที่เพิ่มโปรแกรมมีviewDidAppearทั้งมิได้viewWillAppear(_:) viewWillDisappear
DawnSong

2
@DawnSong - หากคุณเรียกดูการกักกันอย่างถูกต้องviewWillAppearและviewWillDisappearถูกเรียกบนตัวควบคุมมุมมองเด็กก็ใช้ได้ หากคุณมีตัวอย่างที่ไม่มีคุณควรชี้แจงหรือโพสต์คำถามของคุณเองเพื่อถามว่าทำไมจึงไม่เป็นเช่นนั้น
Rob

คำตอบ:


231

สตอรีบอร์ด "มุมมองคอนเทนเนอร์" เป็นUIViewเพียงออบเจ็กต์มาตรฐาน ไม่มีประเภท "มุมมองคอนเทนเนอร์" พิเศษ ในความเป็นจริงหากคุณดูลำดับชั้นของมุมมองคุณจะเห็นว่า "มุมมองคอนเทนเนอร์" เป็นมาตรฐานUIView:

มุมมองคอนเทนเนอร์

เพื่อให้บรรลุเป้าหมายนี้โดยใช้โปรแกรมคุณใช้ "ดูการควบคุมตัวควบคุม":

  • สร้างอินสแตนซ์ตัวควบคุมมุมมองเด็กโดยเรียก instantiateViewController(withIdentifier:)ใช้ออบเจ็กต์สตอรีบอร์ด
  • โทรaddChildในตัวควบคุมมุมมองหลักของคุณ
  • เพิ่มตัวควบคุมviewมุมมองลงในลำดับชั้นมุมมองของคุณด้วยaddSubview(และตั้งค่าframeหรือข้อ จำกัด ตามความเหมาะสม)
  • เรียกใช้didMove(toParent:)เมธอดบนตัวควบคุมมุมมองเด็กส่งการอ้างอิงไปยังตัวควบคุมมุมมองหลัก

ดูการนำตู้คอนเทนเนอร์ดูตัวควบคุมในมุมมองควบคุม Programming คู่มือและ "การนำตู้คอนเทนเนอร์ดูควบคุม" ของ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)
}

รูปแบบหลังนี้มีประโยชน์อย่างมากหากเคยเปลี่ยนไปมาระหว่างตัวควบคุมมุมมองเด็กที่แตกต่างกันและคุณเพียงแค่ต้องการให้แน่ใจว่ามุมมองของเด็กคนหนึ่งอยู่ในตำแหน่งเดียวกันและมุมมองของเด็กคนก่อนหน้า (กล่าวคือข้อ จำกัด เฉพาะทั้งหมดสำหรับตำแหน่งนั้นกำหนดโดยมุมมองคอนเทนเนอร์ แทนที่จะต้องสร้างข้อ จำกัด เหล่านี้ใหม่ทุกครั้ง) แต่ถ้าเพียงแค่ทำการควบคุมมุมมองแบบธรรมดาความต้องการมุมมองคอนเทนเนอร์แยกนี้ก็น่าสนใจน้อยกว่า


ในตัวอย่างด้านบนฉันกำลังตั้งค่าที่translatesAutosizingMaskIntoConstraintsจะfalseกำหนดข้อ จำกัด ด้วยตัวเอง เห็นได้ชัดว่าคุณสามารถปล่อยtranslatesAutosizingMaskIntoConstraintsเป็นtrueและตั้งค่าทั้งframeและautosizingMaskสำหรับมุมมองที่คุณเพิ่มได้หากคุณต้องการ


ดูการแก้ไขก่อนหน้าของคำตอบนี้สำหรับสวิฟท์ 3และสวิฟท์ 2ซ้ำ


1
ฉันไม่คิดว่าคำตอบของคุณจะสมบูรณ์ สิ่งที่สำคัญที่สุดคือViewControllerวงจรชีวิตที่ฝังอยู่ ที่ฝังตัวViewControllerของวงจรชีวิตโดย Interface Builder เป็นปกติ แต่อย่างใดอย่างหนึ่งที่เพิ่มโปรแกรมมีviewDidAppearทั้งมิได้viewWillAppear(_:) viewWillDisappear
DawnSong

อีกสิ่งที่แปลกคือฝังViewController's 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 มีบรรทัดของโค้ด Objective-C ที่ระบุว่า "content.view.frame = [self frameForContentController];" "frameForContentController" ในโค้ดนั้นคืออะไร? นั่นคือกรอบของมุมมองคอนเทนเนอร์หรือไม่?
Daniel Brower

24

คำตอบของ @ Rob ใน Swift 3:

    // 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ในviewControllerแต่ไม่สามารถตั้งชื่อของอดีตได้ ไม่ทราบว่าเป็นไปได้หรือไม่ ฉันได้โพสต์คำถามนี้แล้ว เป็นเรื่องดีสำหรับคุณถ้าคุณได้ดู
mahan

12

นี่คือรหัสของฉันใน swift 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 viewControllers ภายในตัวควบคุมมุมมองต้นแบบเดียวกันซึ่งการremoveFromParentโทรของคุณป้องกันคุณจะแก้ไขชั้นเรียนของคุณเพื่ออนุญาตสิ่งนี้ได้อย่างไร
GarySabo

ยอดเยี่ยม :) ขอบคุณ
Rebeloper

เป็นตัวอย่างที่ดี แต่ฉันจะเพิ่มแอนิเมชั่นการเปลี่ยนแปลงบางอย่างลงในสิ่งนี้ได้อย่างไร (การฝังการแทนที่ตัวควบคุมมุมมองเด็ก)
Michał Ziobro
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.