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


148

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

-(id)init{

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

}

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

Ответы:


172

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

  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. Этот метод добавляет верхние, нижние, ведущие и конечные ограничения - прикрепляя представление к «себе» со всех сторон (подробности см .: https://stackoverflow.com/a/46279424/2274829 )
  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".


7
Установка имени класса, чтобы File's Ownerпопасть в точку ... Спасибо!
Авиэль Гросс

13
UIView не имеет свойства view, поэтому вызов self.view вызывает ошибку
Nastya Gorban

4
@NastyaGorban self.view фактически ссылается в этом случае на свойство розетки (называемое «представление»), которое GK100 связывает из представления верхнего уровня в .xib с SomeView.swift. Если вы не добавите эту розетку, вы получите ошибку, так как представление отсутствует. "собственность в классах NSView, как вы говорите.
Ausiàs

3
Я получаю сбой при загрузке пера (loadNibNamed). Использование Xcode 6.3 и Swift
karthikPrabhu Alagu

11
вызов fromNib()изнутри init(coder aDecoder: NSCoder)создает бесконечный цикл, так как загрузка Nib внутри fromNib()метода вызывает:init(coder aDecoder: NSCoder)
Мэтью Коули

334

Мой вклад:

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

20
Лучший ответ на сегодняшний день.
CodyMace

7
Лучший ответ здесь. Чисто и просто
Marquavious Draggon

3
@YuchenZhong - я предпочитаю [0], чем .first, так как это возвращает необязательный. Если вы заставите его развернуть, это не будет безопаснее. ... и возникает вопрос: почему бы не вернуть опциональное решение, как некоторые из приведенных выше решений? Ответ: можно. Ничего плохого в этом нет. Но ... если он когда-нибудь вернет ноль, имя класса xib / не будет совпадать. Это ошибка разработчика, и ее нужно поймать немедленно и никогда не делать в производство. Здесь я бы предпочел, чтобы приложение перестало работать, оставив его в каком-то странном состоянии. Просто мои 2 цента / предпочтение.
Роберт Гаммессон

1
@allenlinli - метод является статическим расширением UIView, как предполагается для CustomView. Это работает, потому что компилятор выводит тип, используя явную аннотацию типа. Поскольку CustomView является подклассом UIView и тип уже выведен, нам не нужно выводить его снова, поэтому UIView можно опустить, как показано во втором примере. Сказав это, вы, очевидно, могли бы сделать звонок так же, как и его.
Роберт Гаммессон

3
Это решение не сработало для меня, когда в .xib был пользовательский вид. Я бы посоветовал исправить эту часть так: возвращаем Bundle.main.loadNibNamed (String (описывающий: self), owner: nil, options: nil)! [0] as! Т
Надзея

79

Теперь возможность -> Selfбыстрого возврата помогает немного упростить это. Последний подтвержден на Swift 5.

extension UIView {
    class func fromNib(named: String? = nil) -> Self {
        let name = named ?? "\(Self.self)"
        guard
            let nib = Bundle.main.loadNibNamed(name, owner: nil, options: nil)
            else { fatalError("missing expected nib named: \(name)") }
        guard
            /// we're using `first` here because compact map chokes compiler on
            /// optimized release, so you can't use two views in one nib if you wanted to
            /// and are now looking at this
            let view = nib.first as? Self
            else { fatalError("view of type \(Self.self) not found in \(nib)") }
        return view
    }
}

Если ваш .xibфайл и подкласс имеют одинаковое имя, вы можете использовать:

let view = CustomView.fromNib()

Если у вас есть собственное имя, используйте:

let view = CustomView.fromNib(named: "special-case")

НОТА:

Если вы получаете сообщение об ошибке «представление типа YourType не найдено в ..», значит, вы не установили класс представления в .xibфайле

Выберите свое представление в .xibфайле, нажмите cmd + opt + 4и на classвходе введите свой класс


1
Я не могу заставить это работать под XCode 7.1 beta 3 - не уверен, что это бета-версия, но в основном я пытался создать собственный вид непосредственно из кончика в Swift, и я всегда получал один и тот же результат: класс, который он создает не соответствует стандартам KVC для торговых точек. Не уверен, что я что-то не так делаю, но мой класс довольно прост, а владелец файла прав. Я делал это все время под Objective-C.
Эшелон

1
@Logan это на самом деле не связано с вашим кодом, но пользовательские представления imo должны поддерживать загрузку из Storyboard / XIB. Мой комментарий был просто уведомлением для тех, кто хочет создавать такие взгляды
Никита Взял

1
Примечание. У меня все еще есть проблема с использованием второй формы вызова этой функции, а именно let myCustomView = UIView.fromNib() as? CustomView. В этом случае скорее T.selfразрешается UIView, чем CustomViewи не удается найти перо. Я не уверен, почему это - возможно, выводимый тип для letсредств, вызываемых как функция UIView?
Эшелон

2
Важно отметить, что попытка использовать владельца файла для подключения к розеткам (как мы это делали в старые добрые времена) приведет к сбою. В IB владелец файла должен быть нулевым / пустым, а выходы должны быть подключены к представлению.
Эшелон

1
@ Эшелон, ты спас мой день !!! Я соединил свои розетки, используя владельца файла, и он не работал, а вместо этого работал вид.
Ян Шлорф

19

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

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
    }


}

