Load a UIView from nib in Swift

2019-01-04 16:41发布

Here is my Objective-C code which I'm using to load a nib for my customised UIView:

-(id)init{

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

}

What is the equivalent code in Swift?

标签: swift uiview xib
23条回答
爷的心禁止访问
2楼-- · 2019-01-04 17:19

Here's an extension that uses generics for loading a UIView from a 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
        }
    }
}

I prefer this since it doesn't require any additional setup in the nib. It is based on general naming conventions, so if your class is CustomView, and it matches a nib named: CustomView, you could just do this:

let myCustomView = CustomView.fromNib()
// or if you're unsure whether or not the nib exists
let myCustomView: CustomView? = CustomView.fromNib()

If you need to be specific about the nib name for whatever reason, pass a string arg:

let myCustomView = MyCustomView.fromNib("non-conventional-name")

Known Issues

Using this with a private view class seems to cause issues. This appears to be a system wide issue.

查看更多
乱世女痞
3楼-- · 2019-01-04 17:19

A nice way to do this with Swift is to use an 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)[0] as! UIView
    }
}

Then in your code you can simply use:

let view = Views.view1.getView()
查看更多
该账号已被封号
4楼-- · 2019-01-04 17:19

You can do this via storyboard, just add proper constraints for view. You can do this easily by subclassing any view from your own let's say 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)
    }
}

I provide 2 variants how to add constraints - common one and within visual format language - select any you want :)

Also, by default assumed that xib name has same name as implementation class name. If no - just change xibName parameter.

If you subclass your view from BaseView - you can easily put any view and specify class in IB.

查看更多
Viruses.
5楼-- · 2019-01-04 17:22

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

Then call it like this:

let myCustomView: CustomView = UIView.fromNib()

..or even:

let myCustomView: CustomView = .fromNib()
查看更多
不美不萌又怎样
6楼-- · 2019-01-04 17:22
class func loadFromNib<T: UIView>() -> T {
    let nibName = String(describing: self)
    return Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)![0] as! T
}
查看更多
三岁会撩人
7楼-- · 2019-01-04 17:23

Here is a clean and declarative way of programmatically loading a view using a protocol and protocol extension (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
    }
}

And you can use this like so:

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

Some additional considerations:

  1. Make sure your custom view's xib file has the view's Custom Class set (and outlets/actions set from there), not the File Owner's.
  2. You can use this protocol/extension external to your custom view or internal. You may want to use it internally if you have some other setup work when initializing your view.
  3. Your custom view class and xib file need to have the same name.
查看更多
登录 后发表回答