Як створити IBInspectable типу enum


83

enumце НЕ є Інтерфейс Builder атрибута визначається у час виконання. Наведене нижче не відображається в Інспекторі атрибутів Interface Builder:

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

З документації: Ви можете приєднати атрибут IBInspectable до будь-якої властивості в декларації класу, розширенні класу або категорії для будь-якого типу, що підтримується визначеними конструктором інтерфейсу атрибутами середовища виконання: булевим, цілим чи числом із плаваючою комою, рядком, локалізованим рядком, прямокутником , точка, розмір, колір, діапазон та нуль.

З: Як я можу побачити enumінспектор атрибутів Interface Builder?


1
Де перелік у цьому списку? Чому ви думаєте, що можете використовувати перерахування?
Paulw11,

12
Було б непогано мати можливість вибрати enumсправу прямо від IB, я вважаю, або UIFont, як, можливо, рідні об’єкти UIKit.
SwiftArchitect

Відповіді:


75

Стрімкий 3

@IBInspectable var shape:StatusShape = .Rectangle просто створює порожній запис у Interface Builder:

Не доступний у IB

Використовуйте адаптер, який буде виконувати роль моста між Swift та Interface Builder.
shapeAdapterперевіряється з боку IB:

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

Доступно в IB

В відміну від умовної компіляції підходу ( з використанням #if TARGET_INTERFACE_BUILDER), тип shapeзмінної не змінюється з метою, потенційно потребують подальших змін вихідного коду , щоб впоратися з shape:NSIntegerпроти shape:StatusShapeзмін:

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

Повний код

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

► Знайдіть це рішення на GitHub .


Чому ви зберігаєте shapeAsIntяк збережене майно, а не як обчислене?
nhgrif

Правильно. Ви не можете створити властивість writeonly... але обчислена властивість не створює змінну резервного екземпляра, як це робить ваша збережена властивість.
nhgrif

Додано рішення без зберігання резервних копій, запропоноване @nhgrif.
SwiftArchitect

1
Вибачте за мій голос проти. Я думав, що маю робоче рішення з TARGET_INTERFACE_BUILDER, але мене ввели в оману. Вибачте, найкращого вам тут, на SO
Dan Rosenstark

вперед до 2017 року, чи залишається це питання незмінним?
NoSixties

40

Замість того, щоб встановлювати перевірявані переліки за допомогою ints, ви також можете встановити їх за допомогою рядків. Хоча це не настільки бажано, як випадаюче меню, принаймні цей варіант пропонує певний рівень читабельності.

Варіант лише швидкого:

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

Це також можна змусити працювати з ціллю-c, додавши ініціалізатор до переліку. Однак компілятор покаже лише швидку помилку "недоступний" для ваших властивостей лише для IB.

Швидкий варіант із сумісністю 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
        }
    }
}

2
Мені подобається Swift Onlyверсія: вона дозволяє простою англійською мовою в IB .
SwiftArchitect

Хтось знає, як за допомогою цього методу отримати варіанти, представлені як випадаючі рядки в IB?
airowe

Я хотів би знати, якщо це можливо, як це зробити за допомогою випадаючого списку, будь ласка.
Ваель

19

Я не пам’ятаю швидкого синтаксису, але ось як я це вирішив у obj-c

#if TARGET_INTERFACE_BUILDER
@property (nonatomic) IBInspectable NSInteger shape;
#else
@property (nonatomic) StatusShape shape;
#endif

1
Єдине місце, де я маю умовне зібрання, - це одне місце в декларації. Ніякої умовної збірки ніде більше немає, оскільки перелік є цілим числом у obj-c. Видалити одне місце, коли яблуко це виправить (припускаючи, що вони це зроблять)
Джейсон Боб’є

Це дотепне та дивовижне рішення, про яке згадувалося тут nshipster.com/ibinspectable-ibdesignable
Ден

7

На 2020 рік - відповідь @SwiftArchitect оновлена ​​на сьогодні:

Ось типовий повний приклад із усім сьогоднішнім синтаксисом

введіть тут опис зображення

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

і лише приклад використання ...

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

Для цього критичного контролю якості Swift / iOS,

• дуже стара відповідь @SwiftArchitect є абсолютно правильною, але

• Я щойно оновив його та додав критичну "недоступну" річ, яка тепер можлива у Swift.


4

Це стара тема, але корисна. Я адаптував свою відповідь до swift 4.0 та Xcode 9.0 - Swift 4 має свої власні проблеми з цією проблемою. У мене є змінна @IBInspectable з типом переліку, і Xcode 9.0 не задоволений, показуючи мені це "Властивість не може бути позначена @IBInspectable, оскільки її тип не може бути представлений у Objective-c"

@Eporediese частково відповідає на цю проблему (для swift3); використання властивості для розкадрування, але пряме перерахування для решти коду. Нижче наведено більш повний набір коду, який надає вам властивість працювати з обома випадками.

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

2

Рішення Swift 3 на основі 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

2
Якщо ви обернете цю властивість, що перевіряється IB, у #if TARGET_INTERFACE_BUILDERблок, це вплине лише на рендеринг у Interface Builder, а не під час виконання. Це, швидше за все, призведе до несподіваної поведінки, і ви завжди будете отримувати попередження на консолі:Failed to set (statusShapeIB) user defined inspected property ...: this class is not key value coding-compliant for the key statusShapeIB.
Міша,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.