So erstellen Sie eine IBInspectable vom Typ enum

82

enumist kein vom Interface Builder definiertes Laufzeitattribut. Folgendes wird im Attributinspektor von Interface Builder nicht angezeigt:

enum StatusShape:Int {
    case Rectangle = 0
    case Triangle = 1
    case Circle = 2
}
@IBInspectable var shape:StatusShape = .Rectangle

Aus der Dokumentation: Sie können das IBInspectable-Attribut an jede Eigenschaft in einer Klassendeklaration, Klassenerweiterung oder Kategorie für jeden Typ anhängen, der von den vom Interface Builder definierten Laufzeitattributen unterstützt wird: Boolesche Zahl, Ganzzahl- oder Gleitkommazahl, Zeichenfolge, lokalisierte Zeichenfolge, Rechteck , Punkt, Größe, Farbe, Reichweite und Null.

F: Wie kann ich einen enumim Attributinspektor des Interface Builder anzeigen?

SwiftArchitect
quelle
1
Wo ist Enum in dieser Liste? Warum denkst du, kannst du eine Aufzählung verwenden?
Paulw11
11
Es wäre schön, einen enumFall direkt von IB auswählen zu können , oder einen UIFont, wie es die nativen UIKit-Objekte können.
SwiftArchitect

Antworten:

74

Swift 3

@IBInspectable var shape:StatusShape = .Rectangle erstellt lediglich einen leeren Eintrag in Interface Builder:

Nicht verfügbar in IB

Verwenden Sie einen Adapter, der als Brücke zwischen Swift und Interface Builder fungiert.
shapeAdapterist von IB inspizierbar:

   // IB: use the adapter
   @IBInspectable var shapeAdapter:Int {
        get {
            return self.shape.rawValue
        }
        set( shapeIndex) {
            self.shape = StatusShape(rawValue: shapeIndex) ?? .Rectangle
        }
    }

Verfügbar in IB

