it-swarm.com.de

Gibt es eine Möglichkeit für Interface Builder, IBDesignable-Ansichten zu rendern, die drawRect nicht überschreiben:

In meinen UIView-Unterklassen überschreibe ich sehr selten drawRect. Normalerweise ziehe ich es vor, layer.contents mit Bildern vor dem Rendern zu setzen und oft mehrere Unterebenen oder Unteransichten zu verwenden und diese basierend auf Eingabeparametern zu bearbeiten. Gibt es eine Möglichkeit für IB, diese komplexeren Ansichtsstapel wiederzugeben? 

60

Danke, @zisoft für die Hinweise auf prepareForInterfaceBuilder. Es gibt ein paar Nuancen mit dem Render-Zyklus von Interface Builder, die die Quelle meiner Probleme waren und die erwähnenswert sind ...

  1. Bestätigt: Sie müssen -drawRect nicht verwenden.  

Das Einstellen der Bilder in den UIButton-Steuerzuständen funktioniert. Willkürliche Layer-Stapel scheinen zu funktionieren, wenn ein paar Dinge beachtet werden ...

  1. IB verwendet initWithFrame: 

..nicht initWithCoder. awakeFromNib wird auch NICHT aufgerufen.

  1. init... wird nur einmal pro Sitzung aufgerufen

D.h. einmal pro Kompilierung, wenn Sie die Datei ändern. Wenn Sie IBInspectable-Eigenschaften ändern, wird init NICHT erneut aufgerufen. Jedoch...

  1. prepareForInterfaceBuilder wird bei jeder Eigenschaftsänderung aufgerufen

Es ist, als hätte man KVO für alle Ihre IBInspectables sowie für andere integrierte Eigenschaften. Sie können dies selbst testen, indem Sie Ihre _setup-Methode zuerst von Ihrer init..-Methode aufrufen. Das Ändern eines IBInspectable hat keine Auswirkungen. Dann fügen Sie den Aufruf auch zu prepareForInterfaceBuilder hinzu. Whahla! Beachten Sie, dass Ihr Laufzeitcode wahrscheinlich einige zusätzliche KVOs benötigt, da die Methode prepareForIB nicht aufgerufen wird. Mehr dazu unten ...

  1. init... ist zu früh zum Zeichnen, zum Einstellen des Ebeneninhalts usw.

Zumindest mit meiner UIButton-Unterklasse hat der Aufruf von [self setImage:img forState:UIControlStateNormal] keine Auswirkung in IB. Sie müssen es von prepareForInterfaceBuilder oder über einen KVO-Hook aufrufen.

  1. Wenn IB nicht gerendert wird, wird Ihre Komponente nicht gelöscht, sondern die letzte erfolgreiche Version wird beibehalten.

Kann verwirrend sein, wenn Sie Änderungen vornehmen, die keine Auswirkungen haben. Überprüfen Sie die Build-Protokolle.

  1. Tipp: Aktivitätsmonitor in der Nähe halten

Ich bekomme die ganze Zeit über ein paar verschiedene Support-Prozesse und sie nehmen die ganze Maschine mit. Übernehmen Sie Force Quit großzügig.

(UPDATE: Das war nicht wirklich wahr, seit XCode6 aus der Betaversion kam. Es hängt selten mehr)

UPDATE

  1. 6.3.1 scheint KVO in der IB-Version nicht zu mögen. Nun scheint es, als müssten Sie eine Flagge benötigen, um den Interface Builder abzufangen und nicht die KVOs einzurichten. Dies ist in Ordnung, da die prepareForInterfaceBuilder-Methode effektiv alle IBInspectable-Eigenschaften KVOs bereitstellt. Es ist bedauerlich, dass dieses Verhalten zur Laufzeit nicht irgendwie gespiegelt wird und daher den manuellen KVO erfordert. Siehe den aktualisierten Beispielcode unten.

Beispiel für die UIButton-Unterklasse

Nachfolgend finden Sie einige Beispielcodes für eine funktionierende IBDesignable UIButton-Unterklasse. ~~ Beachten Sie, prepareForInterfaceBuilder ist nicht wirklich erforderlich, da KVO auf Änderungen an unseren relevanten Eigenschaften wartet und ein Neuzeichnen auslöst. ~~ UPDATE: Siehe oben Punkt 8.

IB_DESIGNABLE
@interface SBR_InstrumentLeftHUDBigButton : UIButton

