Как инициализировать / создать экземпляр пользовательского класса UIView с файлом XIB в Swift

143

У меня есть класс, MyClassкоторый является подклассом UIView, который я хочу инициализировать с помощью XIBфайла. Я не уверен, как инициализировать этот класс с помощью файла xib с именемView.xib

class MyClass: UIView {

    // what should I do here? 
    //init(coder aDecoder: NSCoder) {} ?? 
}
Стивен Фокс
источник
5
см. полный образец исходного кода для iOS9 Swift 2.0 github.com/karthikprabhuA/CustomXIBSwift и связанный поток stackoverflow.com/questions/24857986/…
karthikPrabhu Alagu

Ответы:

271

Я протестировал этот код, и он отлично работает:

class MyClass: UIView {        
    class func instanceFromNib() -> UIView {
        return UINib(nibName: "nib file name", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as UIView
    }    
}

Инициализируйте представление и используйте его, как показано ниже:

var view = MyClass.instanceFromNib()
self.view.addSubview(view)

ИЛИ

var view = MyClass.instanceFromNib
self.view.addSubview(view())

ОБНОВЛЕНИЕ Swift> = 3.x и Swift> = 4.x

class func instanceFromNib() -> UIView {
    return UINib(nibName: "nib file name", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
}
Эзимет
источник
1
Это должно быть var view = MyClass.instanceFromNib()&, self.view.addSubview(view)а не var view = MyClass.instanceFromNib& self.view.addSubview(view()). Просто небольшое предложение по улучшению ответа :)
Логан
1
В моем случае вид инициализируется позже! если это self.view.addSubview (view), вид должен быть var view = MyClass.instanceFromNib ()
Ezimet
2
@Ezimet, что насчет IBActions внутри этого представления. где с ними обращаться. Как будто у меня есть кнопка в моем представлении (xib), как обрабатывать событие щелчка IBAction этой кнопки.?
Кадир Хуссейн
6
Должен ли он возвращать MyClass вместо просто UIView?
Кесон Се
3
IBoutlets не работают в этом подходе ... Я получаю: «Этот класс не соответствует кодированию ключевого значения для ключа»
Радек Вильчак
84

Решение Сэма уже отличное, несмотря на то, что оно не учитывает различные пакеты (на помощь приходит NSBundle: forClass) и требует ручной загрузки, то есть ввода кода.

Если вам нужна полная поддержка ваших выходов Xib, разных пакетов (используйте в рамках!) И получите хороший предварительный просмотр в раскадровке, попробуйте следующее:

// NibLoadingView.swift
import UIKit

/* Usage: 
- Subclass your UIView from NibLoadView to automatically load an Xib with the same name as your class
- Set the class name to File's Owner in the Xib file
*/

@IBDesignable
class NibLoadingView: UIView {

    @IBOutlet weak var view: UIView!

    override init(frame: CGRect) {
        super.init(frame: frame)
        nibSetup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        nibSetup()
    }

    private func nibSetup() {
        backgroundColor = .clearColor()

        view = loadViewFromNib()
        view.frame = bounds
        view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        view.translatesAutoresizingMaskIntoConstraints = true

        addSubview(view)
    }

    private func loadViewFromNib() -> UIView {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: String(self.dynamicType), bundle: bundle)
        let nibView = nib.instantiateWithOwner(self, options: nil).first as! UIView

        return nibView
    }

}

Используйте свой xib как обычно, т.е. подключите Outlets к File Owner и установите File Owner class в свой собственный класс.

Использование: просто создайте подкласс своего собственного класса View из NibLoadingView и установите имя класса на File's Owner в файле Xib.

Дополнительный код больше не требуется.

Кредиты, причитающиеся к оплате: Разветвлен с небольшими изменениями от DenHeadless на GH. Моя суть: https://gist.github.com/winkelsdorf/16c481f274134718946328b6e2c9a4d8

