I've created a view using Interface Builder as a xib. I've done this as I want to give the user the option to increment any number of these views to the view controller. I've created an Xib view (named "TimeRangeView.xib", created a custom class (named "TimeRangeView.swift") and set the xib's class to this custom class, connected the storyboard elements to the TimeRangeView.swift's IBOutlets, set the xib's file owner to TimeRangeView.swift, and added the view with its class set to my TimeRangeView.swift in the view controller.
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) {
}
}
The storyboard UIViewController I am using this in has an Interface Builder view as a subview; this view's class is assigned as TimeRangeView.swift. This view is connected to the view controller as an IBOutlet:
@IBOutlet var timeRangeView: TimeRangeView!
Within the view controller's viewDidLoad() method, I am wanting to change some of TimeRangeView's subviews (which, as we've covered, are connected as IBOutlets), but they are returning nil when I try to change some of their properties:
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 is not returning nil; it is loading. But when trying to access timeRangeView's subviews (startDatePicker, endDatePicker, maxTimeLabel), they are all returning nil.
The error I am receiving is:
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
This is happening Because the xib was never loaded
So what you can do here is, First add a simple UIView
in the viewController rather than TimeRangeView
@IBOutlet var timeRangeContainerView: UIView!
Now in viewDidLoad
add Fetch the xib
by using
let timeRangeView = Bundle.main.loadNibNamed("TimeRangeView", owner: self, options: nil)?[0] as? TimeRangeView
And add this as a subview of 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
}
The constraints are optional
The marked answer is correct and surely great. I just thought of providing a cleaner code for that aim.
// 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
}
}
A refactored answer here:
Add this as a new file to your project:
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
}
}
Then, add this as an 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
}
}
Last step, usage:
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)
}
}
Don't forget to name your xib file as same as your custom view class file
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.