@IBInspectable avec enum?

86

J'aimerais créer @IBInspectable élément comme vous le voyez sur l'image ci-dessous:

entrez la description de l'image ici

mon idée est d'utiliser quelque chose comme enum comme type pour @IBInspectable, mais il semble que ce n'est pas le cas, des idées comment implémenter un élément comme celui-ci?

ÉDITER:

Il semble que @IBInspectableseuls ces types soient pris en charge:

  • Int
  • CGFloat
  • Double
  • String
  • Bool
  • CGPoint
  • CGSize
  • CGRect
  • UIColor
  • UIImage

décevoir

ignotusverum
la source
Une solution de contournement, en quelque sorte, consiste à placer une propriété calculée inspectable devant la valeur que vous souhaitez définir. Bien sûr, il n'apparaîtra toujours pas comme par magie comme un menu contextuel de valeurs énumérées dans Interface Builder; mais au moins vous pouvez définir une valeur inspectable et l'utiliser pour définir une énumération.
mat
8
À la WWDC cette année, j'ai interrogé un ingénieur Apple du laboratoire des outils de développement à ce sujet. Il a dit qu'il était d'accord que ce serait une fonctionnalité intéressante, mais ce n'est actuellement pas possible. Il m'a suggéré de déposer un radar sur bugreport.apple.com, ce que j'ai fait. Il a été fermé en tant que duplicata du numéro 15505220, mais je recommanderais vivement aux gens de poser des problèmes similaires. Ces problèmes sont souvent résolus si suffisamment de personnes se plaignent.
Will Clarke
1
en quoi cette question est-elle différente de stackoverflow.com/questions/27432736/…
SwiftArchitect
Copie
SwiftArchitect
Et avec SwiftUI et le nouveau Canvas dans Xcode 11, il semble que cela ne sera jamais sur la feuille de route d'Apple
Ben Leggiero

Réponses:

26

Ce n'est pas possible (pour l'instant). Vous ne pouvez utiliser que les types que vous voyez dans la section Attributs d'exécution définis par l'utilisateur .

D'après le document d'Apple :

Vous pouvez attacher l'attribut IBInspectable à n'importe quelle propriété dans une déclaration de classe, une extension de classe ou une catégorie pour tout type pris en charge par les attributs d'exécution définis par Interface Builder: booléen, entier ou nombre à virgule flottante, chaîne, chaîne localisée, rectangle, point, taille , couleur, plage et nul.

yusuke024
la source
2
Lorsque je veux utiliser une énumération, je donne aux valeurs enum des affectations explicites (= 1, = 2, etc.). Et dans le gestionnaire, j'ajoute une assertion si la valeur de IB n'est pas l'une des valeurs de l'énumération. C'est ennuyeux que les énumérations ne soient pas prises en charge, mais cette astuce les rend au moins plus utilisables.
Zev Eisenberg le
Une énumération est un entier.
Amin Negm-Awad du
23

Une autre solution consiste à modifier l'apparence d'une propriété d'énumération au générateur d'interface. Par exemple:

#if TARGET_INTERFACE_BUILDER
@property (nonatomic, assign) IBInspectable NSInteger fontWeight;
#else
@property (nonatomic, assign) FontWeight fontWeight;
#endif

Cela suppose une énumération appelée FontWeight. Il repose sur le fait que les énumérations et leurs valeurs entières brutes peuvent être utilisées de manière interchangeable en Objective-C. Après cela, vous pouvez spécifier un entier dans le générateur d'interface pour la propriété qui n'est pas idéal, mais qui fonctionne et conserve une petite quantité de sécurité de type lors de l'utilisation de la même propriété par programme.

C'est une meilleure alternative que de déclarer une propriété entière distincte car vous n'avez pas besoin d'écrire une logique supplémentaire pour gérer une deuxième propriété entière qui pourrait également être utilisée pour accomplir la même chose.

Cependant, cela ne fonctionne pas avec Swift car nous ne pouvons pas convertir implicitement d'un entier en une énumération. Toute réflexion sur la solution serait appréciée.

Anthony Mattox
la source
2
Je pensais vraiment que cela fonctionnait dans Swift, mais lors de tests supplémentaires ... non
Dan Rosenstark
7

Je fais cela en utilisant une valeur NSInteger Inspectable et remplace le setter pour lui permettre de définir l'énumération. Cela a la limitation de ne pas utiliser de liste contextuelle et si vous modifiez vos valeurs d'énumération, les options d'interface ne seront pas mises à jour pour correspondre.

Exemple.

Dans le fichier d'en-tête:

typedef NS_ENUM(NSInteger, LabelStyle)
{
    LabelStyleContent = 0, //Default to content label
    LabelStyleHeader,
};

