Як ініціалізувати / інстанціювати користувацький клас UIView з файлом XIB у Swift


139

У мене є клас, MyClassякий називається підкласом UIView, який я хочу ініціалізувати з XIBфайлом. Я не впевнений, як ініціалізувати цей клас із файлом xibView.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

Відповіді:


266

Я перевірив цей код, і він чудово працює:

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

UPDATE 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.instanFromNib ()
Ezimet

2
@Ezimet, як щодо IBActions всередині цього погляду. де з ними поводитися. Можливо, у мене є кнопка (xib), як поводитися з подією IBAction цієї кнопки.
Кадір Хуссейн

6
Чи повинен він повертати "MyClass" замість просто UIView?
Kesong Xie

2
IBoutlets не працює при такому підході ... Я розумію: "цей клас не відповідає кодованню ключових значень для ключа"
Радек Вільчак

82

Рішення Сама вже чудово, незважаючи на те, що він не враховує різні пакети (NSBundle: forClass приходить на допомогу) і вимагає завантаження вручну, він же вводить код.

Якщо ви хочете отримати повну підтримку своїх Xib Outlets, різних пакетів (використовуйте в рамках!) Та отримайте приємний попередній перегляд у Storyboard, спробуйте це:

// 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 для власного класу.

Використання: Підклас свій власний клас View від NibLoadingView і встановити ім'я класу для файлу власника в файлі XIb

Більше не потрібен додатковий код.

Кредити, на які належить отримання кредиту: це направлено з незначними змінами від DenHeadless на GH. My Gist: https://gist.github.com/winkelsdorf/16c481f274134718946328b6e2c9a4d8


8
Це рішення гальмує з'єднання розетки (оскільки додає завантажений вид у вигляді підзагляду) і виклик nibSetupз нього init?(coder:)спричинить нескінченну рекурсію, якщо вбудувати NibLoadingViewXIB.
redent84

3
@ redent84 Дякуємо за ваш коментар та відгук. Якщо у вас є другий вигляд, він повинен замінити попередній SubView (нова змінна екземпляр не наводиться). Ви маєте рацію щодо нескінченної рекурсії, яку слід опустити, якщо бороєтесь із ІБ.
Фредерік Вінкельсдорф

1
Як згадувалося, "підключіть розетки до власника файлу та встановіть клас власника файлів для власного класу." підключіть торгові точки до власника файлів
Yusuf X

1
Мені завжди незручно використовувати цей метод для завантаження подання з xib. В основному ми додаємо подання, що є підкласом класу A, у погляді, який також є підкласом класу A. Чи не існує способу запобігти цій ітерації?
Prajeet Shrestha

1
@PrajeetShrestha Це, ймовірно, пов’язано з nibSetup (), що переосмислює колір тла на .clearColor()- після завантаження його з дошки розкадрування. Але це має спрацювати, якщо ви зробите це за кодом після його екземпляра. У будь-якому разі, як сказано, ще більш елегантним є підхід, заснований на протоколі. Так що обов'язково, ось вам посилання: github.com/AliSoftware/Reusable . Зараз я використовую аналогічний підхід щодо UITableViewCells (який я реалізував ще до того, як виявив цей дійсно корисний проект). hth!
Фредерік Вінкельсдорф

77

З 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

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

1
@ jr.root.cs Так, ця відповідь хороша, тому ніхто не повинен її змінювати. Це відповідь Сема, а не ваша. Якщо ви хочете прокоментувати це, залиште коментар; якщо ви хочете опублікувати нову / оновлену версію, зробіть це у своєму власному дописі . Зміни призначені для виправлення помилок друку / відступу / тегів, щоб не додавати ваші версії до публікацій інших людей. Дякую.
Ерік Айя

37

І це відповідь Фредеріка на 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
jfgrang

26

Відповідь 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, переконайтеся, що поле Спеціальний клас - MyClassView. Не турбуйтеся з власником файлу.

Переконайтеся, що спеціальний клас - MyClassView

Також переконайтеся, що ви підключили розетку в MyClassView до мітки: Розетка для myLabel

Щоб створити це:

let myClassView = MyClassView.createMyClassView()
myClassView.myLabel.text = "Hello World!"

Я повертаюся "завантажив ручку" MyClas ", але розетка для перегляду не була встановлена." "Якщо власник не встановлений
jcharch

22

Швидкий 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.")

якщо в користувальницькому перегляді є кнопки, як ми можемо обробляти їх дії в різних контролерах перегляду?
jayant rawat

ви можете використовувати протокол-делегат. подивіться тут stackoverflow.com/questions/29602612/… .
mnemonic23

Не вдалося завантажити зображення в xib, отримавши таку помилку. Не вдалося завантажити зображення " IBBrokenImage ", на яке посилається нитка в комплекті з ідентифікатором "test.Testing"
Ravi Raja Jangid

-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

Яке все це стосується питання?
Ешлі Міллс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.