#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:
Отмеченный ответ правильный и, безусловно, отличный. Я просто подумал о предоставлении более чистого кода для этой цели.
// 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 таким же, как и ваш пользовательский файл класса просмотра