Load view from an external xib file in storyboard

后端 未结 10 2251
一生所求
一生所求 2020-11-27 09:49

I want to use a view throughout multiple viewcontrollers in a storyboard. Thus, I thought about designing the view in an external xib so changes are reflected in every viewc

10条回答
  •  野趣味
    野趣味 (楼主)
    2020-11-27 10:37

    For a while Christopher Swasey's approach was the best approach I had found. I asked a couple of the senior devs on my team about it and one of them had the perfect solution! It satisfies every one of the concerns that Christopher Swasey so eloquently addressed and it doesn't require boilerplate subclass code(my main concern with his approach). There is one gotcha, but other than that it is fairly intuitive and easy to implement.

    1. Create a custom UIView class in a .swift file to control your xib. i.e. MyCustomClass.swift
    2. Create a .xib file and style it as you want. i.e. MyCustomClass.xib
    3. Set the File's Owner of the .xib file to be your custom class (MyCustomClass)
    4. GOTCHA: leave the class value (under the identity Inspector) for your custom view in the .xib file blank. So your custom view will have no specified class, but it will have a specified File's Owner.
    5. Hook up your outlets as you normally would using the Assistant Editor.
      • NOTE: If you look at the Connections Inspector you will notice that your Referencing Outlets do not reference your custom class (i.e. MyCustomClass), but rather reference File's Owner. Since File's Owner is specified to be your custom class, the outlets will hook up and work propery.
    6. Make sure your custom class has @IBDesignable before the class statement.
    7. Make your custom class conform to the NibLoadable protocol referenced below.
      • NOTE: If your custom class .swift file name is different from your .xib file name, then set the nibName property to be the name of your .xib file.
    8. Implement required init?(coder aDecoder: NSCoder) and override init(frame: CGRect) to call setupFromNib() like the example below.
    9. Add a UIView to your desired storyboard and set the class to be your custom class name (i.e. MyCustomClass).
    10. Watch IBDesignable in action as it draws your .xib in the storyboard with all of it's awe and wonder.

    Here is the protocol you will want to reference:

    public protocol NibLoadable {
        static var nibName: String { get }
    }
    
    public extension NibLoadable where Self: UIView {
    
        public static var nibName: String {
            return String(describing: Self.self) // defaults to the name of the class implementing this protocol.
        }
    
        public static var nib: UINib {
            let bundle = Bundle(for: Self.self)
            return UINib(nibName: Self.nibName, bundle: bundle)
        }
    
        func setupFromNib() {
            guard let view = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") }
            addSubview(view)
            view.translatesAutoresizingMaskIntoConstraints = false
            view.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
            view.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
            view.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
            view.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
        }
    }
    

    And here is an example of MyCustomClass that implements the protocol (with the .xib file being named MyCustomClass.xib):

    @IBDesignable
    class MyCustomClass: UIView, NibLoadable {
    
        @IBOutlet weak var myLabel: UILabel!
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            setupFromNib()
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            setupFromNib()
        }
    
    }
    

    NOTE: If you miss the Gotcha and set the class value inside your .xib file to be your custom class, then it will not draw in the storyboard and you will get a EXC_BAD_ACCESS error when you run the app because it gets stuck in an infinite loop of trying to initialize the class from the nib using the init?(coder aDecoder: NSCoder) method which then calls Self.nib.instantiate and calls the init again.

提交回复
热议问题