Загрузить UIView из пера в Swift
Вот мой код Objective C, который я использую, чтобы загрузить перо для моего настроенного UIView
:
-(id)init{
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:@"myXib" owner:self options:nil];
return [subviewArray objectAtIndex:0];
}
Каков эквивалентный код в Swift?
30 ответов
Оригинальное решение
- Я создал XIB и класс с именем SomeView (использовал одно и то же имя для удобства и читабельности). Я основал оба на UIView.
- В XIB я изменил класс "Владелец файла" на SomeView (в инспекторе удостоверений).
- Я создал выход UIView в SomeView.swift, связав его с представлением верхнего уровня в файле XIB (для удобства его назвали "представлением"). Затем я добавил другие выходы к другим элементам управления в файле XIB по мере необходимости.
- в SomeView.swift я загрузил XIB внутри инициализатора "init with code". Нет необходимости присваивать что-либо "себе". Как только XIB загружен, все розетки подключены, включая представление верхнего уровня. Единственное, чего не хватает, это добавить вид сверху в иерархию видов:
,
class SomeView: UIView {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
NSBundle.mainBundle().loadNibNamed("SomeView", owner: self, options: nil)
self.addSubview(self.view); // adding the top level view to the view hierarchy
}
...
}
Обратите внимание, что таким образом я получаю класс, который загружает себя из пера. Затем я мог бы использовать SomeView в качестве класса всякий раз, когда UIView можно использовать в проекте (в конструкторе интерфейса или программно).
Обновление - с использованием синтаксиса Swift 3
Загрузка xib в следующем расширении записывается как метод экземпляра, который затем может использоваться инициализатором, как показано выше:
extension UIView {
@discardableResult // 1
func fromNib<T : UIView>() -> T? { // 2
guard let contentView = Bundle(for: type(of: self)).loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? T else { // 3
// xib not loaded, or its top view is of the wrong type
return nil
}
self.addSubview(contentView) // 4
contentView.translatesAutoresizingMaskIntoConstraints = false // 5
contentView.layoutAttachAll(to: self) // 6
return contentView // 7
}
}
- Использование сбрасываемого возвращаемого значения, поскольку возвращаемое представление в основном не представляет интереса для вызывающей стороны, когда все выходы уже подключены.
- Это универсальный метод, который возвращает необязательный объект типа UIView. Если не удается загрузить представление, возвращается ноль.
- Попытка загрузить файл XIB с тем же именем, что и текущий экземпляр класса. Если это не удается, возвращается ноль.
- Добавление представления верхнего уровня в иерархию представлений.
- В этой строке предполагается, что мы используем ограничения для компоновки представления.
- Этот метод добавляет верхние, нижние, ведущие и конечные ограничения - прикрепляя представление к "себе" со всех сторон (подробности см.: /questions/27167536/nib-fajl-zagruzhennyij-uiview-v-uitableviewcell-ne-rastyagivaetsya/27167560#27167560)
- Возвращение представления верхнего уровня
И метод вызывающей стороны может выглядеть так:
final class SomeView: UIView { // 1.
required init?(coder aDecoder: NSCoder) { // 2 - storyboard initializer
super.init(coder: aDecoder)
fromNib() // 5.
}
init() { // 3 - programmatic initializer
super.init(frame: CGRect.zero) // 4.
fromNib() // 6.
}
// other methods ...
}
- SomeClass - это подкласс UIView, который загружает свое содержимое из файла SomeClass.xib. Ключевое слово final не обязательно.
- Инициализатор, когда представление используется в раскадровке (не забудьте использовать SomeClass в качестве пользовательского класса представления раскадровки).
- Инициализатор, когда представление создается программно (т. Е. "Let myView = SomeView()").
- Использование фрейма со всеми нулями, поскольку это представление выложено с помощью автоматического макета. Обратите внимание, что метод init(frame: CGRect) {..}"не создается независимо, так как автоматическая разметка используется исключительно в нашем проекте.
- & 6. Загрузка файла XIB с использованием расширения.
Кредит: Использование общего расширения в этом решении было вдохновлено ответом Роберта ниже.
Редактирование Изменение "view" на "contentView", чтобы избежать путаницы. Также изменил индекс массива на ".first".
Мой вклад:
Свифт 3 / Свифт 4
extension UIView {
class func fromNib<T: UIView>() -> T {
return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T
}
}
Тогда назовите это так:
let myCustomView: CustomView = UIView.fromNib()
..или даже:
let myCustomView: CustomView = .fromNib()
Вот расширение, которое использует дженерики для загрузки UIView
из кончика
public extension UIView {
public class func fromNib(nibNameOrNil: String? = nil) -> Self {
return fromNib(nibNameOrNil, type: self)
}
public class func fromNib<T : UIView>(nibNameOrNil: String? = nil, type: T.Type) -> T {
let v: T? = fromNib(nibNameOrNil, type: T.self)
return v!
}
public class func fromNib<T : UIView>(nibNameOrNil: String? = nil, type: T.Type) -> T? {
var view: T?
let name: String
if let nibName = nibNameOrNil {
name = nibName
} else {
// Most nibs are demangled by practice, if not, just declare string explicitly
name = nibName
}
let nibViews = NSBundle.mainBundle().loadNibNamed(name, owner: nil, options: nil)
for v in nibViews {
if let tog = v as? T {
view = tog
}
}
return view
}
public class var nibName: String {
let name = "\(self)".componentsSeparatedByString(".").first ?? ""
return name
}
public class var nib: UINib? {
if let _ = NSBundle.mainBundle().pathForResource(nibName, ofType: "nib") {
return UINib(nibName: nibName, bundle: nil)
} else {
return nil
}
}
}
Я предпочитаю это, так как это не требует никакой дополнительной настройки в кончике. Он основан на общих соглашениях об именах, поэтому, если ваш класс CustomView
, и это соответствует перо с именем: CustomView
Вы могли бы просто сделать это:
let myCustomView = CustomView.fromNib()
// or if you're unsure whether or not the nib exists
let myCustomView: CustomView? = CustomView.fromNib()
Если по какой-либо причине вам нужно быть точным в отношении имени пера, передайте строку arg:
let myCustomView = MyCustomView.fromNib("non-conventional-name")
Известные вопросы
Использование этого с закрытым классом представления, кажется, вызывает проблемы. Похоже, это проблема всей системы.
Swift 4 расширения протокола
public protocol NibInstantiatable {
static func nibName() -> String
}
extension NibInstantiatable {
static func nibName() -> String {
return String(describing: self)
}
}
extension NibInstantiatable where Self: UIView {
static func fromNib() -> Self {
let bundle = Bundle(for: self)
let nib = bundle.loadNibNamed(nibName(), owner: self, options: nil)
return nib!.first as! Self
}
}
Принятие
class MyView: UIView, NibInstantiatable {
}
Эта реализация предполагает, что Nib имеет то же имя, что и класс UIView. Ex. MyView.xib. Вы можете изменить это поведение, реализовав nibName() в MyView, чтобы возвращать имя, отличное от реализации расширения протокола по умолчанию.
В xib владельцем файла является MyView, а корневым классом представления является MyView.
использование
let view = MyView.fromNib()
Попробуйте следующий код.
var uiview :UIView?
self.uiview = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)[0] as? UIView
Редактировать:
import UIKit
class TestObject: NSObject {
var uiview:UIView?
init() {
super.init()
self.uiview = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)[0] as? UIView
}
}
Если у вас много пользовательских представлений в вашем проекте, вы можете создать класс как UIViewFromNib
Swift 2.3
class UIViewFromNib: UIView {
var contentView: UIView!
var nibName: String {
return String(self.dynamicType)
}
//MARK:
override init(frame: CGRect) {
super.init(frame: frame)
loadViewFromNib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadViewFromNib()
}
//MARK:
private func loadViewFromNib() {
contentView = NSBundle.mainBundle().loadNibNamed(nibName, owner: self, options: nil)[0] as! UIView
contentView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
contentView.frame = bounds
addSubview(contentView)
}
}
Свифт 3
class UIViewFromNib: UIView {
var contentView: UIView!
var nibName: String {
return String(describing: type(of: self))
}
//MARK:
override init(frame: CGRect) {
super.init(frame: frame)
loadViewFromNib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadViewFromNib()
}
//MARK:
func loadViewFromNib() {
contentView = Bundle.main.loadNibNamed(nibName, owner: self, options: nil)?[0] as! UIView
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
contentView.frame = bounds
addSubview(contentView)
}
}
И в каждом классе просто наследовать от UIViewFromNib
Также вы можете переопределить nibName
свойство если .xib
файл имеет другое имя:
class MyCustomClass: UIViewFromNib {
}
Я добился этого с помощью Swift с помощью следующего кода:
class Dialog: UIView {
@IBOutlet var view:UIView!
override init(frame: CGRect) {
super.init(frame: frame)
self.frame = UIScreen.mainScreen().bounds
NSBundle.mainBundle().loadNibNamed("Dialog", owner: self, options: nil)
self.view.frame = UIScreen.mainScreen().bounds
self.addSubview(self.view)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Не забудьте подключить вашу розетку XIB для просмотра розетки, определенной в swift. Вы также можете установить First Responder на свое имя класса, чтобы начать подключать любые дополнительные розетки.
Надеюсь это поможет!
Протестировано в Xcode 7 beta 4, Swift 2.0 и iOS9 SDK . Следующий код назначит xib для uiview. Вы можете использовать это пользовательское представление xib в раскадровке и получить доступ к объекту IBOutlet.
import UIKit
@IBDesignable class SimpleCustomView:UIView
{
var view:UIView!;
@IBOutlet weak var lblTitle: UILabel!
@IBInspectable var lblTitleText : String?
{
get{
return lblTitle.text;
}
set(lblTitleText)
{
lblTitle.text = lblTitleText!;
}
}
override init(frame: CGRect) {
super.init(frame: frame)
loadViewFromNib ()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadViewFromNib ()
}
func loadViewFromNib() {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "SimpleCustomView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
view.frame = bounds
view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.addSubview(view);
}
}
Доступ к customview программно
self.customView = SimpleCustomView(frame: CGRectMake(100, 100, 200, 200))
self.view.addSubview(self.customView!);
Исходный код - https://github.com/karthikprabhuA/CustomXIBSwift
Swift 4
Не забудьте написать ".first as? CustomView".
if let customView = Bundle.main.loadNibNamed("myXib", owner: self, options: nil)?.first as? CustomView {
self.view.addSubview(customView)
}
Если вы хотите использовать где угодно
Лучшее решение - ответ Роберта Гаммессона.
extension UIView {
class func fromNib<T: UIView>() -> T {
return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T
}
}
Тогда назовите это так:
let myCustomView: CustomView = UIView.fromNib()
Опираясь на вышеуказанные решения.
Это будет работать во всех пакетах проектов и не требует обобщений при вызове fromNib ().
Swift 2
extension UIView {
public class func fromNib() -> Self {
return fromNib(nil)
}
public class func fromNib(nibName: String?) -> Self {
func fromNibHelper<T where T : UIView>(nibName: String?) -> T {
let bundle = NSBundle(forClass: T.self)
let name = nibName ?? String(T.self)
return bundle.loadNibNamed(name, owner: nil, options: nil)?.first as? T ?? T()
}
return fromNibHelper(nibName)
}
}
Свифт 3
extension UIView {
public class func fromNib() -> Self {
return fromNib(nibName: nil)
}
public class func fromNib(nibName: String?) -> Self {
func fromNibHelper<T>(nibName: String?) -> T where T : UIView {
let bundle = Bundle(for: T.self)
let name = nibName ?? String(describing: T.self)
return bundle.loadNibNamed(name, owner: nil, options: nil)?.first as? T ?? T()
}
return fromNibHelper(nibName: nibName)
}
}
Можно использовать так:
let someView = SomeView.fromNib()
Или вот так:
let someView = SomeView.fromNib("SomeOtherNibFileName")
Я предпочитаю это решение (на основе ответа, если @GK100):
- Я создал XIB и класс с именем SomeView (использовал одно и то же имя для удобства и читабельности). Я основал оба на UIView.
- В XIB я изменил класс "Владелец файла" на SomeView (в инспекторе удостоверений).
- Я создал выход UIView в SomeView.swift, связав его с представлением верхнего уровня в файле XIB (для удобства его назвали "представлением"). Затем я добавил другие выходы к другим элементам управления в файле XIB по мере необходимости.
В SomeView.swift я загрузил XIB внутри
init
или жеinit:frame: CGRect
инициализатор. Нет необходимости присваивать что-либо "себе". Как только XIB загружен, все розетки подключены, включая представление верхнего уровня. Единственное, чего не хватает, это добавить вид сверху в иерархию видов:class SomeView: UIView { override init(frame: CGRect) { super.init(frame: frame) NSBundle.mainBundle().loadNibNamed("SomeObject", owner: self, options: nil) self.addSubview(self.view); // adding the top level view to the view hierarchy } required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder) NSBundle.mainBundle().loadNibNamed("SomeObject", owner: self, options: nil) self.addSubview(self.view); // adding the top level view to the view hierarchy } ... }
Обновлено для
Swift 5
:
Где-то определите ниже:
extension UIView {
public class func fromNib<T: UIView>() -> T {
let name = String(describing: Self.self);
guard let nib = Bundle(for: Self.self).loadNibNamed(
name, owner: nil, options: nil)
else {
fatalError("Missing nib-file named: \(name)")
}
return nib.first as! T
}
}
И используйте выше, например:
let view = MyCustomView.fromNib();
Хороший способ сделать это с помощью Swift - использовать enum.
enum Views: String {
case view1 = "View1" // Change View1 to be the name of your nib
case view2 = "View2" // Change View2 to be the name of another nib
func getView() -> UIView {
return NSBundle.mainBundle().loadNibNamed(self.rawValue, owner: nil, options: nil)[0] as! UIView
}
}
Тогда в вашем коде вы можете просто использовать:
let view = Views.view1.getView()
let subviewArray = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)
return subviewArray[0]
Я просто делаю так:
if let myView = UINib.init(nibName: "MyView", bundle: nil).instantiate(withOwner: self)[0] as? MyView {
// Do something with myView
}
В этом примере используется первое представление в наконечнике "MyView.xib" в основном комплекте. Но вы можете изменить либо индекс, имя пера, либо комплект (по умолчанию main).
Раньше я пробуждал представления в методе view init или создавал общие методы, как в приведенных выше решениях (которые, кстати, умны), но я больше этим не занимаюсь.
Таким образом, я могу использовать разные макеты или характеристики, сохраняя логику и код одного и того же представления.
Мне проще позволить фабричному объекту (обычно viewController, который будет использовать представление) создавать его так, как ему нужно. Иногда вам нужен владелец (обычно, когда созданный вид получает розетку, связанную с создателем), иногда нет..
Возможно, поэтому Apple не включила initFromNib
метод в своем классе UIView...
Чтобы взять пример с уровня земли, вы не знаете, как вы родились. Ты просто родился. Так же как и мнения;)
Swift 5 - чистое и простое в использовании расширение
[Копировать и вставить из производственного проекта]
//
// Refactored by Essam Mohamed Fahmi.
//
import UIKit
extension UIView
{
static var nib: UINib
{
return UINib(nibName: "\(self)", bundle: nil)
}
static func instantiateFromNib() -> Self?
{
return nib.instantiate() as? Self
}
}
extension UINib
{
func instantiate() -> Any?
{
return instantiate(withOwner: nil, options: nil).first
}
}
Применение
let myCustomView: CustomView = .instantiateFromNib()
Swift 3 версия ответа Логана
extension UIView {
public class func fromNib(nibName: String? = nil) -> Self {
return fromNib(nibName: nibName, type: self)
}
public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T {
return fromNib(nibName: nibName, type: T.self)!
}
public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T? {
var view: T?
let name: String
if let nibName = nibName {
name = nibName
} else {
name = self.nibName
}
if let nibViews = Bundle.main.loadNibNamed(name, owner: nil, options: nil) {
for nibView in nibViews {
if let tog = nibView as? T {
view = tog
}
}
}
return view
}
public class var nibName: String {
return "\(self)".components(separatedBy: ".").first ?? ""
}
public class var nib: UINib? {
if let _ = Bundle.main.path(forResource: nibName, ofType: "nib") {
return UINib(nibName: nibName, bundle: nil)
} else {
return nil
}
}
}
Вот простой и декларативный способ программной загрузки представления с использованием протокола и расширения протокола (Swift 4.2):
protocol XibLoadable {
associatedtype CustomViewType
static func loadFromXib() -> CustomViewType
}
extension XibLoadable where Self: UIView {
static func loadFromXib() -> Self {
let nib = UINib(nibName: "\(self)", bundle: Bundle(for: self))
guard let customView = nib.instantiate(withOwner: self, options: nil).first as? Self else {
// your app should crash if the xib doesn't exist
preconditionFailure("Couldn't load xib for view: \(self)")
}
return customView
}
}
И вы можете использовать это так:
// don't forget you need a xib file too
final class MyView: UIView, XibLoadable { ... }
// and when you want to use it
let viewInstance = MyView.loadFromXib()
Некоторые дополнительные соображения:
- Убедитесь, что файл xib вашего пользовательского представления имеет представление
Custom Class
набор (и выходы / действия, установленные оттуда), а не владельца файла. - Вы можете использовать этот протокол / расширение вне вашего собственного представления или внутри. Возможно, вы захотите использовать его внутри, если у вас есть другие настройки при инициализации вашего представления.
- Ваш пользовательский класс представления и файл xib должны иметь одно и то же имя.
Все, что вам нужно сделать, это вызвать метод init в вашем UIView
учебный класс.
Сделайте это так:
class className: UIView {
@IBOutlet var view: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
func setup() {
UINib(nibName: "nib", bundle: nil).instantiateWithOwner(self, options: nil)
addSubview(view)
view.frame = self.bounds
}
}
Теперь, если вы хотите добавить это представление как вспомогательное представление в контроллере представления, сделайте это так в файле view controller.swift:
self.view.addSubview(className())
Если вы хотите, чтобы подкласс Swift UIView был полностью автономным и иметь возможность создания экземпляров с использованием init или init(frame:) без раскрытия деталей реализации использования Nib, то вы можете использовать расширение протокола для достижения этой цели. Это решение позволяет избежать вложенной иерархии UIView, как это предлагается многими другими решениями.
public class CustomView: UIView {
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var valueLabel: UILabel!
public convenience init() {
self.init(frame: CGRect.zero)
}
public override convenience init(frame: CGRect) {
self.init(internal: nil)
self.frame = frame
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
fileprivate func commonInit() {
}
}
fileprivate protocol _CustomView {
}
extension CustomView: _CustomView {
}
fileprivate extension _CustomView {
// Protocol extension initializer - has the ability to assign to self, unlike
// class initializers. Note that the name of this initializer can be anything
// you like, here we've called it init(internal:)
init(internal: Int?) {
self = Bundle.main.loadNibNamed("CustomView", owner:nil, options:nil)![0] as! Self;
}
}
class func loadFromNib<T: UIView>() -> T {
let nibName = String(describing: self)
return Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)![0] as! T
}
Аналогично некоторым ответам выше, но более последовательному расширению Swift3 UIView:
extension UIView {
class func fromNib<A: UIView> (nibName name: String, bundle: Bundle? = nil) -> A? {
let bundle = bundle ?? Bundle.main
let nibViews = bundle.loadNibNamed(name, owner: self, options: nil)
return nibViews?.first as? A
}
class func fromNib<T: UIView>() -> T? {
return fromNib(nibName: String(describing: T.self), bundle: nil)
}
}
Что дает удобство загрузки класса из одноименного nib, а также из других nibs/bundle.
Вы можете сделать это через раскадровку, просто добавив соответствующие ограничения для просмотра. Вы можете сделать это легко, создав подклассы любого представления, скажем BaseView
:
Objective-C
BaseView.h
/*!
@class BaseView
@discussion Base View for getting view from xibFile
@availability ios7 and later
*/
@interface BaseView : UIView
@end
BaseView.m
#import "BaseView.h"
@implementation BaseView
#pragma mark - Public
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self prepareView];
}
return self;
}
#pragma mark - LifeCycle
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self prepareView];
}
return self;
}
#pragma mark - Private
- (void)prepareView
{
NSArray *nibsArray = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
UIView *view = [nibsArray firstObject];
view.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:view];
[self addConstraintsForView:view];
}
#pragma mark - Add constraints
- (void)addConstraintsForView:(UIView *)view
{
[self addConstraints:@[[NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0],
[NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0],
[NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:0],
[NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self attribute:NSLayoutAttributeRight
multiplier:1.0
constant:0]
]];
}
@end
Swift 4
import UIKit
class BaseView : UIView {
// MARK: - LifeCycle
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
prepareView()
}
override init(frame: CGRect) {
super.init(frame: frame)
prepareView()
}
internal class func xibName() -> String {
return String(describing: self)
}
// MARK: - Private
fileprivate func prepareView() {
let nameForXib = BaseView.xibName()
let nibs = Bundle.main.loadNibNamed(nameForXib, owner: self, options: nil)
if let view = nibs?.first as? UIView {
view.backgroundColor = UIColor.clear
view.translatesAutoresizingMaskIntoConstraints = false
addSubviewWithConstraints(view, offset: false)
}
}
}
UIView+Subview
public extension UIView {
// MARK: - UIView+Extensions
public func addSubviewWithConstraints(_ subview:UIView, offset:Bool = true) {
subview.translatesAutoresizingMaskIntoConstraints = false
let views = [
"subview" : subview
]
addSubview(subview)
var constraints = NSLayoutConstraint.constraints(withVisualFormat: offset ? "H:|-[subview]-|" : "H:|[subview]|", options: [.alignAllLeading, .alignAllTrailing], metrics: nil, views: views)
constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: offset ? "V:|-[subview]-|" : "V:|[subview]|", options: [.alignAllTop, .alignAllBottom], metrics: nil, views: views))
NSLayoutConstraint.activate(constraints)
}
}
Я предоставляю 2 варианта, как добавить ограничения - общий и на языке визуального формата - выберите любой, который вы хотите:)
Также по умолчанию предполагается, что xib
имя имеет то же имя, что и имя класса реализации. Если нет - просто поменяй xibName
параметр.
Если вы подкласса своего взгляда из BaseView
- Вы можете легко поставить любой вид и указать класс в IB.
let nibs = Bundle.main.loadNibNamed("YourView", owner: nil, options: nil)
let shareView = nibs![0] as! ShareView
self.view.addSubview(shareView)
// Используем этот класс как суперпредставление
import UIKit
class ViewWithXib: UIView {
func initUI() {}
private func xibSetup() {
let view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
addSubview(view)
initUI()
}
private func loadViewFromNib() -> UIView {
let thisName = String(describing: type(of: self))
let view = Bundle(for: self.classForCoder).loadNibNamed(thisName, owner: self, options: nil)?.first as! UIView
return view
}
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
}
// Использование:
class HeaderView: ViewWithXib {
}
let header = HeaderView() // No need to load the view from nib, It will work
Ответ Роберта Гаммессона идеален. Но когда мы пытаемся использовать его в SPM или фреймворке, он не работает.
Я изменил, как показано ниже, чтобы заставить его работать.
internal class func fromNib<T: UIView>() -> T {
return Bundle.module.loadNibNamed(String(describing: T.self), owner: self, options: nil)![0] as! T
}
Я предпочитаю расширение ниже
extension UIView {
class var instanceFromNib: Self {
return Bundle(for: Self.self)
.loadNibNamed(String(describing: Self.self), owner: nil, options: nil)?.first as! Self
}
}
Разница между этим и самым популярным расширением в том, что вам не нужно хранить его как константу или переменную.
class TitleView: UIView { }
extension UIView {
class var instanceFromNib: Self {
return Bundle(for: Self.self)
.loadNibNamed(String(describing: Self.self), owner: nil, options: nil)?.first as! Self
}
}
self.navigationItem.titleView = TitleView.instanceFromNib
Наиболее удобная реализация. Здесь вам нужно два метода, чтобы вернуться непосредственно к объекту вашего класса, а не к UIView.
- viewId помечен как класс, позволяющий переопределить
- Ваш.xib может содержать более одного представления верхнего уровня, эта ситуация также обрабатывается правильно.
extension UIView {
class var viewId: String {
return String(describing: self)
}
static func instance(from bundle: Bundle? = nil, nibName: String? = nil,
owner: Any? = nil, options: [AnyHashable : Any]? = nil) -> Self? {
return instancePrivate(from: bundle ?? Bundle.main,
nibName: nibName ?? viewId,
owner: owner,
options: options)
}
private static func instancePrivate<T: UIView>(from bundle: Bundle, nibName: String,
owner: Any?, options: [AnyHashable : Any]?) -> T? {
guard
let views = bundle.loadNibNamed(nibName, owner: owner, options: options),
let view = views.first(where: { $0 is T }) as? T else { return nil }
return view
}
}
Пример:
guard let customView = CustomView.instance() else { return }
//Here customView has CustomView class type, not UIView.
print(customView is CustomView) // true
Более мощная версия, основанная на ответе Логана
extension UIView {
public class func fromNib(nibName: String? = nil) -> Self {
return fromNib(nibName: nibName, type: self)
}
public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T {
return fromNib(nibName: nibName, type: T.self)!
}
public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T? {
var view: T?
let name: String
if let nibName = nibName {
name = nibName
} else {
name = self.nibName
}
if let nibViews = nibBundle.loadNibNamed(name, owner: nil, options: nil) {
if nibViews.indices.contains(nibIndex), let tog = nibViews[nibIndex] as? T {
view = tog
}
}
return view
}
public class var nibName: String {
return "\(self)".components(separatedBy: ".").first ?? ""
}
public class var nibIndex: Int {
return 0
}
public class var nibBundle: Bundle {
return Bundle.main
}
}
И вы можете использовать как
class BaseView: UIView {
override class var nibName: String { return "BaseView" }
weak var delegate: StandardStateViewDelegate?
}
class ChildView: BaseView {
override class var nibIndex: Int { return 1 }
}
let bundle = Bundle(for: type(of: self))
let views = bundle.loadNibNamed("template", owner: self, options: nil)
self.view.addSubview(views?[0] as! UIView)