Как создать 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 к любому свойству в объявлении класса, расширении класса или категории для любого типа, который поддерживается определенными атрибутами времени выполнения Interface Builder: логическое, целое число или число с плавающей запятой, строка, локализованная строка, прямоугольник , точка, размер, цвет, диапазон и ноль.

В: Как я могу увидеть enumинспектор атрибутов в Интерфейсном Разработчике?

SwiftАрхитектор
источник
1
Где в этом списке перечисление? Почему вы думаете, что можете использовать перечисление?
Paulw11
13
Было бы неплохо иметь возможность выбрать enumслучай прямо из IB, я полагаю, или UIFont, как это могут делать собственные объекты UIKit.
SwiftArchitect

Ответы:

76

Swift 3

@IBInspectable var shape:StatusShape = .Rectangle просто создает пустую запись в Интерфейсном Разработчике :

Недоступно в IB

Используйте адаптер, который будет действовать как мост между Swift и Interface Builder.
shapeAdapterпроверяется из ИБ:

   // 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:NSIntegervs.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 .

SwiftАрхитектор
источник
Почему вы shapeAsIntхраните свойство, а не вычисляемое?
nhgrif
Правильно. Однако вы не можете создать свойство writeonly... но вычисленное свойство не создает переменную резервного экземпляра, как это делает ваше сохраненное свойство.
nhgrif
Добавлено решение без резервного хранилища, предложенное @nhgrif.
SwiftArchitect
1
Извините за мой голос против. Я думал, что у меня есть рабочее решение с TARGET_INTERFACE_BUILDER, но меня обманули. Извините, удачи здесь, на SO
Дэн Розенстарк
перенесемся в 2017 год. Остается ли эта проблема прежней?
NoSixties
41

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

Вариант только для 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
        }
    }
}

Это является возможным также получить эту работу с Objective-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
        }
    }
}
uɥƃnɐʌuop
источник
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. Одно место, которое нужно удалить, когда Apple исправит это (при условии, что они это сделают)
Джейсон Бобье,
Это остроумное и потрясающее решение, упомянутое здесь 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
    }

Для этого критически важного QA 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
Eporediese
источник
2
Если вы заключите это свойство IB-inspectable в #if TARGET_INTERFACE_BUILDERблок, это повлияет только на рендеринг в Интерфейсном Разработчике, но не во время выполнения. Скорее всего, это приведет к неожиданному поведению, и вы всегда будете получать предупреждение в консоли:Failed to set (statusShapeIB) user defined inspected property ...: this class is not key value coding-compliant for the key statusShapeIB.
Миша,