Programlı olarak Kapsayıcı Görünümü nasıl eklenir?


107

Bir Kapsayıcı Görünümü, Arayüz Düzenleyicisi aracılığıyla bir film şeridine kolayca eklenebilir. Bir Kap Görünümü eklendiğinde, bir yer tutucu görünüm, bir yerleştirme segmenti ve bir (alt) görünüm denetleyicisidir.

Ancak, programlı olarak Kapsayıcı Görünümü eklemenin bir yolunu bulamıyorum. Aslında, falan adında bir sınıf bile bulamıyorum UIContainerView.

Container View sınıfı için bir isim kesinlikle iyi bir başlangıçtır. Parçayı içeren eksiksiz bir rehber çok takdir edilecektir.

View Controller Programming Guide'ın farkındayım, ancak bunu Interface Builder'ın Container Viewer için yaptığı gibi görmüyorum. Örneğin, kısıtlamalar doğru şekilde ayarlandığında, (alt) görünüm Konteyner Görünümündeki boyut değişikliklerine uyum sağlayacaktır.


1
"Kısıtlamalar doğru bir şekilde ayarlandığında, (alt) görünüm Konteyner Görünümündeki boyut değişikliklerine uyum sağlayacaktır" dediğinizde ne demek istiyorsunuz (bu, denetleyici kapsamını görüntülediğinizde bunun doğru olmadığını ima eder)? Kısıtlamalar, bunu ister IB'de konteyner görünümü aracılığıyla yapmış olun, ister kontrolör muhafazasını programlı olarak görüntüleyin.
Rob

1
En önemli şey, gömülü kişinin ViewControlleryaşam döngüsüdür. ViewControllerArayüz Oluşturucu'nun katıştırılmış yaşam döngüsü normaldir, ancak programla eklenen yaşam döngüsü viewDidAppearne sahiptir ne viewWillAppear(_:)de viewWillDisappear.
DawnSong

2
@DawnSong - Görünüm çevreleme çağrılarını doğru yaparsanız , çocuk görünümü denetleyicisinde viewWillAppearve viewWillDisappearçağrılır, gayet iyi. Olmadıkları bir örneğiniz varsa, açıklığa kavuşturmalı veya neden olmadığını soran kendi sorunuzu göndermelisiniz.
Rob

Yanıtlar:


228

Film şeridi "kapsayıcı görünümü" yalnızca standart bir UIViewnesnedir. Özel bir "kapsayıcı görünümü" türü yoktur. Aslında, görünüm hiyerarşisine bakarsanız, "kapsayıcı görünümünün" bir standart olduğunu görebilirsiniz UIView:

kapsayıcı görünümü

Bunu programlı bir şekilde başarmak için, "denetleyici kapsamını görüntüle" yi kullanırsınız:

  • instantiateViewController(withIdentifier:)Film şeridi nesnesini çağırarak alt görünüm denetleyicisinin örneğini oluşturun .
  • addChildÜst görünüm denetleyicinizi arayın .
  • Görünüm denetleyicilerini viewile görünüm hiyerarşinize ekleyin addSubview(ve ayrıca frameveya kısıtlamalarını uygun şekilde ayarlayın).
  • Başvuruyu didMove(toParent:)üst görünüm denetleyicisine ileterek, alt görünüm denetleyicisindeki yöntemi çağırın .

Bkz Bir Konteyner View Controller uygulanması halinde Kılavuzu Programlama View Controller ve bölümüne "Bir Konteyner View Controller uygulanması" UIViewController Sınıf Referans .


Örneğin Swift 4.2'de şöyle görünebilir:

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

Yukarıdakinin hiyerarşiye aslında bir "kapsayıcı görünümü" eklemediğini unutmayın. Bunu yapmak istiyorsan, şöyle bir şey yaparsın:

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