...

@property LabelStyle labelStyle;
@property (nonatomic, setter=setLabelAsInt:) IBInspectable NSInteger labelStyleLink;

Dans le fichier d'implémentation:

- (void)setLabelAsInt:(NSInteger)value
{
    self.labelStyle = (LabelStyle)value;
}

Vous pouvez éventuellement ajouter de la logique pour vous assurer qu'il est défini sur une valeur valide

Matthew Cawley
la source
Je ne sais pas pourquoi ce vote a été rejeté. Cela semble être un bon moyen de contourner le problème.
Fogmeister
Merci @Fogmeister - J'utilise actuellement cette implémentation dans une application pour le moment :)
Matthew Cawley
4

Sikhapol est correct, les énumérations ne sont pas encore prises en charge également pas dans xCode 9. Je crois que l'approche la plus sûre consiste à utiliser les énumérations comme chaînes et à implémenter une variable IBInspectable "shadow" (privée). Voici un exemple d'élément BarBtnPaintCode qui représente un élément de bouton de barre qui peut être stylisé avec une icône personnalisée (cela est fait à l'aide de PaintCode) directement dans Interface Builder (swift 4).

Dans la construction de l'interface, vous entrez simplement la chaîne (identique à la valeur d'énumération), ce qui la maintient claire (si vous entrez des nombres, personne ne sait ce qu'ils signifient)

class BarBtnPaintCode: BarBtnPaintCodeBase {

    enum TypeOfButton: String {
        case cancel
        case ok
        case done
        case edit
        case scanQr
        //values used for tracking if wrong input is used
        case uninitializedLoadedFromStoryboard
        case unknown
    }

    var typeOfButton = TypeOfButton.uninitializedLoadedFromStoryboard

    @IBInspectable private var type : String {
        set {
            typeOfButton = TypeOfButton(rawValue: newValue) ?? .unknown
            setup()
        }
        get {
            return typeOfButton.rawValue
        }
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    init(typeOfButton: TypeOfButton, title: String? = nil, target: AnyObject?, action: Selector) {
        super.init()
        self.typeOfButton = typeOfButton
        setup()
        self.target = target
        self.action = action
        self.title  = title
    }

    override func setup() {
        //same for all
        setTitleTextAttributes([NSAttributedStringKey.font : UIFont.defaultFont(size: 15)],for: UIControlState.normal)
        //depending on the type
        switch typeOfButton {
        case .cancel  :
            title = nil
            image = PaintCode.imageOfBarbtn_cancel(language: currentVisibleLanguage)
        case .ok      :
            title = nil
            image = PaintCode.imageOfBarbtn_ok(language: currentVisibleLanguage)
        case .done    :
            title = nil
            image = PaintCode.imageOfBarbtn_done(language: currentVisibleLanguage)
        case .edit    :
            title = nil
            image = PaintCode.imageOfBarbtn_edit(language: currentVisibleLanguage)
        case .uninitializedLoadedFromStoryboard :
            title = nil
            image = PaintCode.imageOfBarbtn_unknown
            break
        case .unknown:
            log.error("BarBtnPaintCode used with unrecognized type")
            title = nil
            image = PaintCode.imageOfBarbtn_unknown
            break
        }

    }

}
HixField
la source
Solution fantastique, mise en œuvre avec Swift 4 sans problème. si vous utilisez ceci, faites attention à l'orthographe correcte des types dans les storyboards et les xibs
MindBlower3
1

Comme @sikhapol a répondu, ce n'est pas possible. La solution de contournement que j'utilise pour cela est d'avoir un tas de IBInspectablebooléens dans ma classe et d'en sélectionner un dans le générateur d'interface. Pour plus de sécurité que plusieurs ne sont pas définis, ajoutez un NSAssertdans le setter pour chacun.

- (void)setSomeBool:(BOOL)flag
{
    if (flag)
    {
        NSAssert(!_someOtherFlag && !_someThirdFlag, @"Only one flag can be set");
    }
}

C'est un peu fastidieux et un peu bâclé de l'OMI, mais c'est le seul moyen d'accomplir ce genre de comportement auquel je peux penser

Chris
la source
1

Je veux ajouter que les identifiants d'un enumne sont pas disponibles à l'exécution pour quiconque dans Objective-C. Il ne peut donc pas être possible de l'afficher n'importe où.

Amin Negm-Awad
la source
1

Ma solution était de faire:

@IBInspectable  
var keyboardType = UIKeyboardType.default.rawValue {
        didSet { 
             textField.keyboardType = UIKeyboardType(rawValue: keyboardType)! 
        }
}

Sur l'IB lui-même, vous devrez définir un int dans le champ keyboardType

Gal Marom
la source