How to embed a custom view xib in a storyboard scene?

前端 未结 11 543
生来不讨喜
生来不讨喜 2021-01-29 21:33

I\'m relatively new in the XCode/iOS world; I\'ve done some decent sized storyboard based apps, but I didn\'t ever cut me teeth on the whole nib/xib thing. I want to use the sam

相关标签:
11条回答
  • 2021-01-29 21:57

    You're almost there. You need to override initWithCoder in your custom class you assigned the view to.

    - (id)initWithCoder:(NSCoder *)aDecoder {
        if ((self = [super initWithCoder:aDecoder])) {
            [self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"ViewYouCreated" owner:self options:nil] objectAtIndex:0]];
        }
        return self; }
    

    Once that's done the StoryBoard will know to load the xib inside that UIView.

    Here's a more detailed explanation:

    This is how your UIViewController looks like on your story board: enter image description here

    The blue space is basically a UIView that will "hold" your xib.

    This is your xib:

    enter image description here

    There's an Action connected to a button on it that will print some text.

    and this is the final result:

    enter image description here

    The difference between the first clickMe and the second is that the first was added to the UIViewController using the StoryBoard. The second was added using code.

    0 讨论(0)
  • 2021-01-29 21:59

    I've been using this code snippet for years. If you plan on having custom class views in your XIB just drop this in the .m file of your custom class.

    As a side effect it results in awakeFromNib being called so you can leave all your init/setup code in there.

    - (id)awakeAfterUsingCoder:(NSCoder*)aDecoder {
        if ([[self subviews] count] == 0) {
            UIView *view = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:nil options:nil][0];
            view.frame = self.frame;
            view.autoresizingMask = self.autoresizingMask;
            view.alpha = self.alpha;
            view.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;
            return view;
        }
        return self;
    }
    
    0 讨论(0)
  • 2021-01-29 22:01

    It's been a while on this one, and I've seen a number of answers go by. I recently revisited it because I had just been using UIViewController embedding. Which works, until you want to put something in "an element repeated at runtime" (e.g. a UICollectionViewCell or a UITableViewCell). The link provided by @TomSwift led me to follow the pattern of

    A) Rather than make the parent view class be the custom class type, make the FileOwner be the target class (in my example, CycleControlsBar)

    B) Any outlet/action linking of the nested widgets goes to that

    C) Implement this simple method on CycleControlsBar:

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        if let container = (Bundle.main.loadNibNamed("CycleControlsBar", owner: self, options: nil) as? [UIView])?.first {
            self.addSubview(container)
            container.constrainToSuperview() // left as an excise for the student
        }
    }
    

    Works like a charm and so much simpler than the other approaches.

    0 讨论(0)
  • 2021-01-29 22:04

    A pretty cool and reusable way of doing this Interface Builder and Swift 4:

    1. Create a new class like so:

      import Foundation
      import UIKit
      
      @IBDesignable class XibView: UIView {
      
          @IBInspectable var xibName: String?
      
          override func awakeFromNib() { 
              guard let name = self.xibName, 
                    let xib = Bundle.main.loadNibNamed(name, owner: self), 
                    let view = xib.first as? UIView else { return }    
              self.addSubview(view)
          }
      
      }
      
    2. In your storyboard, add a UIView that will act as the container for the Xib. Give it a class name of XibView:

    3. In the property inspector of this new XibView, set the name of your .xib (without the file extension) in the IBInspectable field:

    4. Add a new Xib view to your project, and in the property inspector, set the Xib's "File's Owner" to XibView (ensure you've only set the "File's Owner" to your custom class, DO NOT subclass the content view, or it will crash), and again, set the IBInspectable field:

    One thing to note: This assumes that you're matching the .xib frame to its container. If you do not, or need it to be resizable, you'll need to add in some programmatic constraints or modify the subview's frame to fit. I use snapkit to make things easy:

    xibView.snp_makeConstraints(closure: { (make) -> Void in
        make.edges.equalTo(self)
    })
    

    Bonus points

    Allegedly you can use prepareForInterfaceBuilder() to make these reusable views visible in Interface Builder, but I haven't had much luck. This blog suggests adding a contentView property, and calling the following:

    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        xibSetup()
        contentView?.prepareForInterfaceBuilder()
    }
    
    0 讨论(0)
  • 2021-01-29 22:04
    • Create xib file File > New > New File > iOS > User Interface > View
    • Create custom UIView class File > New > New File > iOS > Source > CocoaTouch
    • Assign the xib file's identity to the custom view class
    • In viewDidLoad of the view controller initialize the xib and its associated file using loadNibNamed: on NSBundle.mainBundle and the first view returned can be added as a subview of self.view.
    • The custom view loaded from the nib can be saved to a property for setting the frame in viewDidLayoutSubviews. Just set the frame to self.view's frame unless you need to make it smaller than self.view.

      class ViewController: UIViewController {
      
          weak var customView: MyView!
      
          override func viewDidLoad() {
              super.viewDidLoad()
              self.customView = NSBundle.mainBundle().loadNibNamed("MyView", owner: self, options: nil)[0] as! MyView
              self.view.addSubview(customView)
              addButtonHandlerForCustomView()
          }
      
          private func addButtonHandlerForCustomView() {
              customView.buttonHandler = {
                  [weak self] (sender:UIButton) in
                  guard let welf = self else {
                      return
                  }
                  welf.buttonTapped(sender)
              }
          }
      
          override func viewDidLayoutSubviews() {
              self.customView.frame = self.view.frame
          }
      
          private func buttonTapped(button:UIButton) {
      
          }
      }
      
    • Also, if you want to talk back from the xib to your UIViewController instance then create a weak property on the custom view's class.

      class MyView: UIView {
      
          var buttonHandler:((sender:UIButton)->())!
      
          @IBAction func buttonTapped(sender: UIButton) {
              buttonHandler(sender:sender)
          }
      }
      

    Here's the project on GitHub

    0 讨论(0)
  • 2021-01-29 22:04

    While I don't recommend the path you're going down it can be done by placing an "embedded view controller view" where you want the view to appear.

    Embed a view controller that contains a single view -- the view you want to be reused.

    0 讨论(0)
提交回复
热议问题