@property (nonatomic, strong) IBInspectable  NSString *topText;
@property (nonatomic) IBInspectable CGFloat topTextSize;
@property (nonatomic, strong) IBInspectable NSString *bottomText;
@property (nonatomic) IBInspectable CGFloat bottomTextSize;
@property (nonatomic, strong) IBInspectable UIColor *borderColor;
@property (nonatomic, strong) IBInspectable UIColor *textColor;

@end



@implementation HUDBigButton
{
    BOOL _isInterfaceBuilder;
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self _setup];

    }
    return self;
}

//---------------------------------------------------------------------

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self _setup];
    }
    return self;
}

//---------------------------------------------------------------------

- (void)_setup
{
    // Defaults.  
    _topTextSize = 11.5;
    _bottomTextSize = 18;
    _borderColor = UIColor.whiteColor;
    _textColor = UIColor.whiteColor;
}

//---------------------------------------------------------------------

- (void)prepareForInterfaceBuilder
{
    [super prepareForInterfaceBuilder];
    _isInterfaceBuilder = YES;
    [self _render];
}

//---------------------------------------------------------------------

- (void)awakeFromNib
{
    [super awakeFromNib];
    if (!_isInterfaceBuilder) { // shouldn't be required but jic...

        // KVO to update the visuals
        @weakify(self);
        [self
         bk_addObserverForKeyPaths:@[@"topText",
                                     @"topTextSize",
                                     @"bottomText",
                                     @"bottomTextSize",
                                     @"borderColor",
                                     @"textColor"]
         task:^(id obj, NSDictionary *keyPath) {
             @strongify(self);
             [self _render];
         }];
    }
}

//---------------------------------------------------------------------

- (void)dealloc
{
    if (!_isInterfaceBuilder) {
        [self bk_removeAllBlockObservers];
    }
}

//---------------------------------------------------------------------

- (void)_render
{
    UIImage *img = [SBR_Drawing imageOfHUDButtonWithFrame:self.bounds
                                                edgeColor:_borderColor
                                          buttonTextColor:_textColor
                                                  topText:_topText
                                              topTextSize:_topTextSize
                                               bottomText:_bottomText
                                       bottomTextSize:_bottomTextSize];

    [self setImage:img forState:UIControlStateNormal];
}

@end
129

Diese Antwort bezieht sich auf das Überschreiben von drawRect, kann jedoch einige Anregungen geben:

Ich habe eine benutzerdefinierte UIView-Klasse mit komplexen Zeichnungen in drawRect. Sie müssen auf Referenzen achten, die während der Entwurfszeit nicht verfügbar sind, z. B. UIApplication. Dafür überschreibe ich prepareForInterfaceBuilder, wo ich ein boolesches Flag setze, das ich in drawRect verwende, um zwischen Laufzeit und Designzeit zu unterscheiden:

@IBDesignable class myView: UIView {
    // Flag for InterfaceBuilder
    var isInterfaceBuilder: Bool = false    

    override init(frame: CGRect) {
        super.init(frame: frame)
        // Initialization code
    }

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

    override func prepareForInterfaceBuilder() {
        self.isInterfaceBuilder = true
    }

    override func drawRect(rect: CGRect)
    {
        // rounded cornders
        self.layer.cornerRadius = 10
        self.layer.masksToBounds = true

        // your drawing stuff here

        if !self.isInterfaceBuilder {
            // code for runtime
            ...
        }

    }

}

Und so sieht es in InterfaceBuilder aus:

enter image description here

15
zisoft

Sie müssen nicht drawRect verwenden. Stattdessen können Sie Ihre benutzerdefinierte Schnittstelle in einer Xib-Datei erstellen und in initWithCoder und initWithFrame laden. Nach dem Hinzufügen von IBDesignable wird das Live-Rendering in IB ausgeführt. Schauen Sie sich dieses kurze Tutorial an: https://www.youtube.com/watch?v=L97MdpaF3Xg

9
Leszek Szary

Ich denke, dass layoutSubviews der einfachste Mechanismus ist.

Hier ist ein (viel) einfacheres Beispiel in Swift:

@IBDesignable
class LiveLayers : UIView {

    var circle:UIBezierPath {
        return UIBezierPath(ovalInRect: self.bounds)
    }

    var newLayer:CAShapeLayer {
        let shape = CAShapeLayer()
        self.layer.addSublayer(shape)
        return shape
    }
    lazy var myLayer:CAShapeLayer = self.newLayer

    // IBInspectable proeprties here...
    @IBInspectable var pathLength:CGFloat = 0.0 { didSet {
        self.setNeedsLayout()
    }}

    override func layoutSubviews() {
        myLayer.frame = self.bounds // etc
        myLayer.path = self.circle.CGPath
        myLayer.strokeEnd = self.pathLength
    }
}