Фредерик Винкельсдорф
источник
9
Это решение тормозит выходные соединения (потому что оно добавляет загруженное представление как подпредставление), а вызов nibSetupиз init?(coder:)вызовет бесконечную рекурсию при встраивании NibLoadingViewв XIB.
redent84
3
@ redent84 Спасибо за ваш комментарий и голос против. Если у вас есть второй взгляд, он должен заменить предыдущий SubView (новая переменная экземпляра не указана). Вы правы насчет бесконечной рекурсии, которую следует опустить, если вы боретесь с IB.
Фредерик Винкельсдорф,
1
Как уже упоминалось, «подключите Outlets к File Owner и установите File Owner class в свой собственный класс». подключите выходы к Владельцу Файла
Юсуф X
1
Мне всегда неудобно использовать этот метод для загрузки представления из xib. По сути, мы добавляем представление, которое является подклассом класса A, в представление, которое также является подклассом класса A. Разве нет способа предотвратить эту итерацию?
Prajeet Shrestha
1
@PrajeetShrestha Это, вероятно, связано с тем, что nibSetup () переопределяет цвет фона на .clearColor()- после загрузки его из раскадровки. Но он должен работать, если вы делаете это с помощью кода после его создания. В любом случае, как уже говорилось, еще более элегантный подход - это протокол на основе. Конечно, вот вам ссылка: github.com/AliSoftware/Reusable . Сейчас я использую аналогичный подход в отношении UITableViewCells (который я реализовал до того, как обнаружил этот действительно полезный проект). hth!
Frederik Winkelsdorf
78

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

import UIKit

protocol UIViewLoading {}
extension UIView : UIViewLoading {}

extension UIViewLoading where Self : UIView {

  // note that this method returns an instance of type `Self`, rather than UIView
  static func loadFromNib() -> Self {
    let nibName = "\(self)".characters.split{$0 == "."}.map(String.init).last!
    let nib = UINib(nibName: nibName, bundle: nil)
    return nib.instantiateWithOwner(self, options: nil).first as! Self
  }

}
Сэм
источник
4
Это лучшее решение, чем выбранный ответ, поскольку нет необходимости в приведении типов, и его также можно будет повторно использовать в любых других подклассах UIView, которые вы создадите в будущем.
user3344977 01
3
Я пробовал это с Swift 2.1 и Xcode 7.2.1. Некоторое время он работал, а в других непредсказуемо зависал из-за блокировки мьютекса. Последние две строки, используемые непосредственно в коде, работали каждый раз, при этом последняя строка была изменена наvar myView = nib.instantiate... as! myViewType
Пол Линсей
@ jr-root-cs Ваша правка содержала опечатки / ошибки, мне пришлось откатить ее. И в любом случае, пожалуйста, не добавляйте код к существующим ответам. Вместо этого сделайте комментарий; или добавьте свою версию в свой ответ . Спасибо.
Эрик Айя
Я опубликовал код, который я открыл и протестировал в своем проекте с использованием Swift 3(XCode 8.0 beta 6) без проблем. Опечатка была Swift 2. Почему это должен быть другой ответ, если это хороший ответ, и пользователям, вероятно, понравится поиск, каковы изменения, когда они будут использовать XC8
jr.root.cs 06
1
@ jr.root.cs Да, это хороший ответ, поэтому никто не должен его изменять. Это ответ Сэма, а не ваш. Если вы хотите прокомментировать это, оставьте комментарий; если вы хотите опубликовать новую / обновленную версию, сделайте это в своем собственном сообщении . Правки предназначены для исправления опечаток / отступов / тегов, чтобы не добавлять ваши версии в сообщения других людей. Спасибо.
Эрик Айя
38

И это ответ Фредерика на Swift 3.0.

/*
 Usage:
 - make your CustomeView class and inherit from this one
 - in your Xib file make the file owner is your CustomeView class
 - *Important* the root view in your Xib file must be of type UIView
 - link all outlets to the file owner
 */
@IBDesignable
class NibLoadingView: UIView {

    @IBOutlet weak var view: UIView!

    override init(frame: CGRect) {
        super.init(frame: frame)
        nibSetup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        nibSetup()
    }

    private func nibSetup() {
        backgroundColor = .clear

        view = loadViewFromNib()
        view.frame = bounds
        view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.translatesAutoresizingMaskIntoConstraints = true

        addSubview(view)
    }

    private func loadViewFromNib() -> UIView {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
        let nibView = nib.instantiate(withOwner: self, options: nil).first as! UIView

        return nibView
    }
}
Махмуд С
источник
31

Универсальный способ загрузки представления из xib:

Пример:

let myView = Bundle.loadView(fromNib: "MyView", withType: MyView.self)

Реализация:

extension Bundle {

    static func loadView<T>(fromNib name: String, withType type: T.Type) -> T {
        if let view = Bundle.main.loadNibNamed(name, owner: nil, options: nil)?.first as? T {
            return view
        }

        fatalError("Could not load view with type " + String(describing: type))
    }
}
Максим Мартынов
источник
Лучший ответ мне в качестве выходного представления как Тип подкласса
UIView
27

