【问题标题】:Xib's IBOutlets returning nil when using Xib in storyboard在情节提要中使用 Xib 时,Xib 的 IBOutlets 返回 nil
【发布时间】:2019-09-02 13:21:29
【问题描述】:

我使用 Interface Builder 作为 xib 创建了一个视图。我这样做是因为我想让用户可以选择将任意数量的这些视图增加到视图控制器。我创建了一个 Xib 视图(名为“TimeRangeView.xib”,创建了一个自定义类(名为“TimeRangeView.swift”)并将 xib 的类设置为此自定义类,将情节提要元素连接到 TimeRangeView.swift 的 IBOutlets,设置xib 的文件所有者到 TimeRangeView.swift,并将其类设置为视图控制器中我的 TimeRangeView.swift 的视图。

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

【问题讨论】:

  • 如果视图在情节提要中,则它不在单独的 xib 中。您的带有插座的 xib 永远不会加载。

标签: ios swift interface-builder xib nib


【解决方案1】:

发生这种情况是因为从未加载过 xib

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

@IBOutlet var timeRangeContainerView: UIView!

现在在 viewDidLoad 添加 Fetch the xib by using

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】:

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

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

【讨论】:

    【解决方案3】:

    这里重构的答案:

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

    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 文件命名为与您的自定义视图类文件相同

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-07-31
      • 1970-01-01
      • 1970-01-01
      • 2013-11-30
      • 2020-10-02
      • 2016-05-06
      • 2013-11-10
      相关资源
      最近更新 更多