6

I'm trying to create a reusable UIView in Swift that I can plug into my Storyboard view controllers. My key issue right now is that the reusable UIView "widget" doesn't fully fit into the UIView box in the storyboard. I followed this tutorial to set up the reusable UIView widget

  1. Created a subclass of UIView and a corresponding .xib -- and connected these:

    import UIKit
    
    class MyWidgetView: UIView {
    
    @IBOutlet var view: UIView!;
    
    required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder);
    
    NSBundle.mainBundle().loadNibNamed("MyWidgetView", owner: self, options: nil);
    self.addSubview(self.view);
    }
    
    }
    
  2. In the XIB, which is the interface file corresponding to the code above, I used UIView with Freeform size under the Simulated Metrics, and Scale to Fill under View mode.

  3. In the main storyboard, I added a UIView block (same rectangular shape) and changed the Class to MyWidgetView

  4. It works, but the components I created in the XIB look squished in the actual app, despite the fact that I used layout constraints in both the XIB and also the main storyboard.

See the screenshot. The pink part isn't supposed to appear, since that is just a color of the UIVIew on the main storyboard that I added to test the sizing. That UIView is actually MyWidgetView (after I changed the class in step 3. So in theory, since MyWidgetView == the UIView on the main storyboard, and that UIView has constraints that make it rectangular in the superview, then why is my widget squished? The blue part below should extend all the way right. Squished widget

Uzumaki Naruto
  • 547
  • 5
  • 18

2 Answers2

15

The actual view hierarchy loaded from the nib file in your code is added via self.addSubview(self.view). So, the frame of your self.view actually has no relationship with its parent, i.e. MyWidgetView.

You may choose either adding layout constraints through code or just setting its frame after being added as a subview. Personally, I prefer the latter. In my experiment, the following is what works for me. I am using Xcode 6.4, which I think is not the same one as yours.

required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)

    if let nibsView = NSBundle.mainBundle().loadNibNamed("MyWidgetView", owner: self, options: nil) as? [UIView] {
        let nibRoot = nibsView[0]
        self.addSubview(nibRoot)
        nibRoot.frame = self.bounds
    }
}
southp
  • 494
  • 1
  • 4
  • 14
  • Okay, I'll try this out. I have XCode 7 beta 5 and Swift 2.0 so I can load apps on my device without having to go through Apple's $99 subscription. – Uzumaki Naruto Aug 18 '15 at 12:13
  • Impressive...this works. Can you explain a bit more how it works? Especially around the "nibRoot" -- not sure I totally understand this part. – Uzumaki Naruto Aug 18 '15 at 22:21
  • 2
    Glad it works! As described in [the official document](https://developer.apple.com/library/ios/documentation/UIKit/Reference/NSBundle_UIKitAdditions/#//apple_ref/occ/instm/NSBundle/loadNibNamed:owner:options:), the return value of `NSBundle.loadNibNamed()` is **An array containing the top-level objects in the nib file**. In our case, there is only one top level object: A view containing all your `MyWidgetView` view hierarchy. So, after a successful call of `loadNibNamed`, we picked the first and the only one view as `nibRoot`, and add it as a subview. – southp Aug 19 '15 at 09:54
  • Here is a clear question and answer I wrote that works with auto layout. http://stackoverflow.com/questions/30335089/create-a-uiview-xib-and-reuse-in-storyboard – Garfbargle Jan 19 '16 at 02:17
  • You can find a detailled explanation of how to do it the right way here: http://stackoverflow.com/a/34524346/971329. See my comment below if you need the Swift implementation of a reusable superview that cares for all the stuff. – blackjacx Mar 11 '16 at 20:41
1

Alternatively the variable frame can be overridden. This code worked for me when CardImgText was set to files owner for the view.

class CardImgTxt: NSView {

    @IBOutlet var view: NSView!
    override var frame: NSRect{
        didSet{
            view.frame = bounds
        }
    }

    override func drawRect(dirtyRect: NSRect) {
        super.drawRect(dirtyRect)


        // Drawing code here.
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        NSBundle.mainBundle().loadNibNamed("View", owner: self, topLevelObjects: nil)
        addSubview(view)
    }

}

if you are more interested in efficiency than real time updating. Then replace :

    override var frame: NSRect{
        didSet{
            view.frame = bounds
        }
    }

with:

override func viewDidEndLiveResize() {
    view.frame = bounds
}
jiminybob99
  • 857
  • 1
  • 8
  • 15