Bu ikinci model, farklı çocuk görünümü denetleyicileri arasında geçiş yapılıyorsa ve yalnızca bir çocuğun görüşünün aynı konumda olduğundan ve önceki çocuğun görüşünde olduğundan emin olmak istiyorsanız son derece kullanışlıdır (yani, yerleştirme için tüm benzersiz kısıtlamalar kapsayıcı görünümü tarafından belirlenir, bu kısıtlamaları her seferinde yeniden oluşturmak yerine). Ancak, yalnızca basit görünüm sınırlaması gerçekleştiriyorsanız, bu ayrı konteyner görünümüne duyulan ihtiyaç daha az zorlayıcıdır.


Yukarıdaki örneklerde, ben ayarlıyorum translatesAutosizingMaskIntoConstraintsiçin falsekısıtlamaları kendim tanımlayan. Belli ki bırakabilirsiniz translatesAutosizingMaskIntoConstraintsolarak trueve her iki set frameve autosizingMaskdilerseniz ekleyebilir görünümleri için.


Swift 3 ve Swift 2 yorumlamaları için bu cevabın önceki revizyonlarına bakın .


Cevabınızın tam olduğunu sanmıyorum. En önemli şey, gömülü kişinin ViewControlleryaşam döngüsüdür. ViewControllerArayüz Oluşturucu'nun katıştırılmış yaşam döngüsü normaldir, ancak programla eklenen yaşam döngüsü viewDidAppearne sahiptir ne viewWillAppear(_:)de viewWillDisappear.
DawnSong

Bir başka garip şey de gömülü ViewController'lerin viewDidAppearebeveynininki viewDidLoadyerine ebeveyninin viewDidAppear
çağrılması

@DawnSong - "ama programlı olarak eklenen viewDidAppear, [ama] ne ne viewWillAppear(_:)de viewWillDisappear". willYöntemler her iki senaryoda da doğru denir görünür. didMove(toParentViewController:_)Bunu programlı olarak yaparken aramak gerekir , yoksa olmaz. Görünüşün zamanlamasıyla ilgili olarak. yöntemler, her iki şekilde aynı sırayla çağrılırlar. Farklı olan şey, zamanlamadır viewDidLoad, çünkü gömme ile önceden yüklenir parent.viewDidLoad, ancak beklediğimiz gibi programatik ile bu işlem sırasında olur parent.viewLoadLoad.
Rob

2
Çalışmayan kısıtlamalara takılı kaldım; eksik olduğum ortaya çıktı translatesAutoresizingMaskIntoConstraints = false. Neden gerekli olduğunu ya da neden işlerin yürümesini sağladığını bilmiyorum ama cevabınıza eklediğiniz için teşekkür ederim.
hasen

1
At @Rob developer.apple.com/library/archive/featuredarticles/... Listesinde 5-1 yılında, diyor Objective-C kodu, bir çizgi vardır "content.view.frame = [öz frameForContentController];". Bu koddaki "frameForContentController" nedir? Bu, konteyner görünümünün çerçevesi mi?
Daniel Brower

24

@ Rob'un Swift 3'teki cevabı:

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

Detaylar

  • Xcode 10.2 (10E125), Swift 5

Çözüm

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

Kullanım

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

Tam örnek

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

Sonuçlar

görüntü açıklamasını buraya girin görüntü açıklamasını buraya girin görüntü açıklamasını buraya girin


1
Ben eklemek için bu kodu kullanmış tableViewControllerbir de viewControllerfakat eski unvanı ayarlayamaz. Bunun mümkün olup olmadığını bilmiyorum. Bu soruyu gönderdim . Ona bir bakmanız çok hoş.
mahan

12

İşte Swift 5'teki kodum.

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

}

Kullanım

@IBOutlet weak var container:UIView!

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

Film şeridi olmayan görünüm denetleyicisiyle diğer yerleştirme işlevini kullanın.


2
Harika bir sınıf, ancak kendimi aynı ana görünüm denetleyicisine 2 viewController yerleştirmek zorunda buluyorum, removeFromParentçağrınız bunu engelliyor, buna izin vermek için sınıfınızı nasıl değiştirirsiniz?
GarySabo

brilliant :) Teşekkürler
Yeniden geliştirici

Güzel bir örnek, ancak buna nasıl bazı geçiş animasyonları ekleyebilirim (çocuk görünümü denetleyicilerini gömmek, değiştirmek)?
Michał Ziobro
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.