Подтвердить что ты не робот

Загрузите UIView из nib в Swift

Вот мой код Objective-C, который я использую, чтобы загрузить nib для моего настроенного UIView:

-(id)init{

    NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:@"myXib" owner:self options:nil];
    return [subviewArray objectAtIndex:0];

}

Что такое эквивалентный код в Swift?

4b9b3361

Ответ 1

Оригинальное решение

  1. Я создал XIB и класс с именем SomeView (использовал одно и то же имя для удобства и читабельности). Я основал оба на UIView.
  2. В XIB я изменил класс "Владелец файла" на SomeView (в инспекторе удостоверений).
  3. Я создал выход UIView в SomeView.swift, связав его с представлением верхнего уровня в файле XIB (для удобства его назвали "представлением"). Затем я добавил другие выходы к другим элементам управления в файле XIB по мере необходимости.
  4. в 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
    }
}
  1. Использование сбрасываемого возвращаемого значения, поскольку возвращаемое представление в основном не представляет интереса для вызывающей стороны, когда все выходы уже подключены.
  2. Это универсальный метод, который возвращает необязательный объект типа UIView. Если не удается загрузить представление, возвращается ноль.
  3. Попытка загрузить файл XIB с тем же именем, что и текущий экземпляр класса. Если это не удается, возвращается ноль.
  4. Добавление представления верхнего уровня в иерархию представлений.
  5. В этой строке предполагается, что мы используем ограничения для компоновки представления.
  6. Этот метод добавляет верхние, нижние, ведущие и конечные ограничения - прикрепляя представление к "себе" со всех сторон (подробности см.: fooobar.com/questions/68532/...)
  7. Возвращение представления верхнего уровня

И метод вызывающей стороны может выглядеть так:

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 ...
}
  1. SomeClass - это подкласс UIView, который загружает свое содержимое из файла SomeClass.xib. Ключевое слово final не обязательно.
  2. Инициализатор, когда представление используется в раскадровке (не забудьте использовать SomeClass в качестве пользовательского класса представления раскадровки).
  3. Инициализатор, когда представление создается программно (т.е. "Let myView = SomeView()").
  4. Использование фрейма со всеми нулями, поскольку это представление выложено с помощью автоматического макета. Обратите внимание, что метод init (frame: CGRect) {..} "не создается независимо, так как автоматическая разметка используется исключительно в нашем проекте.
  5. & 6. Загрузка файла XIB с использованием расширения.

Кредит: Использование общего расширения в этом решении было вдохновлено ответом Роберта ниже.

Редактирование Изменение "view" на "contentView", чтобы избежать путаницы. Также изменил индекс массива на ".first".

Ответ 2

Мой вклад:

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
    }
}

Тогда назови это так:

let myCustomView: CustomView = UIView.fromNib()

.. или даже:

let myCustomView: CustomView = .fromNib()

Ответ 3

Здесь расширение, которое использует generics для загрузки UIView из nib

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")

Известные проблемы

Использование этого с классом private view вызывает проблемы. Это, по-видимому, проблема системы.

Ответ 4

попробуйте следующий код.

var uiview :UIView?

self.uiview = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)[0] as? UIView

Edit:

import UIKit

class TestObject: NSObject {

     var uiview:UIView?

    init()  {
        super.init()
       self.uiview = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)[0] as? UIView
    }


}

Ответ 5

Я достиг этого с помощью 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 на свое собственное имя класса, чтобы начать соединение с любыми дополнительными выходами.

Надеюсь, это поможет!

Ответ 6

Протестировано в 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);



    }


}

Доступ к пользовательскому просмотру программно

self.customView =  SimpleCustomView(frame: CGRectMake(100, 100, 200, 200))
        self.view.addSubview(self.customView!);

Исходный код - https://github.com/karthikprabhuA/CustomXIBSwift

Ответ 7

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()

Ответ 8

Если у вас много пользовательских представлений в вашем проекте, вы можете создать класс типа 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)
    }
}

Swift 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 {

}

Ответ 9

Основываясь на вышеупомянутых решениях.

Это будет работать во всех пакетах проектов и не будет необходимости генерировать при вызове 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)
    }
}

Swift 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")

Ответ 10

Хороший способ сделать это с помощью 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).first as? UIView
    }
}