Ich habe dieses Snippet noch nicht getestet, habe aber zuvor Muster wie diese verwendet. Beachten Sie die Verwendung der verzögerten Eigenschaft, die an eine berechnete Eigenschaft delegiert, um die Erstkonfiguration zu vereinfachen.

7
Chris Conover

In meinem Fall gab es zwei Probleme:

  1. Ich habe initWithFrame nicht in der benutzerdefinierten Ansicht implementiert: (Normalerweise wird initWithCoder: aufgerufen, wenn Sie über IB initialisieren, aber aus irgendeinem Grund wird initWithFrame: nur für IBDesignable benötigt. Wird während der Implementierung nicht über IB aufgerufen.)

  2. Die Spitze meiner benutzerdefinierten Ansicht wurde von mainBundle geladen: [NSBundle bundleForClass:[self class]] wurde benötigt.

4
Jakub Truhlář

Um die Antwort von Hari Karam Singh näher zu erläutern, erklärt diese Slideshow weiter:

http://www.splinter.com.au/presentations/ibdesignable/

Wenn Sie dann nicht sehen, dass Ihre Änderungen im Interface Builder angezeigt werden, probieren Sie diese Menüs aus:

  • Xcode-> Editor-> Ansichten automatisch aktualisieren
  • Xcode-> Editor-> Alle Ansichten aktualisieren
  • Xcode-> Editor-> Ausgewählte Ansichten debuggen

Beim Debuggen meiner Ansicht wurde Xcode leider eingefroren, aber es sollte für kleine Projekte (YMMV) funktionieren.

3
Zack Morris

Ich glaube, Sie können prepareForInterfaceBuilder implementieren und Ihre Kernanimationsarbeit dort erledigen, damit sie in IB .. angezeigt wird .. Ich habe einige ausgefallene Dinge mit Unterklassen von UIButton gemacht, die ihre eigene Kernanimationsebenenarbeit machen, um Grenzen oder Hintergründe zu zeichnen und sie werden live in Interface Builder gerendert, also stelle ich mir vor, wenn Sie UIView direkt subclassieren, dann prepareForInterfaceBuilder ist alles, was Sie anders machen müssen. Beachten Sie jedoch, dass die Methode immer nur von IB ausgeführt wird

Bearbeitet, um Code wie gewünscht einzufügen

Ich habe etwas ähnliches, aber nicht genau so (leider kann ich Ihnen nicht geben, was ich wirklich mache, aber es ist eine Arbeitssache)

class BorderButton: UIButton {
    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    func commonInit(){
        layer.borderWidth = 1
        layer.borderColor = self.tintColor?.CGColor
        layer.cornerRadius = 5    
    }

    override func tintColorDidChange() {
        layer.borderColor = self.tintColor?.CGColor
    }

    override var highlighted: Bool {
        willSet {
            if(newValue){
                layer.backgroundColor = UIColor(white: 100, alpha: 1).CGColor
            } else {
                layer.backgroundColor = UIColor.clearColor().CGColor
            }
        }
    }
}

Ich überschreibe sowohl initWithCoder als auch initWithFrame, da ich die Komponente in Code oder in IB verwenden möchte (und als anderen Antwortstatus müssen Sie initWithFrame implementieren, um IB glücklich zu machen.

Dann habe ich in commonInit das Kernanimationsmaterial eingerichtet, um einen Rand zu zeichnen und ihn hübsch zu gestalten.

Ich implementiere auch eine willSet für die hervorgehobene Variable, um die Hintergrundfarbe zu ändern, weil ich es hasse, wenn Schaltflächen Rahmen zeichnen, aber keine Rückmeldung geben, wenn sie gedrückt wird (ich hasse es, wenn die gedrückte Schaltfläche wie die nicht gedrückte Schaltfläche aussieht).

2
Alpine

Swift 3 Makro

#if TARGET_INTERFACE_BUILDER
#else
#endif

und eine Klasse mit einer Funktion, die aufgerufen wird, wenn IB das Storyboard rendert

@IBDesignable
class CustomView: UIView
{
    @IBInspectable
    public var isCool: Bool = true {
        didSet {
            #if TARGET_INTERFACE_BUILDER

            #else

            #endif
        }
    }

    override func prepareForInterfaceBuilder() {
        // code
    }
}

IBInspectable kann mit den folgenden Typen verwendet werden

Int, CGFloat, Double, String, Bool, CGPoint, CGSize, CGRect, UIColor, UIImage
0
Ako