在使用Storyboard中的Xib时,Xib的IBOutlets返回nil

3
我使用Interface Builder创建了一个xib视图。这样做是因为我想让用户有选择地将任意数量的这些视图添加到视图控制器中。我创建了一个Xib视图(名为“TimeRangeView.xib”),创建了一个自定义类(名为“TimeRangeView.swift”),并将xib的类设置为此自定义类,将故事板元素连接到TimeRangeView.swift的IBOutlets,将xib的文件所有者设置为TimeRangeView.swift,并在视图控制器中添加了该视图,其类设置为我的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) {

    }
}

我正在使用的Storyboard 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

1
如果一个视图在故事板中,那么它不会在单独的xib文件中。你的带有outlets的xib永远不会被加载。 - matt
3个回答

4
这是因为xib文件从未被加载。你可以这样做:首先在viewController中添加一个简单的UIView,而不是TimeRangeView。
@IBOutlet var timeRangeContainerView: UIView!

现在,在 viewDidLoad 中添加以下内容,通过使用 Fetch 命令获取 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
}


限制条件是可选的。

惊人的解决方案!我将编辑我的帖子以展示我修改代码的方式,让它能够工作。非常感谢! - David Chopin

1
标记的答案是正确的,而且非常好。我只是想提供一个更干净的代码来实现这个目标。

链接到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

这是重新设计的回答:

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

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

最后一步,用法:
final class ProfileViewController {

    private weak var profilePhotoView: ProfilePhotoView!

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

不要忘记将xib文件命名为与自定义视图类文件相同的名称。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接