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?
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 ...
-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 ...
initWithFrame:
..nicht initWithCoder
. awakeFromNib
wird auch NICHT aufgerufen.
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...
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 ...
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.
Kann verwirrend sein, wenn Sie Änderungen vornehmen, die keine Auswirkungen haben. Überprüfen Sie die Build-Protokolle.
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
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.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
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:
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
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.
In meinem Fall gab es zwei Probleme:
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.)
Die Spitze meiner benutzerdefinierten Ansicht wurde von mainBundle
geladen: [NSBundle bundleForClass:[self class]]
wurde benötigt.
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:
Beim Debuggen meiner Ansicht wurde Xcode leider eingefroren, aber es sollte für kleine Projekte (YMMV) funktionieren.
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).
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