J'ai créé une vue en utilisant Interface Builder comme xib. J'ai fait cela car je veux donner à l'utilisateur la possibilité d'incrémenter n'importe quel nombre de ces vues dans le contrôleur de vue. J'ai créé une vue Xib (nommée "TimeRangeView.xib", créé une classe personnalisée (nommée "TimeRangeView.swift") et défini la classe de xib sur cette classe personnalisée, connecté les éléments du storyboard aux IBOutlets de TimeRangeView.swift, définir le le propriétaire du fichier de xib à TimeRangeView.swift, et a ajouté la vue avec sa classe définie sur mon TimeRangeView.swift dans le contrôleur de vue.
TimeRangeView.swift:
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
Le storyboard UIViewController dans lequel j'utilise ceci a une vue Interface Builder comme sous-vue; la classe de cette vue est assignée comme TimeRangeView.swift. Cette vue est connectée au contrôleur de vue en tant qu'IBOutlet:
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 }
Dans la méthode viewDidLoad () du contrôleur de vue, je souhaite modifier certaines des sous-vues de TimeRangeView (qui, comme nous l'avons vu, sont connectées en tant qu'IBOutlets), mais elles renvoient nul lorsque j'essaye de modifier certaines de leurs propriétés:
@IBOutlet var timeRangeView: TimeRangeView!
timeRangeView ne renvoie pas nil; il est en cours de chargement. Mais lorsque vous essayez d'accéder aux sous-vues de timeRangeView (startDatePicker, endDatePicker, maxTimeLabel), elles renvoient toutes nil.
L'erreur que je reçois est:
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) { } }
3 Réponses :
Cela se produit parce que le xib n'a jamais été chargé
Donc, ce que vous pouvez faire ici, c'est d'abord ajouter un
UIView
simple dans le viewController plutôt queTimeRangeView
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 }Maintenant dans
viewDidLoad
addxib
lexib
en utilisantlet timeRangeView = Bundle.main.loadNibNamed("TimeRangeView", owner: self, options: nil)?[0] as? TimeRangeViewEt ajoutez ceci en tant que sous-vue de
timeRangeContainerView
@IBOutlet var timeRangeContainerView: UIView!Les contraintes sont facultatives
Solution phénoménale! Je vais modifier mon message pour montrer ce que j'ai changé mon code pour le faire fonctionner. Merci beaucoup!
La réponse marquée est correcte et sûrement excellente. J'ai juste pensé à fournir un code plus propre pour cet objectif.
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 } }
.. ..
// Assuming NavigationArrow is the class of your nib and navigationContainerView is the view container: navigationView = NavigationArrow.loadNibInside(ofType: NavigationArrow.self, containerView: navigationContainerView)
Une réponse refactorisée ici:
Ajoutez ceci en tant que nouveau fichier à votre projet:
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) } }
Ensuite, ajoutez ceci en tant qu'extension:
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 } }
Dernière étape, utilisation:
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 } }
N'oubliez pas de nommer votre fichier xib comme votre fichier de classe d'affichage personnalisé
Si une vue est dans le storyboard, elle n'est pas dans un xib séparé. Votre xib avec les prises n'est jamais chargé.