IBOutlets Xib возвращают ноль при использовании Xib в раскадровке

#ios #swift #interface-builder #xib #nib

#iOS #swift #конструктор интерфейса #xib #перо

Вопрос:

Я создал представление, используя Interface Builder в качестве xib. Я сделал это, поскольку хочу предоставить пользователю возможность увеличивать любое количество этих представлений в контроллере представления. Я создал представление Xib (с именем «TimeRangeView.xib», создал пользовательский класс (с именем «TimeRangeView.swift») и установил для класса xib этот пользовательский класс, подключил элементы раскадровки к IBOutlets TimeRangeView.swift, установил для владельца файла 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 не возвращает ноль; он загружается. Но при попытке получить доступ к вложенным представлениям timeRangeView (startDatePicker, endDatePicker, maxTimeLabel) все они возвращают ноль.

Ошибка, которую я получаю,:

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

Комментарии:

1. Если представление находится в раскадровке, его нет в отдельном xib. Ваш xib с выходами никогда не загружается.

Ответ №1:

Это происходит потому, что xib никогда не загружался

Итак, что вы можете сделать здесь, сначала добавьте простой UIView в ViewController, а не TimeRangeView

 @IBOutlet var timeRangeContainerView: UIView!
  

Теперь в viewDidLoad add извлекается с 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
}

  

Ограничения являются необязательными

Комментарии:

1. Феноменальное решение! Я собираюсь отредактировать свой пост, чтобы показать, на что я изменил свой код, чтобы заставить его работать. Большое спасибо!

Ответ №2:

Отмеченный ответ правильный и, безусловно, отличный. Я просто подумал о предоставлении более чистого кода для этой цели.

Ссылка на 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 таким же, как и ваш пользовательский файл класса просмотра