Rightpoint / Eject

An eject button for Interface Builder to generate swift code
MIT License
522 stars 24 forks source link

States! #27

Closed BrianSemiglia closed 7 years ago

BrianSemiglia commented 7 years ago

Just a thought but what if, as a user, I could:

  1. Create a layout in interface builder.
  2. Duplicate that view and create second, third, nth variations/states.
  3. Eject xibs into view-decorating extensions and playground file that demos animated/sudden state changes.

Code like this:

let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor(white: 1, alpha: 1)

let button = UIButton(type: .roundedRect)
button.titleLabel?.lineBreakMode = .byTruncatingMiddle
button.isOpaque = false
button.translatesAutoresizingMaskIntoConstraints = false
button.clipsToBounds = true
button.setContentCompressionResistancePriority(1, for: .vertical)
button.setTitle("Button", for: .normal)

view.addSubview(button)
contentView.addSubview(view)

would become...

extension UITableViewCell {
    func decorate() {
        let view = UIView()
        view.setState_XcodeSpecificLabel_()
        let button = UIButton()
        button.setState_XcodeSpecificLabel1_()
        view.addSubview(button)
        contentView.addSubview(view)
        button.setState_XcodeSpecificLabel2_()
    }
}

extension UIView {
    func setState_XcodeSpecificLabel_() {
        translatesAutoresizingMaskIntoConstraints = false
        backgroundColor = UIColor(white: 1, alpha: 1)
    }
}

extension UIButton {
    func setState_XcodeSpecificLabel1_() {
        type = .roundedRect
        titleLabel?.lineBreakMode = .byTruncatingMiddle
        isOpaque = false
        translatesAutoresizingMaskIntoConstraints = false
        clipsToBounds = true
        setContentCompressionResistancePriority(1, for: .vertical)
        setTitle("Button", for: .normal)
    }
}

extension UIButton {
    func setState_XcodeSpecificLabel2_() {
         type = .roundedRect
         titleLabel?.lineBreakMode = .byTruncatingMiddle
         isOpaque = false
         translatesAutoresizingMaskIntoConstraints = false
         clipsToBounds = true
         setContentCompressionResistancePriority(1, for: .vertical)
         setTitle("Button", for: .normal)
    }
}

Pros

  1. Compose states visually

Cons

  1. Extensions approach requires concrete types
  2. Changes to XIB-states would sometimes require changes to sibling states, which would be error-prone.
KingOfBrian commented 7 years ago

Hey Brian! I am looking into allowing configuration of the structure of the code beyond 1 giant method. This should allow you to structure configuration methods like you have above, with one method for all of the top level views. This will probably be with a better interface on the Reference object and a .stencil file, similar to what SwiftGen uses.

The pattern you state is a bit tricky though, as there is idempotent configuration, and additive configuration (adding subviews) and the approach above would require them to be all idempotent. There are ways around this, but would require a few extra moving parts than what Eject provides.

Also, I'm not sure that Eject will mature enough into a tool that could be used in the build system (IE: Using eject to enhance IB usage, not replace IB usage). I'd love to say it would be, but the current goal is to help users transition xib files out of IB, so your milage may vary!