Мне нужно вызвать этот метод внутри метода инициализации объекта, который является init () в Swift.
Bagusflyer

12

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

3
Это, безусловно, самое элегантное и простое решение, и я понятия не имею, почему это не принятый ответ!
horseshoe7

@ horseshoe7 потому что написано через 4 года после вопроса.
Freeubi

11

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

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


10

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


9

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

}

8

Опираясь на вышеуказанные решения.

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

6

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

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

Но в init () Swift, нет возвращаемого значения. Я забыл упомянуть, что мне нужно вызвать loadNibNamed при инициализации UIView.
Bagusflyer

Что вы имеете в виду «нет возвращаемого значения»? selfнеявно возвращается из всех initметодов ...
Grimxn

1
Я имею в виду, что я вызываю loadNibNamed внутри метода init. загруженный UIView присваивается self в ObjC. Но в скором времени это не так.
Bagusflyer

5

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

2
Обратите внимание, что если вы сделаете это с пустым файлом пера или файлом пера с корневым узлом none UIView, вы потерпите крах, так как вы не проверяете размер массива или элемент в позиции 0.
Мэтью Коули,

4

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

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

Я предпочитаю использовать init с фреймом, поэтому я вырвал это! одна вещь, которую нужно отметить ... добавьте self.view.frame = frame, если вы хотите, чтобы вид соответствовал кадру, который вы передаете
Mike E

3

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

3

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

2

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

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

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

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

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

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

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

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


2

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

это отличный ответ, но что-то не так, я его отредактирую.
C0mrade

Это способ, которым я реализовал. Но вы можете импровизировать. Заранее спасибо @ C0mrade
Alap Anerao

1

Подобно некоторым ответам выше, но более последовательному расширению 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.


1

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


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

0

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

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

0

Наиболее удобная реализация. Здесь вам нужно два метода, чтобы вернуться непосредственно к объекту вашего класса, а не к 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

0

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

0
  let bundle = Bundle(for: type(of: self))
   let views = bundle.loadNibNamed("template", owner: self, options: nil)
    self.view.addSubview(views?[0] as! UIView)

Код только ответы не рекомендуется. Пожалуйста, добавьте некоторые объяснения относительно того, как это решает проблему, или как это отличается от существующих ответов. Из обзора
Ник

0

Я предпочитаю ниже расширение

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

Какую версию Xcode вы используете? Пожалуйста, убедитесь, что вы используете последнюю версию XCode. У меня отлично работает с XCode 11.5 (последняя версия на дату).
iCoder

0
    let nibs = Bundle.main.loadNibNamed("YourView", owner: nil, options: nil)
    let shareView = nibs![0] as! ShareView
    self.view.addSubview(shareView)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.