Voici mon code Objective-C que j'utilise pour charger une plume pour ma UIView
personnalisée:
-(id)init{
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:@"myXib" owner:self options:nil];
return [subviewArray objectAtIndex:0];
}
Quel est le code équivalent dans Swift?
Solution originale
.
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
}
...
}
Notez que de cette façon je reçois une classe qui se charge de nib. Je pourrais ensuite utiliser SomeView en tant que classe chaque fois que UIView pourrait être utilisé dans le projet (dans le générateur d'interface ou par programme).
Update - en utilisant la syntaxe Swift 3
Le chargement d'un xib dans l'extension suivante s'écrit comme une méthode d'instance, qui peut ensuite être utilisée par un initialiseur comme celui ci-dessus:
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
}
}
Et la méthode de l'appelant pourrait ressembler à ceci:
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 ...
}
Crédit: L'utilisation de l'extension générique dans cette solution a été inspirée par la réponse de Robert ci-dessous.
Edit Changer "view" en "contentView" pour éviter toute confusion. Également changé l'indice de tableau en ".first".
Ma contribution:
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
}
}
Puis appelle ça comme ça:
let myCustomView: CustomView = UIView.fromNib()
..ou même:
let myCustomView: CustomView = .fromNib()
Voici une extension qui utilise des génériques pour charger une UIView
à partir d'une 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
}
}
}
Je préfère cela car il ne nécessite aucune configuration supplémentaire dans la plume. Il est basé sur les conventions de nommage générales, donc si votre classe est CustomView
et qu'elle correspond à une nib nommée: CustomView
, vous pouvez simplement faire ceci:
let myCustomView = CustomView.fromNib()
// or if you're unsure whether or not the nib exists
let myCustomView: CustomView? = CustomView.fromNib()
Si vous devez préciser le nom du nib pour une raison quelconque, passez un argument de chaîne:
let myCustomView = MyCustomView.fromNib("non-conventional-name")
Problèmes connus
Son utilisation avec une classe de vue privée semble poser problème. Cela semble être un problème à l’échelle du système.
essayez de suivre le code.
var uiview :UIView?
self.uiview = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)[0] as? UIView
Modifier:
import UIKit
class TestObject: NSObject {
var uiview:UIView?
init() {
super.init()
self.uiview = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)[0] as? UIView
}
}
J'ai réalisé cela avec Swift avec le code suivant:
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)
}
}
N'oubliez pas de connecter votre XIB view sortie à la sortie view définie dans Swift. Vous pouvez également définir First Responder sur votre nom de classe personnalisé pour commencer à connecter des prises supplémentaires.
J'espère que cela t'aides!
Testé dans Xcode 7 beta 4, Swift 2.0 et iOS9 SDK. Le code suivant assignera xib à uiview . Vous pourrez utiliser cette vue xib personnalisée dans le storyboard et accéder également à l'objet 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);
}
}
Accéder à la vue personnalisée par programme
self.customView = SimpleCustomView(frame: CGRectMake(100, 100, 200, 200))
self.view.addSubview(self.customView!);
Code source - https://github.com/karthikprabhuA/CustomXIBSwift
Construire sur les solutions ci-dessus.
Cela fonctionnera pour tous les lots de projets et aucun besoin de génériques lors de l'appel deNib ().
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)
}
}
Peut être utilisé comme ceci:
let someView = SomeView.fromNib()
Ou comme ceci:
let someView = SomeView.fromNib("SomeOtherNibFileName")
Si vous avez beaucoup de vues personnalisées dans votre projet, vous pouvez créer une classe comme 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)
}
}
Et dans chaque classe, héritez simplement de UIViewFromNib
, vous pouvez également remplacer la propriété nibName
si le fichier .xib
a un nom différent:
class MyCustomClass: UIViewFromNib {
}
Une bonne façon de faire cela avec Swift est d’utiliser une énumération.
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
}
}
Ensuite, dans votre code, vous pouvez simplement utiliser:
let view = Views.view1.getView()
Extensions de protocole 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
}
}
Adoption
class MyView: UIView, NibInstantiatable {
}
Cette implémentation suppose que Nib a le même nom que la classe UIView. Ex. MyView.xib. Vous pouvez modifier ce comportement en implémentant nibName () dans MyView pour renvoyer un nom différent de celui de l'extension de protocole par défaut.
Dans la bibliothèque xib, le propriétaire des fichiers est MyView et la classe de vue racine est MyView.
Utilisation
let view = MyView.fromNib()
Swift 3 version de la réponse de 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
}
}
}
Je préfère cette solution (basée sur la réponse si @ GK100):
Dans SomeView.Swift, j'ai chargé le fichier XIB dans l'initialiseur init
ou init:frame: CGRect
. Il n'y a pas besoin d'attribuer quoi que ce soit à "soi". Dès que le fichier XIB est chargé, tous les points de vente sont connectés, y compris la vue de niveau supérieur. La seule chose qui manque, c'est d'ajouter la vue de dessus à la hiérarchie de vues:
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
}
...
}
let subviewArray = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)
return subviewArray[0]
Voici une méthode propre et déclarative de chargement par programme d'une vue à l'aide d'un protocole et d'une extension de protocole (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
}
}
Et vous pouvez utiliser ceci comme ceci:
// 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()
Quelques considérations supplémentaires :
Custom Class
de la vue (et les prises/actions définies à partir de là), et non le propriétaire du fichier.Tout ce que vous avez à faire est d’appeler la méthode init dans votre classe UIView
.
Faites-le comme ça:
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
}
}
Maintenant, si vous souhaitez ajouter cette vue en tant que sous-vue dans le contrôleur de vue, procédez de cette manière dans le fichier view controller.Swift:
self.view.addSubview(className())
Swift 4
N'oubliez pas d'écrire ".first as? CustomView".
if let customView = Bundle.main.loadNibNamed("myXib", owner: self, options: nil)?.first as? CustomView {
self.view.addSubview(customView)
}
Si vous voulez utiliser n'importe où
La meilleure solution est la réponse de Robert Gummesson.
extension UIView {
class func fromNib<T: UIView>() -> T {
return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T
}
}
Puis appelle ça comme ça:
let myCustomView: CustomView = UIView.fromNib()
Vous pouvez le faire via un scénario, en ajoutant simplement les contraintes appropriées pour la vue. Vous pouvez le faire facilement en sous-classant une vue de votre propre, disons BaseView
:
Objectif 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)
}
}
Je propose 2 variantes pour ajouter des contraintes - communes et dans un langage de format visuel - sélectionnez celles que vous voulez :)
De plus, supposons par défaut que xib
name ait le même nom que le nom de la classe d'implémentation. Si non, changez simplement le paramètre xibName
.
Si vous sous-classez votre vue à partir de BaseView
, vous pouvez facilement placer n'importe quelle vue et spécifier une classe dans IB.
Je fais juste comme ça:
if let myView = UINib.init(nibName: "MyView", bundle: nil).instantiate(withOwner: self)[0] as? MyView {
// Do something with myView
}
Cet exemple utilise la première vue de la nib "MyView.xib" dans le bundle principal. Mais vous pouvez modifier l’index, le nom du nib ou le bundle (main par défaut).
J'avais l'habitude d'éveiller des vues dans la méthode view init ou de créer des méthodes génériques comme dans les solutions ci-dessus (qui sont intelligentes d'ailleurs), mais je ne le fais plus.
De cette façon, je peux utiliser différentes dispositions ou caractéristiques tout en conservant la même logique de vue et le même code.
Je trouve plus facile de laisser un objet fabrique (généralement le viewController qui utilisera la vue) le crée quand il en a besoin. Parfois, vous avez besoin d'un propriétaire (généralement lorsque la vue créée a un point de vente connecté au créateur), parfois pas ..
C'est probablement pourquoi Apple n'a pas inclus de méthode initFromNib
dans sa classe UIView ...
Pour prendre un exemple au niveau du sol, vous ne savez pas comment vous êtes né. Vous venez de naître. Ainsi sont les vues;)
Semblable à certaines des réponses ci-dessus mais avec une extension plus cohérente de 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)
}
}
Ce qui donne l’opportunité de pouvoir charger la classe à partir d’une plume auto-nommée mais aussi d’autres plumes/bundles.
class func loadFromNib<T: UIView>() -> T {
let nibName = String(describing: self)
return Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)![0] as! T
}
Si vous voulez que la sous-classe de Swift UIView soit entièrement autonome et que vous puissiez être instanciée à l'aide de init ou init (frame :) sans exposer les détails d'implémentation de l'utilisation d'un Nib, vous pouvez utiliser une extension de protocole pour y parvenir. Cette solution évite la hiérarchie imbriquée UIView comme suggéré par de nombreuses autres solutions.
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;
}
}
L'implémentation la plus pratique. Ici, vous avez besoin de deux méthodes pour retourner directement à l'objet de votre classe, pas à UIView.
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
}
}
Exemple:
guard let customView = CustomView.instance() else { return }
//Here customView has CustomView class type, not UIView.
print(customView is CustomView) // true
Version plus puissante basée sur la réponse de 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
}
}
Et vous pouvez utiliser comme
class BaseView: UIView {
override class var nibName: String { return "BaseView" }
weak var delegate: StandardStateViewDelegate?
}
class ChildView: BaseView {
override class var nibIndex: Int { return 1 }
}