Загрузите UIView из nib в Swift
вот мой Objective-C код, который я использую, чтобы загрузить перо для моего настроенного UIView
:
-(id)init{
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:@"myXib" owner:self options:nil];
return [subviewArray objectAtIndex:0];
}
Что такое эквивалентный код в Swift?
22 ответа:
Оригинальная Решение
- Я создал XIB и класс с именем SomeView (используется то же имя для удобство и читабельность). Я основывал оба на UIView.
- в XIB я изменил класс "владелец файла" на SomeView (в инспекторе идентификации).
- Я создал выход UIView в SomeView.swift, связывая его с представлением верхнего уровня в файле XIB (для удобства назвал его "view"). Затем я добавил другие выходы к другим элементам управления в XIB файл по мере необходимости.
- в SomeView.swift, я загрузил XIB внутри инициализатора "init with code". Не нужно ничего приписывать "себе". Как только XIB загружен, все розетки подключены, включая вид верхнего уровня. Единственное, чего не хватает, это добавить вид сверху в иерархию представлений:
.
class SomeView: UIView { required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder) NSBundle.mainBundle().loadNibNamed("SomeView", owner: self, options: nil) self.addSubview(self.view); // adding the top level view to the view hierarchy } ... }
обратите внимание, что таким образом я получаю класс, который загружается из nib. Затем я мог бы использовать SomeView в качестве класса всякий раз, когда UIView может использоваться в проект (в interface builder или программно).
обновление-использование синтаксиса Swift 3
загрузка xib в следующем расширении записывается как метод экземпляра, который затем может использоваться инициализатором, подобным приведенному выше:
extension UIView { @discardableResult // 1 func fromNib<T : UIView>() -> T? { // 2 guard let contentView = Bundle(for: type(of: self)).loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? T else { // 3 // xib not loaded, or its top view is of the wrong type return nil } self.addSubview(contentView) // 4 contentView.translatesAutoresizingMaskIntoConstraints = false // 5 contentView.layoutAttachAll(to: self) // 6 return contentView // 7 } }
- использование отбрасываемого возвращаемого значения, поскольку возвращаемое представление в основном не представляет интереса для вызывающего абонента, когда все розетки уже подключены.
- это универсальный метод, который возвращает необязательный объект типа UIView. Если он не загружает представление, он возвращает nil.
- попытка загрузить XIB-файл с тем же именем, что и текущий экземпляр класса. Если это не удается, возвращается nil.
- добавление представления верхнего уровня в иерархию представлений.
- эта строка предполагает, что мы используем ограничения на макет посмотреть.
- этот метод добавляет верхние, нижние, ведущие и конечные ограничения-прикрепление представления к "себе" со всех сторон (см.: https://stackoverflow.com/a/46279424/2274829 дополнительные сведения)
- возврат вида верхнего уровня
и вызывающий метод может выглядеть так:
final class SomeView: UIView { // 1. required init?(coder aDecoder: NSCoder) { // 2 - storyboard initializer super.init(coder: aDecoder) fromNib() // 5. } init() { // 3 - programmatic initializer super.init(frame: CGRect.zero) // 4. fromNib() // 6. } // other methods ... }
- SomeClass-это подкласс UIView, который загружает свое содержимое из SomeClass.файл xib. Ключевое слово" final " является необязательным.
- инициализатор, когда представление используется в раскадровке (не забудьте использовать SomeClass в качестве пользовательского класса раскадровки вид.)
- инициализатор, когда представление создается программно (т. е.: "let myView = SomeView ()").
- использование кадра все нули, так как этот вид выложен с помощью автоматической компоновки. Обратите внимание, что " init(frame: CGRect) {..} "метод не создается самостоятельно, так как авто-макет используется исключительно в нашем проекте.
- & 6. Загрузки xib файл с расширением.
кредит: использование общего расширения в этом решении было вдохновлено Ответ Роберта ниже.
Edit Изменение "вид" на "contentView", чтобы избежать путаницы. Также изменен индекс массива на ".сначала."
мой вклад:
Swift 3 / Swift 4
extension UIView { class func fromNib<T: UIView>() -> T { return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T } }
тогда назовем это так:
let myCustomView: CustomView = UIView.fromNib()
..или даже:
let myCustomView: CustomView = .fromNib()
вот расширение, которое использует дженерики для загрузки
UIView
от нибpublic extension UIView { public class func fromNib(nibNameOrNil: String? = nil) -> Self { return fromNib(nibNameOrNil, type: self) } public class func fromNib<T : UIView>(nibNameOrNil: String? = nil, type: T.Type) -> T { let v: T? = fromNib(nibNameOrNil, type: T.self) return v! } public class func fromNib<T : UIView>(nibNameOrNil: String? = nil, type: T.Type) -> T? { var view: T? let name: String if let nibName = nibNameOrNil { name = nibName } else { // Most nibs are demangled by practice, if not, just declare string explicitly name = nibName } let nibViews = NSBundle.mainBundle().loadNibNamed(name, owner: nil, options: nil) for v in nibViews { if let tog = v as? T { view = tog } } return view } public class var nibName: String { let name = "\(self)".componentsSeparatedByString(".").first ?? "" return name } public class var nib: UINib? { if let _ = NSBundle.mainBundle().pathForResource(nibName, ofType: "nib") { return UINib(nibName: nibName, bundle: nil) } else { return nil } } }
Я предпочитаю это, так как он не требует каких-либо дополнительных настроек в наконечнике. Он основан на общих соглашениях об именах, поэтому, если ваш класс
CustomView
, и он соответствует наконечнику с именем:CustomView
, вы могли бы просто сделать это:let myCustomView = CustomView.fromNib() // or if you're unsure whether or not the nib exists let myCustomView: CustomView? = CustomView.fromNib()
если по какой-либо причине вам нужно уточнить имя пера, передайте строку arg:
let myCustomView = MyCustomView.fromNib("non-conventional-name")
Известные Проблемы
использование этого с частным классом представления, похоже, вызывает проблемы. Это, по-видимому, общесистемная проблема.
попробуйте выполнить следующий код.
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 } }
Я достиг этого с помощью 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 view выход посмотреть выход определен в swift. Вы также можете установить First Responder на свое имя пользовательского класса, чтобы начать подключение любых дополнительных розеток.
надеюсь, что это помогает!
протестировано в Xcode 7 beta 4, Swift 2.0 и iOS9 SDK . Следующий код назначит xib для uiview. Вы можете использовать этот пользовательский вид xib в раскадровке и получить доступ к объекту IBOutlet.
import UIKit @IBDesignable class SimpleCustomView:UIView { var view:UIView!; @IBOutlet weak var lblTitle: UILabel! @IBInspectable var lblTitleText : String? { get{ return lblTitle.text; } set(lblTitleText) { lblTitle.text = lblTitleText!; } } override init(frame: CGRect) { super.init(frame: frame) loadViewFromNib () } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) loadViewFromNib () } func loadViewFromNib() { let bundle = NSBundle(forClass: self.dynamicType) let nib = UINib(nibName: "SimpleCustomView", bundle: bundle) let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView view.frame = bounds view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight] self.addSubview(view); } }
доступ к customview программно
self.customView = SimpleCustomView(frame: CGRectMake(100, 100, 200, 200)) self.view.addSubview(self.customView!);
исходный код -https://github.com/karthikprabhuA/CustomXIBSwift
если у вас есть много пользовательских представлений в вашем проекте вы можете создать класс, как
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
свойство if.xib
файл имеет другое имя:class MyCustomClass: UIViewFromNib { }
основываясь на вышеуказанных решениях.
это будет работать во всех пакетах проектов и не нужно для дженериков при вызове 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")
хороший способ сделать это с Swift-использовать перечисление.
enum Views: String { case view1 = "View1" // Change View1 to be the name of your nib case view2 = "View2" // Change View2 to be the name of another nib func getView() -> UIView { return NSBundle.mainBundle().loadNibNamed(self.rawValue, owner: nil, options: nil)[0] as! UIView } }
потом в вашем коде, вы можете просто использовать:
let view = Views.view1.getView()
Я предпочитаю это решение (на основе ответа if @GK100):
- Я создал XIB и класс с именем SomeView (использовал то же имя для удобства и читабельности). Я основывал оба на UIView.
- в XIB я изменил класс "владелец файла" на SomeView (в инспекторе идентификации).
- Я создал выход UIView в SomeView.swift, связывая его с представлением верхнего уровня в файле XIB (для удобства назвал его "view"). Затем я добавил другие выходы другие элементы управления в файле XIB по мере необходимости.
В SomeView.Свифт, я загрузил XIB внутрь
init
илиinit:frame: CGRect
инициализатор. Не нужно ничего приписывать "себе". Как только XIB загружен, все розетки подключены, включая вид верхнего уровня. Единственное, чего не хватает, это добавить вид сверху в иерархию представлений:class SomeView: UIView { override init(frame: CGRect) { super.init(frame: frame) NSBundle.mainBundle().loadNibNamed("SomeObject", owner: self, options: nil) self.addSubview(self.view); // adding the top level view to the view hierarchy } required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder) NSBundle.mainBundle().loadNibNamed("SomeObject", owner: self, options: nil) self.addSubview(self.view); // adding the top level view to the view hierarchy } ... }
Swift 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 } } }
let subviewArray = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil) return subviewArray[0]
все, что вам нужно сделать, это вызвать метод 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 } }
Теперь, если вы хотите добавить это представление в качестве подвида в контроллере вида, Сделайте это таким образом в контроллере вида.swift file:
self.view.addSubview(className())
похоже на некоторые из ответов выше, но более последовательное расширение 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) } }
что дает удобство загрузки класса из самоназванного наконечника, но также и из других наконечников/Пучков.
Я просто делаю так :
if let myView = UINib.init(nibName: "MyView", bundle: nil).instantiate(withOwner: self)[0] as? MyView { // Do something with myView }
в этом примере используется первый вид в nib " MyView.xib " в основной связке. Но вы можете изменить либо индекс, имя пера, либо пакет (main по умолчанию ).
я использовал , чтобы пробудить представления в метод init представления или сделать общие методы, как в решениях выше ( которые, кстати, умны), но я больше этого не делаю.
таким образом, я могу использовать разные макеты или черты, сохраняя при этом ту же логику представления и код.
мне проще позволить объекту фабрики (обычно viewController, который будет использовать представление ) создать его по мере необходимости. Иногда вам нужен владелец (обычно , когда созданный вид получил выход, подключенный к создателю), иногда нет..
вероятно, поэтому Apple не включила
initFromNib
метод в своем классе UIView...чтобы взять пример уровня земли, вы не знаете, как вы родились. Вы просто рождаетесь. Так же как и взгляды ;)
вы можете сделать это с помощью раскадровки, просто добавить соответствующие ограничения для просмотра. Вы можете легко сделать это путем создания подклассов любой вид из вашего собственного скажем
BaseView
:С
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
имя имеет то же имя, что и реализация имя класса. Если нет-просто измените
вот чистый и декларативный способ программной загрузки представления с использованием протокола и расширения протокола (Swift 4.2):
protocol XibLoadable { associatedtype CustomViewType static func loadFromXib() -> CustomViewType } extension XibLoadable where Self: UIView { static func loadFromXib() -> Self { let nib = UINib(nibName: "\(self)", bundle: Bundle(for: self)) guard let customView = nib.instantiate(withOwner: self, options: nil).first as? Self else { // your app should crash if the xib doesn't exist preconditionFailure("Couldn't load xib for view: \(self)") } return customView } }
и вы можете использовать это так:
// don't forget you need a xib file too final class MyView: UIView, XibLoadable { ... } // and when you want to use it let viewInstance = MyView.loadFromXib()
некоторые дополнительные соображения:
- убедитесь, что xib-файл вашего пользовательского представления имеет вид
Custom Class
set (и выходы / действия, установленные оттуда), а не владельца файла.- вы можете использовать этот протокол/расширение внешних пользовательских вид или внутренний. Вы можете использовать его внутренне, если у вас есть какая-то другая работа по настройке при инициализации вашего представления.
- ваш пользовательский класс представления и xib-файл должны иметь одно и то же имя.
более мощная версия, основанная на ответе Логана
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 } }
самая удобная реализация. Здесь вам нужно два метода, чтобы вернуться непосредственно к объекту вашего класса, а не UIView.
- viewid, которые обозначены как класс, что позволяет переопределить
- ваш .xib может содержать более одного представления верхнего уровня, эта ситуация также правильно.
extension UIView { class var viewId: String { return String(describing: self) } static func instance(from bundle: Bundle? = nil, nibName: String? = nil, owner: Any? = nil, options: [AnyHashable : Any]? = nil) -> Self? { return instancePrivate(from: bundle ?? Bundle.main, nibName: nibName ?? viewId, owner: owner, options: options) } private static func instancePrivate<T: UIView>(from bundle: Bundle, nibName: String, owner: Any?, options: [AnyHashable : Any]?) -> T? { guard let views = bundle.loadNibNamed(nibName, owner: owner, options: options), let view = views.first(where: { 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
Если вы хотите, чтобы подкласс Swift UIView был полностью автономным и имел возможность быть созданным с помощью init или init(frame:), не раскрывая детали реализации использования пера, то вы можете использовать расширение протокола для достижения этой цели. Это решение позволяет избежать вложенной иерархии UIView, как это предлагается многими другими решениями.
public class CustomView: UIView { @IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var valueLabel: UILabel! public convenience init() { self.init(frame: CGRect.zero) } public override convenience init(frame: CGRect) { self.init(internal: nil) self.frame = frame } public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) commonInit() } fileprivate func commonInit() { } } fileprivate protocol _CustomView { } extension CustomView: _CustomView { } fileprivate extension _CustomView { // Protocol extension initializer - has the ability to assign to self, unlike // class initializers. Note that the name of this initializer can be anything // you like, here we've called it init(internal:) init(internal: Int?) { self = Bundle.main.loadNibNamed("CustomView", owner:nil, options:nil)![0] as! Self; } }
class func loadFromNib<T: UIView>() -> T { let nibName = String(describing: self) return Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)![0] as! T }
Swift 4
не забудьте написать ".сначала как? Пользовательские".
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()