在情节提要中使用Xib时,Xib的IBOutlets返回nil

时间:2019-04-11 20:09:53

标签: ios swift interface-builder xib nib

我已经使用Interface Builder作为xib创建了一个视图。之所以这样做,是因为我想为用户提供增加这些视图中任意数量的视图控制器的选项。我创建了一个Xib视图(名为“ TimeRangeView.xib”,创建了一个自定义类(名称为“ TimeRangeView.swift”),并将xib的类设置为该自定义类,将情节提要板元素连接至TimeRangeView.swift的IBOutlets,设置了xib的文件所有者添加到TimeRangeView.swift中,并在视图控制器中将其类设置为View的视图添加到我的TimeRangeView.swift中。enter image description here enter image description here

TimeRangeView.swift:

import UIKit

class TimeRangeView: UIView {
    @IBOutlet var startTimeLabel: UILabel!
    @IBOutlet var startDatePicker: UITextField!
    @IBOutlet var endTime: UILabel!
    @IBOutlet var endDatePicker: UITextField!
    @IBOutlet var addButton: UIButton!
    @IBOutlet var maxTimeLabel: UILabel!



    @IBAction func addButtonPressed(_ sender: UIButton) {

    }
}

我正在使用的情节提要UIViewController有一个Interface Builder视图作为子视图;该视图的类分配为TimeRangeView.swift。该视图作为IBOutlet连接到视图控制器:

@IBOutlet var timeRangeView: TimeRangeView!

在视图控制器的viewDidLoad()方法中,我想更改一些TimeRangeView的子视图(如上所述,它们作为IBOutlets连接),但是当我尝试更改其某些属性时,它们返回nil :

override func viewDidLoad() {
    super.viewDidLoad()
    timeRangeView.maxTimeLabel.text = "Max duration: \(timeString ?? "n/a")"

    //Change the text fields' input views
    let startTimeDatePickerToolBar = UIToolbar().ToolbarPicker(mySelect: #selector(dismissPicker))

    let endTimeDatePickerToolBar = UIToolbar().ToolbarPicker(mySelect: #selector(dismissPicker))

    startTimeDatePicker.setDate(Date(timeIntervalSince1970: NSDate().timeIntervalSince1970 + 60), animated: false)

    endTimeDatePicker.setDate(Date(timeIntervalSince1970: NSDate().timeIntervalSince1970 + 120), animated: false)

    timeRangeView.startDatePicker.inputView = startTimeDatePicker
    timeRangeView.startDatePicker.inputAccessoryView = startTimeDatePickerToolBar
    timeRangeView.startDatePicker.delegate = self

    timeRangeView.endDatePicker.inputView = endTimeDatePicker
    timeRangeView.endDatePicker.inputAccessoryView = endTimeDatePickerToolBar
    timeRangeView.endDatePicker.delegate = self
}

timeRangeView不返回nil;它正在加载。但是,当尝试访问timeRangeView的子视图(startDatePicker,endDatePicker,maxTimeLabel)时,它们都返回nil。

我收到的错误是:

Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value

3 个答案:

答案 0 :(得分:1)

标记的答案是正确的,肯定是很好的。 我只是想为此目的提供更简洁的代码。

Link to github

// Assuming NavigationArrow is the class of your nib and navigationContainerView is the view container:
navigationView = NavigationArrow.loadNibInside(ofType: NavigationArrow.self, containerView: navigationContainerView)

.. ..

extension UIView: NibReusable {

     class func loadNibInside<T: UIView>(ofType: T.Type, containerView: UIView) -> T {
        let view = T.instantiate() as! T
        view.fill(in: containerView)
        return view
    }

    public class func instantiate<T: UIView>() -> T {

        return nib.instantiate(withOwner: nil, options: nil).first as! T
    }

    @discardableResult
    public func fill(in view: UIView) -> (left: NSLayoutConstraint, right: NSLayoutConstraint, top: NSLayoutConstraint, bottom: NSLayoutConstraint) {

        if view.subviews.contains(self) == false {
            view.addSubview(self)
        }
        translatesAutoresizingMaskIntoConstraints = false
        let left = leadingAnchor.constraint(equalTo: view.leadingAnchor)
        left.isActive = true
        let right = trailingAnchor.constraint(equalTo: view.trailingAnchor)
        right.isActive = true
        let top = topAnchor.constraint(equalTo: view.topAnchor)
        top.isActive = true
        let bottom = bottomAnchor.constraint(equalTo: view.bottomAnchor)
        bottom.isActive = true
        return (left, right, top, bottom)
    }
}



public protocol NibReusable: Reusable {

    static var nib: UINib { get }
}

public extension NibReusable {

    public static var nib: UINib {
        let name = NSStringFromClass(self).components(separatedBy: ".").last!
        return UINib(nibName: name, bundle: Bundle(for: self))
    }
}


public protocol Reusable: class {

    static var reuseIdentifier: String { get }
}

public extension Reusable {

    public static var reuseIdentifier: String {
        let name = NSStringFromClass(self).components(separatedBy: ".").last!
        return name
    }
}

答案 1 :(得分:0)

  

之所以会这样,是因为xib从未加载

所以您可以在这里做的是,首先在viewController中添加一个简单的UIView而不是TimeRangeView

@IBOutlet var timeRangeContainerView: UIView!

现在在viewDidLoad中添加通过使用获取xib

let timeRangeView = Bundle.main.loadNibNamed("TimeRangeView", owner: self, options: nil)?[0] as? TimeRangeView

并将其添加为timeRangeContainerView的子视图

override func viewDidLoad() {
    super.viewDidLoad()
    let timeRangeView = Bundle.main.loadNibNamed("TimeRangeView", owner: self, options: nil)?[0] as? TimeRangeView

    timeRangeView.frame = CGRect(origin: CGPoint.zero, size: self.timeRangeContainerView.frame.size)

    self.timeRangeContainerView.insertSubview(timeRangeView, at: 0)

    //  Add constraints
    timeRangeView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
    timeRangeView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0).isActive = true
    timeRangeView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true
    timeRangeView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
    timeRangeView.translatesAutoresizingMaskIntoConstraints = false  


    //Add the rest of your code here
}

约束是可选的

答案 2 :(得分:0)

此处是重构答案:

将此作为新文件添加到您的项目中:

import UIKit

class CustomSubviewFromNib {
  static func add(subview: UIView, into parentView: UIView) {
    subview.frame = CGRect(origin: CGPoint.zero, size: parentView.frame.size)
    
    parentView.insertSubview(subview, at: 0)
    
    //  Add constraints
    subview.leadingAnchor.constraint(equalTo: parentView.leadingAnchor, constant: 0).isActive = true
    subview.trailingAnchor.constraint(equalTo: parentView.trailingAnchor, constant: 0).isActive = true
    subview.topAnchor.constraint(equalTo: parentView.topAnchor, constant: 0).isActive = true
    subview.bottomAnchor.constraint(equalTo: parentView.bottomAnchor, constant: 0).isActive = true
    subview.translatesAutoresizingMaskIntoConstraints = false
  }
}

然后,将其添加为扩展名:

import UIKit

extension UIView {
    class func fromNib<T: UIView>() -> T {
        return Bundle(for: T.self).loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T
    }
}

最后一步,用法:

private weak var profilePhotoView: ProfilePhotoView!

final class ProfileViewController {
    override func viewDidLoad() {
      super.viewDidLoad()
        
      setupProfilePhotoView()
    }
      
    func setupProfilePhotoView() {
      let profilePhotoView: ProfilePhotoView = UIView.fromNib()
      self.profilePhotoView = profilePhotoView
      CustomSubviewFromNib.add(subview: profilePhotoView, into: profileContainerView)
    }
}

别忘了将您的xib文件命名为与自定义视图类文件相同的名称