I'm having an issue with a multi-level hierarchy of childViewControllers in iOS/Swift. There are three layers to the current setup from lowest-to-highest:
我在iOS / Swift中遇到了childViewControllers的多级层次结构问题。从最低到最高的当前设置有三个层:
InfoViewController
- InfoViewController
SelectionViewController
- SelectionViewController
MainViewController
- MainViewController
The InfoViewController
has a view that is loaded from XIB.
InfoViewController有一个从XIB加载的视图。
SelectionViewController
contains a UIStackView
with an InfoViewController
and a UIButton
.
SelectionViewController包含一个带有InfoViewController和UIButton的UIStackView。
MainViewController
is the top-level VC which is usually embedded in a UINavigationController
.
MainViewController是*VC,通常嵌入在UINavigationController中。
The Problem
问题
When I add the InfoViewController
and it's view directly to the MainViewController
everything works great.
当我将InfoViewController及其视图直接添加到MainViewController时,一切都很有效。
func setupInfoViewControllerDirectlyOnMainVC () {
addChildViewController(infoViewController)
infoViewController.view.embedInside(otherView: infoContainerView)
infoViewController.didMove(toParentViewController: self)
}
However, if I add the SelectionViewController
to the MainViewController
using the same method, the embedded InfoViewController
doesn't update it's UI - it always looks like the untouched XIB file that it is controlling. When it is NOT embedded in this SelectionViewController
is behaves as expected.
但是,如果我使用相同的方法将SelectionViewController添加到MainViewController,嵌入的InfoViewController不会更新它的UI - 它总是看起来像它正在控制的未触摸的XIB文件。当它未嵌入此SelectionViewController时,其行为与预期一致。
As shown below, the UI is visible, but any changes made to it via asking it's ViewController do not show up - it looks exactly like the XIB file it was created from.
如下所示,UI是可见的,但是通过询问它的ViewController对它进行的任何更改都不会显示 - 它看起来与它创建的XIB文件完全相同。
Below is the class setup starting with the BasicInfoView followed by the three viewControllers listed above.
下面是从BasicInfoView开始的类设置,后面是上面列出的三个viewControllers。
BasicInfoView
BasicInfoView
class PLBasicInfoView: PLDesignableViewFromXib {
//
// MARK: Outlets
//
//
@IBOutlet weak var photoView: PFRoundImageView!
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var subtitleLabel: UILabel!
var imageFile:PFFile? {
didSet {
photoView.file = imageFile
photoView.loadInBackground()
}
}
//
// MARK: Initialization
//
//
override var nameOfXib: String {
return "PLBasicInfoView"
}
override var intrinsicContentSize: CGSize {
return CGSize(width: super.intrinsicContentSize.width, height: 56)
}
}
BasicInfoViewController
BasicInfoViewController
class PLBasicInfoViewController: UIViewController {
/**
The BasicInfoView which will be managed by this controller.
*/
var basicInfoView = PLBasicInfoView()
/**
This is the master stack view which contains all subviews.
*/
var stackView = UIStackView()
/**
PFFile representing the image to be displayed in the imageView. Setting a valid imageFile object automatically laods the image from the server. If set to nil, the defaultImage is displayed instead.
*/
var imageFile: PFFile? {
didSet {
if imageFile != nil {
basicInfoView.imageFile = imageFile
} else {
basicInfoView.photoView.image = defaultImage
}
}
}
/**
Default UIImage to be displayed in the imageView if there is no imageFile assigned.
*/
var defaultImage: UIImage! {
return #imageLiteral(resourceName: "ios7-camera-outline")
}
/**
Main text of the infoView
*/
var titleText:String? {
didSet {
basicInfoView.titleLabel.isHidden = (titleText == nil)
basicInfoView.titleLabel.text = titleText
}
}
/**
Secondary text of the infoView. Displays under titleText.
*/
var subtitleText:String? {
didSet {
basicInfoView.subtitleLabel.isHidden = (subtitleText == nil)
basicInfoView.subtitleLabel.text = subtitleText
}
}
/**
Embed our stackView into main view. The custom embedInsider(otherView:UIView) method (UIView extension) will take care of the subview additional as well as all layout constraints.
*/
func setupStackView () {
stackView.embedInside(otherView: view)
stackView.axis = .vertical
stackView.addArrangedSubview(basicInfoView)
}
override
func viewDidLoad() {
super.viewDidLoad()
setupStackView()
}
}
SelectionViewController
SelectionViewController
class PLSelectableInfoViewController: UIViewController {
/**
If true, the info view will be shown and the selection button will be hidden.
*/
var isAssigned = false {
didSet {
selectionButton.isHidden = isAssigned
infoView.isHidden = !isAssigned
}
}
/**
The View controller dispaying the object in question.
*/
var infoViewController: PLBasicInfoViewController! {
return PLBasicInfoViewController()
}
private
var infoView: UIView!
/**
Button on bottom of stack. Intended to allow user to assign a new value to the contact property.
*/
var selectionButton = PLButton()
/**
Stack view containing all subviews.
*/
var stackView = UIStackView()
//
// MARK: UIViewController Overrides
//
//
override
func viewDidLoad() {
super.viewDidLoad()
setupStackView()
addInfoView()
}
private
func setupStackView () {
stackView.embedInside(otherView: view)
stackView.axis = .vertical
}
private
func addInfoView () {
addChildViewController(infoViewController)
infoView = infoViewController.view
stackView.addArrangedSubview(infoView)
infoViewController.didMove(toParentViewController: self)
}
}
Some non-relevant code has been removed
一些不相关的代码已被删除
Notes
笔记
Please note that in practice, both BasicInfoViewController and SelectionViewController are subclassed. For example, I have a ContactInfoViewController which can be passed a Contact object and display full name, company name and photo (as explained above, this works fine). There is also a subclass of SelectionViewController to complement this: ContactSelectionViewController. ContactSelectionViewController also has a Contact object property which can be assigned and is then passed to the embedded ContactInfoViewController - this is the point at which the data is not displayed. I have included these subclasses below for additional reference.
请注意,在实践中,BasicInfoViewController和SelectionViewController都是子类。例如,我有一个ContactInfoViewController,它可以传递一个Contact对象并显示全名,公司名称和照片(如上所述,这样可以正常工作)。还有一个SelectionViewController的子类来补充它:ContactSelectionViewController。 ContactSelectionViewController还有一个Contact对象属性,可以将其赋值,然后传递给嵌入式ContactInfoViewController - 这是不显示数据的点。我在下面列出了这些子类以供进一步参考。
ContactInfoViewController
ContactInfoViewController
Again, this works perfectly when placed directly into the MainViewController.
同样,当直接放入MainViewController时,这非常有效。
class PLContactInfoViewController: PLBasicInfoViewController {
/**
Contact object managed by this controller.
*/
var contact: PLContact? {
didSet {
if contact == nil {
titleText = "Not Set"
subtitleText = nil
return
}
contact?.fetchIfNeededInBackground(block: { (object, error) in
if let _ = object as? PLContact {
self.updateWithContact()
}
})
}
}
override
var defaultImage: UIImage! {
return #imageLiteral(resourceName: "ios7-contact-outline")
}
private
func updateWithContact () {
if let c = contact {
titleText = c.fullName
imageFile = c.photo
c.company?.fetchIfNeededInBackground(block: { (object, error) in
if let comp = object as? PLCompany {
self.subtitleText = comp.name
} else {
self.subtitleText = nil
}
})
}
}
}
ContactSelectionViewController
ContactSelectionViewController
This VC functions properly, but the embedded ContactInfoViewController does not display data. For some reason, the view from the ContactInfoViewController is not being updated with data when it is embedded inside this controller.
此VC正常运行,但嵌入式ContactInfoViewController不显示数据。出于某种原因,ContactInfoViewController中的视图在嵌入此控制器内时未使用数据进行更新。
class PLContactAssignmentViewController: PLSelectableInfoViewController {
/**
The contact currently selected by this contorller
*/
var selectedContact: PLContact? {
didSet {
isAssigned = !(selectedContact == nil)
contactInfoViewController.contact = selectedContact
}
}
override
var infoViewController: PLBasicInfoViewController! {
return PLContactInfoViewController()
}
private
var contactInfoViewController: PLContactInfoViewController {
return infoViewController as! PLContactInfoViewController
}
}
1 个解决方案
#1
1
Try
尝试
var _infoViewController: PLBasicInfoViewController?
var infoViewController: PLBasicInfoViewController! {
if let vc = _infoViewController {
return vc
}
_infoViewController = PLBasicInfoViewController()
return _infoViewController!
}
or
要么
lazy var infoViewController: PLBasicInfoViewController = {
return PLBasicInfoViewController()
}()
It might be because you are initiating PLBasicInfoViewController every time when you try to access.
这可能是因为您每次尝试访问时都在启动PLBasicInfoViewController。
#1
1
Try
尝试
var _infoViewController: PLBasicInfoViewController?
var infoViewController: PLBasicInfoViewController! {
if let vc = _infoViewController {
return vc
}
_infoViewController = PLBasicInfoViewController()
return _infoViewController!
}
or
要么
lazy var infoViewController: PLBasicInfoViewController = {
return PLBasicInfoViewController()
}()
It might be because you are initiating PLBasicInfoViewController every time when you try to access.
这可能是因为您每次尝试访问时都在启动PLBasicInfoViewController。