Je remplace très rarement drawRect dans mes sous-classes UIView, préférant généralement définir layer.contents
avec des images avant le rendu et utilisant souvent plusieurs sous-couches ou sous-vues et les manipulant en fonction de paramètres d'entrée. Existe-t-il un moyen pour IB de restituer ces piles de vues plus complexes?
Merci, @zisoft pour m'avoir informé de prepareForInterfaceBuilder
. Voici quelques nuances avec le cycle de rendu d'Interface Builder qui ont été à l'origine de mes problèmes et qui méritent d'être signalées.
-drawRect
. La définition d'images sur les états de contrôle UIButton fonctionne. Les piles de couches arbitraires semblent fonctionner si quelques éléments sont pris en compte ...
initWithFrame:
..pas initWithCoder
. awakeFromNib
est également appelé PAS.
init...
n'est appelé qu'une fois par session C'est à dire. Une fois par recompilation chaque fois que vous apportez une modification au fichier. Lorsque vous modifiez les propriétés IBInspectable, init n'est PAS appelé à nouveau. Toutefois...
prepareForInterfaceBuilder
est appelé à chaque changement de propriété C'est comme avoir KVO sur tous vos IBInspectables ainsi que sur d'autres propriétés intégrées. Vous pouvez le tester vous-même en appelant votre méthode _setup
, d'abord uniquement à partir de votre méthode init..
. Changer un IBInspectable n'aura aucun effet. Ajoutez ensuite l'appel à prepareForInterfaceBuilder
. Whahla! Notez que votre code d'exécution aura probablement besoin de KVO supplémentaire car il n'appellera pas la méthode prepareForIB
. Plus sur ce ci-dessous ...
init...
est trop tôt pour dessiner, définir le contenu du calque, etc. Au moins avec ma sous-classe UIButton
, appeler [self setImage:img forState:UIControlStateNormal]
n’a aucun effet dans IB. Vous devez l’appeler depuis prepareForInterfaceBuilder
ou via un hook KVO.
Cela peut être déroutant lorsque vous apportez des modifications sans effet. Vérifiez les journaux de construction.
Je suis suspendu tout le temps à un couple de processus de support différents et ils détruisent toute la machine. Appliquez Force Quit
librement.
(UPDATE: Cela n’est plus vraiment le cas depuis la sortie de la version bêta de XCode6. Il ne se bloque plus)
METTRE À JOUR
prepareForInterfaceBuilder
permet effectivement à KVO d'utiliser toutes les propriétés IBInspectable
. Il est regrettable que ce comportement ne soit pas reflété d'une manière ou d'une autre lors de l'exécution, nécessitant ainsi le KVO manuel. Voir l'exemple de code mis à jour ci-dessous.Vous trouverez ci-dessous un exemple de code d’une sous-classe IBDesignable UIButton
en état de fonctionnement. ~~ Remarque, prepareForInterfaceBuilder
n'est pas nécessaire car KVO écoute les modifications apportées à nos propriétés pertinentes et déclenche une mise à jour. ~~ UPDATE: voir le point 8 ci-dessus.
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
Cette réponse est liée à l’attrait de drawRect, mais elle peut peut-être donner quelques idées:
J'ai une classe UIView personnalisée qui comporte des dessins complexes dans drawRect. Vous devez faire attention aux références qui ne sont pas disponibles au moment de la conception, c’est-à-dire UIApplication. Pour cela, je remplace la variable prepareForInterfaceBuilder
par un drapeau booléen que j'utilise dans drawRect pour distinguer le temps d'exécution du temps de conception:
@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
...
}
}
}
Voici à quoi cela ressemble dans InterfaceBuilder:
Vous n'avez pas besoin d'utiliser drawRect, mais vous pouvez créer votre interface personnalisée dans un fichier xib, la charger dans initWithCoder et initWithFrame. Le rendu sera actif dans IB après l'ajout d'IBDesignable. Consultez ce court tutoriel: https://www.youtube.com/watch?v=L97MdpaF3Xg
Je pense que layoutSubviews est le mécanisme le plus simple.
Voici un exemple (beaucoup) plus simple dans 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
}
}
Je n'ai pas testé cet extrait, mais j'ai déjà utilisé des modèles comme celui-ci. Notez l'utilisation de la propriété lazy déléguant à une propriété calculée pour simplifier la configuration initiale.
Dans mon cas, il y avait deux problèmes:
Je n'ai pas implémenté initWithFrame
dans la vue personnalisée: (Habituellement, initWithCoder:
est appelé lors de l'initialisation via IB, mais pour une raison quelconque, initWithFrame:
est nécessaire pour IBDesignable
uniquement. N'est pas appelé lors de l'exécution lorsque vous implémentez via IB)
La nib de ma vue personnalisée se chargeait depuis mainBundle
: [NSBundle bundleForClass:[self class]]
était nécessaire.
Pour élaborer la réponse de Hari Karam Singh , ce diaporama explique plus avant:
http://www.splinter.com.au/presentations/ibdesignable/
Ensuite, si vous ne voyez pas vos modifications apparaître dans Interface Builder, essayez les menus suivants:
Malheureusement, le débogage de ma vue a figé Xcode, mais cela devrait fonctionner pour de petits projets (YMMV).
Je pense que vous pouvez implémenter prepareForInterfaceBuilder
et effectuer votre travail d'animation principal pour l'afficher dans IB . J'ai créé des choses fantaisistes avec les sous-classes d'UIButton qui effectuent leur propre travail de couche d'animation principale pour dessiner des bordures ou des arrière-plans. et ils rendent le rendu dans le constructeur d’interface très bien, alors j’imagine que si vous sous-classez UIView directement, alors prepareForInterfaceBuilder
est tout ce que vous devez faire différemment. Gardez toutefois à l'esprit que cette méthode n'est exécutée que par IB.
Édité pour inclure le code comme demandé
J'ai quelque chose de similaire à, mais pas exactement comme ça (désolé, je ne peux pas vous donner ce que je fais vraiment, mais c'est une affaire de travail)
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
}
}
}
}
Je substitue à la fois initWithCoder
et initWithFrame
parce que je veux pouvoir utiliser le composant dans le code ou dans IB (et comme d'autres réponses l'indiquent, vous devez implémenter initWithFrame
pour que IB soit heureux.
Ensuite, dans commonInit
, j'ai configuré l'animation de base pour dessiner une bordure et la rendre jolie.
J'implémente également une variable willSet
pour la variable en surbrillance afin de changer la couleur d'arrière-plan car je déteste quand les boutons dessinent des bordures, mais je ne donne pas de commentaires lorsque vous appuyez dessus (je déteste quand le bouton enfoncé ressemble au bouton non pressé)
Swift 3 macro
#if TARGET_INTERFACE_BUILDER
#else
#endif
et classe avec fonction qui est appelée quand IB rend le storyboard
@IBDesignable
class CustomView: UIView
{
@IBInspectable
public var isCool: Bool = true {
didSet {
#if TARGET_INTERFACE_BUILDER
#else
#endif
}
}
override func prepareForInterfaceBuilder() {
// code
}
}
IBInspectable peut être utilisé avec les types ci-dessous
Int, CGFloat, Double, String, Bool, CGPoint, CGSize, CGRect, UIColor, UIImage