Im Gegensatz zum Ansatz der bedingten Kompilierung (unter Verwendung #if TARGET_INTERFACE_BUILDER) shapeändert sich der Typ der Variablen nicht mit dem Ziel, sodass möglicherweise weitere Änderungen des Quellcodes erforderlich sind, um die shape:NSIntegervs.- shape:StatusShapeVariationen zu bewältigen :

   // Programmatically: use the enum
   var shape:StatusShape = .Rectangle

Vollständiger Code

@IBDesignable
class ViewController: UIViewController {

    enum StatusShape:Int {
        case Rectangle
        case Triangle
        case Circle
    }

    // Programmatically: use the enum
    var shape:StatusShape = .Rectangle

    // IB: use the adapter
    @IBInspectable var shapeAdapter:Int {
        get {
            return self.shape.rawValue
        }
        set( shapeIndex) {
            self.shape = StatusShape(rawValue: shapeIndex) ?? .Rectangle
        }
    }
}

► Finden Sie diese Lösung auf GitHub .

SwiftArchitect
quelle
Warum behalten Sie shapeAsInteine gespeicherte Eigenschaft und keine berechnete Eigenschaft?
nhgrif
Richtig. Sie können zwar keine Eigenschaft writeonlyerstellen, aber eine berechnete Eigenschaft erstellt keine Hintergrundinstanzvariable wie Ihre gespeicherte Eigenschaft.
nhgrif
Es wurde eine von @nhgrif vorgeschlagene No-Backing-Store- Lösung hinzugefügt.
SwiftArchitect
1
Entschuldigen Sie meine Ablehnung. Ich dachte, ich hätte eine funktionierende Lösung mit TARGET_INTERFACE_BUILDER, wurde aber irregeführt. Entschuldigung, viel Glück hier auf SO
Dan Rosenstark
Schneller Vorlauf bis 2017 bleibt dieses Problem immer noch dasselbe?
NoSixties
40

Anstatt Ihre überprüfbaren Aufzählungen mit Ints festzulegen, können Sie sie auch mit Zeichenfolgen festlegen. Obwohl diese Option nicht ganz so vorzuziehen ist wie ein Dropdown, bietet sie zumindest ein gewisses Maß an Lesbarkeit.

Nur-Swift-Option:

// 1. Set up your enum
enum Shape: String {
    case Rectangle = "rectangle" // lowercase to make it case-insensitive
    case Triangle = "triangle"
    case Circle = "circle"
}


// 2. Then set up a stored property, which will be for use in code
var shape = Shape.Rectangle // default shape


// 3. And another stored property which will only be accessible in IB (because the "unavailable" attribute prevents its use in code)
@available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'shape' instead.")
@IBInspectable var shapeName: String? {
    willSet {
        // Ensure user enters a valid shape while making it lowercase.
        // Ignore input if not valid.
        if let newShape = Shape(rawValue: newValue?.lowercased() ?? "") {
            shape = newShape
        }
    }
}

Es ist möglich, dies auch mit Objective-C zum Laufen zu bringen, indem der Aufzählung ein Initialisierer hinzugefügt wird. Der Compiler zeigt jedoch nur den Fehler "nicht verfügbar" für Ihre Nur-IB-Eigenschaften in schnellem Code an.

Schnelle Option mit Obj-C-Kompatibilität:

@objc enum Shape: Int {
    case None
    case Rectangle
    case Triangle
    case Circle

    init(named shapeName: String) {
        switch shapeName.lowercased() {
        case "rectangle": self = .Rectangle
        case "triangle": self = .Triangle
        case "circle": self = .Circle
        default: self = .None
        }
    }
}

var shape = Shape.Rectangle // default shape

@available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'shape' instead.")
@IBInspectable var shapeName: String? {
    willSet {
        if let newShape = Shape(rawValue: newValue?.lowercased() ?? "") {
            shape = newShape
        }
    }
}
Don Vaughn
quelle
2
Ich mag die Swift OnlyVersion: Sie erlaubt einfaches Englisch in IB .
SwiftArchitect
Weiß jemand, wie man die Optionen mit dieser Methode als Dropdown-Liste von Zeichenfolgen in IB darstellt?
Airowe
Ich würde gerne wissen, wie dies mit einem Dropdown möglich ist.
Wael
19

Ich kann mich nicht an die schnelle Syntax erinnern, aber so habe ich sie in obj-c gelöst

#if TARGET_INTERFACE_BUILDER
@property (nonatomic) IBInspectable NSInteger shape;
#else
@property (nonatomic) StatusShape shape;
#endif
Jason Bobier
quelle
1
Der einzige Ort, an dem ich die bedingte Versammlung habe, ist dieser eine Punkt in der Erklärung. Keine bedingte Assembly irgendwo anders, da eine Aufzählung ein ganzzahliger Wert in obj-c ist. Eine Stelle, die entfernt werden muss, wenn Apple dies behebt (vorausgesetzt, dass dies der
Fall ist
Dies ist eine witzige und großartige Lösung, die hier erwähnt wird. Nshipster.com/ibinspectable-ibdesignable
Dan Rosenstark
7

Für 2020 - @SwiftArchitect Antwort für heute aktualisiert:

Hier ist ein typisches vollständiges Beispiel mit der gesamten heutigen Syntax

Geben Sie hier die Bildbeschreibung ein

import UIKit

@IBDesignable class ClipLabels: UILabel {
    
    enum Side: Int { case left, right }
    
    var side: Side = .left {
        didSet {
            common()
        }
    }
    
    @available(*, unavailable, message: "IB only")
    @IBInspectable var leftRight01: Int {
        get {
            return self.side.rawValue
        }
        set(index) {
            self.side = Side(rawValue: index) ?? .left
        }
    }
    

und nur ein Anwendungsbeispiel ...

switch side {
    case .left:
        textColor = .red
    case .right:
        textColor = .green
    }

Für diese wichtige Swift / iOS-Qualitätssicherung

• Die sehr alte Antwort von @SwiftArchitect ist aber vollkommen richtig

• Ich habe es gerade aktualisiert und das kritische "nicht verfügbare" Ding hinzugefügt, das jetzt in Swift möglich ist.

Fattie
quelle
4

Dies ist ein alter Thread, aber nützlich. Ich habe meine Antwort auf Swift 4.0 und Xcode 9.0 angepasst - Swift 4 hat seine eigenen kleinen Probleme mit diesem Problem. Ich habe eine @ IBInspectable-Variable mit Aufzählungstyp und Xcode 9.0 ist nicht zufrieden. Dies zeigt mir, dass "Eigenschaft nicht als @IBInspectable markiert werden kann, da ihr Typ in Objective-c nicht dargestellt werden kann".

@Eporediese beantwortet dieses Problem (für swift3) teilweise; Verwenden einer Eigenschaft für das Storyboard, aber einer geraden Aufzählung für den Rest des Codes. Im Folgenden finden Sie einen vollständigeren Codesatz, mit dem Sie in beiden Fällen mit einer Eigenschaft arbeiten können.

enum StatusShape: Int {
  case Rectangle = 0
  case Triangle = 1
  case Circle = 2
}
var _shape:StatusShape = .Rectangle  // this is the backing variable

#if TARGET_INTERFACE_BUILDER
  @IBInspectable var shape: Int {    // using backing variable as a raw int

    get { return _shape.rawValue }
    set {
      if _shape.rawValue != newValue {
        _shape.rawValue = newValue
      }
    }
}
#else
var shape: StatusShape {  // using backing variable as a typed enum
  get { return _shape }
  set {
    if _shape != newValue {
      _shape = newValue
    }
  }
}
#endif
Oh mein
quelle
2

Swift 3- Lösung basierend auf SwiftArchitect

enum StatusShape: Int {
    case rectangle, triangle, circle
}
var statusShape: StatusShape = .rectangle
#if TARGET_INTERFACE_BUILDER
@IBInspectable var statusShapeIB: Int {
    get { 
        return statusShape.rawValue 
    }
    set { 
        guard let statusShape = StatusShape(rawValue: newValue) else { return }
        self.statusShape = statusShape
    }
}   //convenience var, enum not inspectable
#endif
Eporediese
quelle
2
Wenn Sie diese IB-inspizierbare Eigenschaft in einen #if TARGET_INTERFACE_BUILDERBlock einschließen, wirkt sich dies nur auf das Rendern in Interface Builder aus, nicht zur Laufzeit. Dies wird wahrscheinlich zu unerwartetem Verhalten führen und Sie werden immer eine Warnung in der Konsole erhalten:Failed to set (statusShapeIB) user defined inspected property ...: this class is not key value coding-compliant for the key statusShapeIB.
Mischa