Ответ Swift 3: в моем случае я хотел иметь выход в моем пользовательском классе, который я мог бы изменить:

class MyClassView: UIView {
    @IBOutlet weak var myLabel: UILabel!

    class func createMyClassView() -> MyClass {
        let myClassNib = UINib(nibName: "MyClass", bundle: nil)
        return myClassNib.instantiate(withOwner: nil, options: nil)[0] as! MyClassView
    }
}

Находясь в .xib, убедитесь, что поле Custom Class - MyClassView. Не беспокойтесь о владельце файла.

Убедитесь, что Custom Class - MyClassView

Также убедитесь, что вы подключили розетку в MyClassView к метке: Выход для myLabel

Чтобы создать его:

let myClassView = MyClassView.createMyClassView()
myClassView.myLabel.text = "Hello World!"
Джереми
источник
Я возвращаюсь "загрузил перо" MyClas ", но выход для просмотра не был установлен." ", Если владелец не установлен
jcharch
25

Swift 4

В моем случае мне нужно передать данные в это настраиваемое представление, поэтому я создаю статическую функцию для создания экземпляра представления.

  1. Создать расширение UIView

    extension UIView {
        class func initFromNib<T: UIView>() -> T {
            return Bundle.main.loadNibNamed(String(describing: self), owner: nil, options: nil)?[0] as! T
        }
    }
    
  2. Создать MyCustomView

    class MyCustomView: UIView {
    
        @IBOutlet weak var messageLabel: UILabel!
    
        static func instantiate(message: String) -> MyCustomView {
            let view: MyCustomView = initFromNib()
            view.messageLabel.text = message
            return view
        }
    }
    
  3. Установите собственный класс в MyCustomView в файле .xib. При необходимости подключите розетку как обычно. введите описание изображения здесь

  4. Реальный вид

    let view = MyCustomView.instantiate(message: "Hello World.")
    
мнемоника23
источник
если в настраиваемом представлении есть кнопки, как мы можем обрабатывать их действия в разных контроллерах представления?
Jayant Rawat
вы можете использовать протокол-делегат. посмотрите здесь stackoverflow.com/questions/29602612/… .
mnemonic23
Не удалось загрузить изображение в xib, возникает следующая ошибка. Не удалось загрузить изображение « IBBrokenImage », на которое ссылается перо в пакете с идентификатором «test.Testing»
Рави Раджа Джангид
1

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

let customView = UINib(nibName:"CustomView",bundle:.main).instantiate(withOwner: nil, options: nil).first as! UIView
customView.frame = self.view.bounds
self.view.addSubview(customView)
Прадип Редди Кипа
источник
-4
override func draw(_ rect: CGRect) 
{
    AlertView.layer.cornerRadius = 4
    AlertView.clipsToBounds = true

    btnOk.layer.cornerRadius = 4
    btnOk.clipsToBounds = true   
}

class func instanceFromNib() -> LAAlertView {
    return UINib(nibName: "LAAlertView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! LAAlertView
}

@IBAction func okBtnDidClicked(_ sender: Any) {

    removeAlertViewFromWindow()

    UIView.animate(withDuration: 0.4, delay: 0.0, options: .allowAnimatedContent, animations: {() -> Void in
        self.AlertView.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)

    }, completion: {(finished: Bool) -> Void in
        self.AlertView.transform = CGAffineTransform.identity
        self.AlertView.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
        self.AlertView.isHidden = true
        self.AlertView.alpha = 0.0

        self.alpha = 0.5
    })
}


func removeAlertViewFromWindow()
{
    for subview  in (appDel.window?.subviews)! {
        if subview.tag == 500500{
            subview.removeFromSuperview()
        }
    }
}


public func openAlertView(title:String , string : String ){

    lblTital.text  = title
    txtView.text  = string

    self.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
    appDel.window!.addSubview(self)


    AlertView.alpha = 1.0
    AlertView.isHidden = false

    UIView.animate(withDuration: 0.2, animations: {() -> Void in
        self.alpha = 1.0
    })
    AlertView.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)

    UIView.animate(withDuration: 0.3, delay: 0.2, options: .allowAnimatedContent, animations: {() -> Void in
        self.AlertView.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)

    }, completion: {(finished: Bool) -> Void in
        UIView.animate(withDuration: 0.2, animations: {() -> Void in
            self.AlertView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)

        })
    })


}
Мадан Кумават
источник
плохо отформатированный код, без объяснения, отсюда и голос против.
J. Doe
Какое отношение все это имеет к вопросу?
Эшли Миллс