Тогда в вашем коде вы можете просто использовать:

let view = Views.view1.getView()

Ответ 11

let subviewArray = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)
return subviewArray[0]

Ответ 12

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()

Ответ 13

Я предпочитаю это решение (на основе ответа, если @GK100):

  • Я создал XIB и класс с именем SomeView (использовал одно и то же имя для удобства и удобочитаемости). Я основывался как на UIView.
  • В XIB я изменил класс "Владелец файла" на SomeView (в инспекторе идентификации).
  • Я создал UIView-выход в SomeView.swift, связав его с видом верхнего уровня в XIB файле (назвав его "view" для удобства). Затем я добавил другие источники в другие элементы управления в файле 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
      }
    
    
      ...
    }
    

Ответ 14

Swift 3 версия ответа Logan

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
        }
    }
}

Ответ 15

Все, что вам нужно сделать, это метод вызова 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
    }
}

Теперь, если вы хотите добавить это представление в качестве подвидного представления в контроллере вида, сделайте это так в файле controller.swift:

self.view.addSubview(className())

Ответ 16

Я просто так делаю:

if let myView = UINib.init(nibName: "MyView", bundle: nil).instantiate(withOwner: self)[0] as? MyView {
// Do something with myView
}

В этом примере используется первое представление в nib "MyView.xib" в основном пакете. Но вы можете изменить либо индекс, имя элемента, либо набор (по умолчанию основной).

Я использовал пробуждение представлений в методе init init или создавал общие методы, как в вышеприведенных решениях (которые, кстати, умудрены), но я больше этого не делаю.

Таким образом, я могу использовать разные макеты или черты, сохраняя одну и ту же логику и код представления.

Мне легче позволить объект factory (обычно viewController, который будет использовать представление) создавать его по мере необходимости. Иногда вам нужен владелец (обычно, когда созданный вид получил выход, связанный с создателем), иногда нет.

Вероятно, почему Apple не включила метод initFromNib в свой класс UIView...

Чтобы взять пример уровня земли, вы не знаете, как вы рождаетесь. Вы только что родились. Точно так же вид;)

Ответ 17

Вот простой и декларативный способ программной загрузки представления с использованием протокола и расширения протокола (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()

Некоторые дополнительные соображения:

  1. Убедитесь, что в вашем пользовательском xib файле установлен Custom Class (и оттуда установлены выходы/действия), а не владелец файла.
  2. Вы можете использовать этот протокол/расширение вне вашего собственного представления или внутри. Возможно, вы захотите использовать его внутри, если у вас есть другие настройки при инициализации вашего представления.
  3. Ваш пользовательский класс представления и файл xib должны иметь одно и то же имя.

Ответ 18

Как и в некоторых из приведенных выше ответов, но более последовательное расширение UIView Swift3:

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)
    }
}

Это дает возможность загружать класс из собственного имени, но также из других nibs/bundles.

Ответ 19

Вы можете сделать это через раскадровку, просто добавив соответствующие ограничения для просмотра. Вы можете сделать это легко, BaseView подклассы любого представления, скажем, 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.

Ответ 20

class func loadFromNib<T: UIView>() -> T {
    let nibName = String(describing: self)
    return Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)![0] as! T
}

Ответ 21

Более мощная версия, основанная на ответе Logan

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 }
}

Ответ 22

Если вы хотите, чтобы подкласс 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;
    }
}

Ответ 23

Наиболее удобная реализация. Здесь вам нужно два метода, чтобы вернуться непосредственно к объекту вашего класса, а не к UIView.

  1. viewId помечен как класс, позволяющий переопределить
  2. Ваш .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

Ответ 24

Можно использовать протокол и общий для этого.

protocol NibLoader: class {}

extension NibLoader {
    static func loadFromNib<T: ClassNameExpressible>(_ name: String? = T.className, bundle: Bundle? = .main) -> T {
        guard let nibName = name else {
            fatalError("no nib name found")
        }

        guard let firstObject = UINib(nibName: nibName, bundle: bundle)
            .instantiate(withOwner: T.self, options: nil)
            .first else {
                fatalError("no nib first object found")
        }

        // swiftlint:disable:next force_cast
        return firstObject as! T
        // swiftlint:disable:previous force_cast
    }
}

extension UIViewController: NibLoader {}
extension UIView: NibLoader {}