14

I have some strange problem with loading xib file in swift project. It's so frustrating because I already know how to do it in Obj-C. But since swift is swift so you can't do it like you did.. :/

So I have create IconTextFiled.xib and IconTextField.swift. (extends UITextField) In xib I fill field Class in Idenity inspector and in storyboard I do the same for some textFields. So we good to go just add loading from xib to init method? No.

In objc I would do it like this

- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"IconTextField" owner:self options:nil];
        self = [nib objectAtIndex:0];
    }
    return self;
}

So I thought if I translate to swift it will be good.

required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    let nib:NSArray = NSBundle.mainBundle().loadNibNamed("IconTextField", owner: self, options: nil)
    self = nib.objectAtIndex(0)
}

But that doeasn't work. I don't know why but it try to create much more object and crash

Finally I found extension

extension IconTextField {
    class func loadFromNibNamed(nibNamed: String, bundle : NSBundle? = nil) -> IconTextField? {
        return UINib(
            nibName: nibNamed,
            bundle: bundle
            ).instantiateWithOwner(nil, options: nil)[0] as? IconTextField
    }
}

So in ViewController it looks like

@IBOutlet var password: IconTextField!
override func viewDidLoad() {
    super.viewDidLoad()
    password = IconTextField.loadFromNibNamed("IconTextField")
}

And again fail. Could you tell me how you load and use xib files?

UPDATE

Ok following after Daniel anwser

My current code

class IconTextField: UITextField {
    @IBOutlet var icon: UIImageView!
    @IBOutlet weak var view: UIView!

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        NSLog("initWithCoder \(self)")
        NSBundle.mainBundle().loadNibNamed("IconTextField", owner: self, options: nil)
        self.addSubview(view)
    }   
}

enter image description here

Those two var are connected to those views

Consoleo output, was a lot bigger and end with EXC_BAD_ACCESS

2014-10-24 10:09:09.984 testproject[20337:3757479] initWithCoder <stensgroup.IconTextField: 0x7be7bfb0;>
2014-10-24 10:09:09.984 testproject[20337:3757479] initWithCoder <stensgroup.IconTextField: 0x7be7ddf0;>
2014-10-24 10:09:09.985 testproject[20337:3757479] initWithCoder <stensgroup.IconTextField: 0x7be7fa20;>
2014-10-24 10:09:09.985 testproject[20337:3757479] initWithCoder <stensgroup.IconTextField: 0x7be814f0;>
2014-10-24 10:09:09.986 testproject[20337:3757479] initWithCoder <stensgroup.IconTextField: 0x7be830c0;>
2014-10-24 10:09:10.083 testproject[20337:3757479] initWithCoder <stensgroup.IconTextField: 0x7d183270;>
2014-10-24 10:09:10.084 testproject[20337:3757479] initWithCoder <stensgroup.IconTextField: 0x7d187cd0;>
2014-10-24 10:09:10.084 testproject[20337:3757479] initWithCoder <stensgroup.IconTextField: 0x7d189960;>

It should be only two initWithCoder. It seams that func loadNibNamed is calling initWithCoder

Błażej
  • 3,617
  • 7
  • 35
  • 62
  • I have to ask... Why are you doing this to a `UITextField`? I've seen it done for `UITableViewCells`, but never a text field. – Daniel T. Oct 24 '14 at 01:20
  • Because I want to create reuseable control. For example that `IconTextField ` will have `UIImgaeView` as icon. So that I don't have to attach `UIImageView` every single time. – Błażej Oct 24 '14 at 07:53

3 Answers3

8

This works for me:

class IconTextField: UITextField {
    @IBOutlet weak var view: UIView!
    @IBOutlet weak var test: UIButton!

    required init(coder: NSCoder) {
        super.init(coder: coder)
        NSBundle.mainBundle().loadNibNamed("IconTextField", owner: self, options: nil)
        self.addSubview(view)
        assert(test != nil, "the button is conected just like it's supposed to be")
    }
}

Once loadNibNamed:owner:options: is called the view and test button are connected to the outlets as expected. Adding the nib's view self's subview hierarchy makes the nib's contents visible.

Daniel T.
  • 32,821
  • 6
  • 50
  • 72
  • This is also how I always did it in Objective-C – Daniel T. Oct 24 '14 at 01:08
  • Ok I just update my question. I don't know what I do not right. – Błażej Oct 24 '14 at 08:16
  • I just used your exact updated code (copy-paste) in Xcode 6.1 and it worked fine. I also got very different output from the println: `initWithCoder >` – Daniel T. Oct 24 '14 at 14:08
  • Output is fine.. I just want to save us from too much text :) What is your storyboard settings? Could you host your project? – Błażej Oct 24 '14 at 16:30
  • For test I try it in Obj-c and reaction is the same. Weird this kind of loading xib will wokr only for UITableViewCell? – Błażej Oct 24 '14 at 18:33
  • Sorry for my stupid begginner´s question, but, I want to know if that iconTextField class is inside some iconTextField.swift file? If not, where? – Josh May 13 '15 at 10:03
6

I prefer to load from nib, by implementing loadFromNib() function in a protocol extension as follows:

(as explained here: https://stackoverflow.com/a/33424509/845027)

import UIKit

protocol UIViewLoading {}
extension UIView : UIViewLoading {}

extension UIViewLoading where Self : UIView {

  // note that this method returns an instance of type `Self`, rather than UIView
  static func loadFromNib() -> Self {
    let nibName = "\(self)".characters.split{$0 == "."}.map(String.init).last!
    let nib = UINib(nibName: nibName, bundle: nil)
    return nib.instantiateWithOwner(self, options: nil).first as! Self
  }

}
Community
  • 1
  • 1
Sam
  • 5,892
  • 1
  • 25
  • 27
  • This is the best option in my opinion, I've done the same (as in use a protocol). However you could make a small change to make it a bit more legible by getting the nib name like this: `let nibName = "\(Self.self)"` Using a protocol means you easily allow any view subclass to adopt this functionality for free without having to inherit – ssh88 Feb 20 '17 at 14:35
1

You can use this:

if let customView = Bundle.main.loadNibNamed("MyCustomView", owner: self, options: nil)?.first as? MyCustomView {
        // Set your view here with instantiated customView
}
Zaldy Bughaw
  • 797
  • 8
  • 16