Jak utworzyć IBInspectable typu wyliczenie

83

enumto nie atrybut definiuje czas pracy interfejsu Builder. Poniższe informacje nie są wyświetlane w Inspektorze atrybutów programu Interface Builder:

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

Z dokumentacji: Możesz dołączyć atrybut IBInspectable do dowolnej właściwości w deklaracji klasy, rozszerzeniu klasy lub kategorii dla dowolnego typu, który jest obsługiwany przez zdefiniowane w programie Interface Builder atrybuty środowiska wykonawczego: wartość logiczna, liczba całkowita lub zmiennoprzecinkowa, ciąg, zlokalizowany ciąg, prostokąt , punkt, rozmiar, kolor, zakres i zero.

P: Jak mogę zobaczyć enumInspektor atrybutów w Interface Builder?

SwiftArchitect
źródło
1
Gdzie jest wyliczenie na tej liście? Dlaczego myślisz, że możesz użyć wyliczenia?
Paulw11,
13
Byłoby miło móc wybrać enumprzypadek prosto z IB, jak przypuszczam, lub a UIFont, tak jak natywne obiekty UIKit.
SwiftArchitect

Odpowiedzi:

76

Szybki 3

@IBInspectable var shape:StatusShape = .Rectangle po prostu tworzy pusty wpis w Interface Builder:

Niedostępne w IB

Użyj adaptera, który będzie działał jako pomost między Swift a Interface Builder.
shapeAdaptermożna sprawdzić od IB:

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

Dostępne w wersji IB

W przeciwieństwie do metody kompilacji warunkowej (przy użyciu #if TARGET_INTERFACE_BUILDER), typ shapezmiennej nie zmienia się wraz z celem, potencjalnie wymagając dalszych zmian w kodzie źródłowym, aby poradzić sobie z wersjami shape:NSIntegervs.shape:StatusShape

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

Kompletny kod

@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
        }
    }
}

► Znajdź to rozwiązanie na GitHub .

SwiftArchitect
źródło
Dlaczego zachowujesz shapeAsIntjako własność przechowywaną, a nie obliczoną?
nhgrif
Dobrze. Nie możesz jednak utworzyć właściwości writeonly... ale właściwość obliczona nie tworzy zmiennej instancji zapasowej, tak jak robi to twoja właściwość przechowywana.
nhgrif
Dodano rozwiązanie bez magazynu zapasowego zaproponowane przez @nhgrif.
SwiftArchitect
1
Przepraszam za mój głos przeciw. Myślałem, że mam działające rozwiązanie z TARGET_INTERFACE_BUILDER, ale zostałem wprowadzony w błąd. Przepraszamy, powodzenia w SO
Dan Rosenstark
szybko do przodu do 2017 r. czy ten problem pozostaje ten sam?
NoSixties
41

Zamiast ustawiać możliwe do sprawdzenia wyliczenia z wartościami int, możesz również ustawić je za pomocą ciągów. Chociaż nie jest tak preferowana jak lista rozwijana, przynajmniej ta opcja zapewnia pewien poziom czytelności.

Opcja tylko w wersji Swift:

// 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
        }
    }
}

Możliwe jest również, aby to działało również z celem-c, dodając inicjator do wyliczenia. Jednak kompilator pokaże tylko błąd „niedostępny” dla właściwości tylko IB w kodzie Swift.

Opcja Swift z kompatybilnością Obj-C:

@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
        }
    }
}
uɥƃnɐʌuop
źródło
2
Podoba mi się ta Swift Onlywersja: pozwala na zwykły angielski w IB .
SwiftArchitect
Czy ktoś wie, jak uzyskać opcje prezentowane jako lista rozwijana ciągów w IB przy użyciu tej metody?
airowe
Chciałbym wiedzieć, jeśli to możliwe, jak to zrobić za pomocą rozwijanego menu.
Wael
19

Nie pamiętam szybkiej składni, ale tak to rozwiązałem w obj-c

#if TARGET_INTERFACE_BUILDER
@property (nonatomic) IBInspectable NSInteger shape;
#else
@property (nonatomic) StatusShape shape;
#endif
Jason Bobier
źródło
1
Jedynym miejscem, w którym mam montaż warunkowy, jest to jedno miejsce w deklaracji. Brak asemblacji warunkowej nigdzie indziej, ponieważ wyliczenie jest wartością całkowitą w obj-c. Jedno miejsce do usunięcia, gdy Apple to naprawi (zakładając, że tak się stanie).
Jason Bobier
To dowcipne i niesamowite rozwiązanie, o którym wspomniano tutaj nshipster.com/ibinspectable-ibdesignable
Dan Rosenstark
7

Na rok 2020 - odpowiedź @SwiftArchitect zaktualizowana na dziś:

Oto typowy pełny przykład z całą dzisiejszą składnią

wprowadź opis obrazu tutaj

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
        }
    }
    

i tylko przykład użycia ...

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

W przypadku tej krytycznej kontroli jakości Swift / iOS

• bardzo stara odpowiedź @SwiftArchitect jest całkowicie poprawna, ale

• Właśnie zaktualizowałem go i dodałem krytyczną „niedostępną” rzecz, która jest teraz możliwa w Swift.

Fattie
źródło
4

To stary wątek, ale przydatny. Dostosowałem moją odpowiedź do Swift 4.0 i Xcode 9.0 - Swift 4 ma własne małe problemy z tym problemem. Mam zmienną @IBInspectable z typem wyliczenia i Xcode 9.0 nie jest zadowolony, pokazując mi tę „Właściwość nie może być oznaczona jako @IBInspectable, ponieważ jej typ nie może być reprezentowany w Objective-c”

@Eporediese częściowo rozwiązuje ten problem (dla swift3); używając właściwości dla scenorysu, ale prostego wyliczenia dla pozostałej części kodu. Poniżej znajduje się bardziej kompletny zestaw kodów, który zawiera właściwość do pracy w obu przypadkach.

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
O mój
źródło
2

Rozwiązanie Swift 3 oparte na 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
źródło
2
Jeśli umieścisz tę właściwość inspekcyjną IB w #if TARGET_INTERFACE_BUILDERbloku, wpłynie to tylko na renderowanie w programie Interface Builder, a nie w czasie wykonywania. Spowoduje to prawdopodobnie nieoczekiwane zachowanie i zawsze otrzymasz ostrzeżenie w konsoli:Failed to set (statusShapeIB) user defined inspected property ...: this class is not key value coding-compliant for the key statusShapeIB.
Mischa