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:03

Tested in Xcode 7 beta 4 , Swift 2.0 and iOS9 SDK . The following code will assign xib to the uiview. You can able to use this custom xib view in storyboard and access the IBOutlet object also.

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



    }


}

Access customview programatically

self.customView =  SimpleCustomView(frame: CGRectMake(100, 100, 200, 200))
        self.view.addSubview(self.customView!);

Source code - https://github.com/karthikprabhuA/CustomXIBSwift

查看更多
混吃等死
3楼-- · 2019-01-04 17:03
let subviewArray = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)
return subviewArray[0]
查看更多
唯我独甜
4楼-- · 2019-01-04 17:05

I achieved this with Swift by the following code:

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

Don't forget to connect your XIB view outlet to view outlet defined in swift. You can also set First Responder to your custom class name to start connecting any additional outlets.

Hope this helps!

查看更多
时光不老,我们不散
5楼-- · 2019-01-04 17:06

The most convenient implementation. Here you need two methods, in order to return directly to the object of your class, not UIView.

  1. viewId marked as a class, allowing override
  2. Your .xib can contain more than one view of the top level, this situation is also handled correctly.

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

Example:

guard let customView = CustomView.instance() else { return }

//Here customView has CustomView class type, not UIView.
print(customView is CustomView) // true
查看更多
对你真心纯属浪费
6楼-- · 2019-01-04 17:07

Original Solution

  1. I created a XIB and a class named SomeView (used the same name for convenience and readability). I based both on a UIView.
  2. In the XIB, I changed the "File's Owner" class to SomeView (in the identity inspector).
  3. I created a UIView outlet in SomeView.swift, linking it to the top level view in the XIB file (named it "view" for convenience). I then added other outlets to other controls in the XIB file as needed.
  4. in SomeView.swift, I loaded the XIB inside the "init with code" initializer. There is no need to assign anything to "self". As soon as the XIB is loaded, all outlets are connected, including the top level view. The only thing missing, is to add the top view to the view hierarchy:

.

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

Note that this way I get a class that loads itself from nib. I could then use SomeView as a class whenever UIView could be used in the project (in interface builder or programmatically).

Update - using Swift 3 syntax

Loading a xib in the following extension is written as an instance method, which can then be used by an initializer like the one above:

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
    }
}
  1. Using a discardable return value since the returned view is mostly of no interest to caller when all outlets are already connected.
  2. This is a generic method that returns an optional object of type UIView. If it fails to load the view, it returns nil.
  3. Attempting to load a XIB file with the same name as the current class instance. If that fails, nil is returned.
  4. Adding the top level view to the view hierarchy.
  5. This line assumes we're using constraints to layout the view.
  6. This method adds top, bottom, leading & trailing constraints - attaching the view to "self" on all sides (See: https://stackoverflow.com/a/46279424/2274829 for details)
  7. Returning the top level view

And the caller method might look like this:

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 ...
}
  1. SomeClass is a UIView subclass that loads its content from a SomeClass.xib file. The "final" keyword is optional.
  2. An initializer for when the view is used in a storyboard (remember to use SomeClass as the custom class of your storyboard view).
  3. An initializer for when the view is created programmatically (i.e.: "let myView = SomeView()").
  4. Using an all-zeros frame since this view is laid out using auto-layout. Note that an "init(frame: CGRect) {..}" method is not created independently, since auto-layout is used exclusively in our project.
  5. & 6. Loading the xib file using the extension.

Credit: Using a generic extension in this solution was inspired by Robert's answer below.

Edit Changing "view" to "contentView" to avoid confusion. Also changed the array subscript to ".first".

查看更多
Evening l夕情丶
7楼-- · 2019-01-04 17:07

I just do this way :

if let myView = UINib.init(nibName: "MyView", bundle: nil).instantiate(withOwner: self)[0] as? MyView {
// Do something with myView
}

This sample uses the first view in the nib "MyView.xib" in the main bundle. But you can vary either the index, the nib name, or the bundle ( main by default ).

I used to awake views into the view init method or make generic methods as in the solutions above ( which are smart by the way ) , but I don't do it anymore.

This way I can use different layouts or traits while keeping the same view logic and code.

I find easier to let a factory object ( usually the viewController that will use the view ) create it as it needs it. Sometimes you need an owner ( Usually when the created view got an outlet connected to the creator ) , sometimes not..

That's probably why Apple did not include an initFromNib method in its UIView class...

To take a ground level example, you don't know how you are born. You just are born. So are the views ;)

查看更多
登录 后发表回答