Есть ли способ для Interface Builder отображать IBDesignable представления, которые не переопределяют drawRect:


Я очень редко переопределить drawRect в мой наследник UIView подклассы, как правило, предпочитая установить layer.contents с предварительным рендерингом изображений и часто с использованием нескольких подслоев или подвидов и манипулированием ими на основе входных параметров. Есть ли способ для IB отобразить эти более сложные стеки представлений?

8 60

8 ответов:

спасибо, @zisoft за то, что втянул меня в prepareForInterfaceBuilder. Есть несколько нюансов с циклом рендеринга Interface Builder, которые были источником моих проблем и заслуживают внимания...

  1. подтверждается: вам не нужно использовать -drawRect.

установка изображений на UIButton состояния управления работает. Произвольные стеки слоев, похоже, работают, если иметь в виду несколько вещей...

  1. IB использует initWithFrame:

..не initWithCoder. awakeFromNib тоже не назовешь.

  1. init... вызывается только один раз за сеанс!--26-->

т. е. один раз за повторную компиляцию всякий раз, когда вы вносите изменения в файл. При изменении свойств IBInspectable init больше не вызывается. Однако...

  1. prepareForInterfaceBuilder вызывается при каждом изменении свойства

это как иметь кво на всех ваших IBInspectables, а также другие встроенные свойства. Вы можете проверить это самостоятельно, имея свой _setup метод вызывается, сначала только из вашего init.. метод. Изменение IBInspectable не будет иметь никакого эффекта. Затем добавьте вызов также в prepareForInterfaceBuilder. Уахла! Обратите внимание, что ваш код времени выполнения, вероятно, потребуется некоторое дополнительное KVO, так как он не будет вызывать prepareForIB метод. Подробнее об этом ниже...

  1. init... слишком рано рисовать, установить содержание слоя, так далее.

по крайней мере с моей UIButton подкласс, называя [self setImage:img forState:UIControlStateNormal] не имеет никакого эффекта В IB. Вам нужно позвонить из prepareForInterfaceBuilder или через крючок кво.

  1. когда IB не удается отобразить, он не очищает наш компонент your, а сохраняет последнюю успешную версию.

может быть запутанным время от времени, когда вы делаете изменения, которые не имеют никакого эффекта. Проверьте сборку системный журнал.

  1. совет: держите Монитор активности рядом

я все время зависаю на нескольких разных процессах поддержки, и они берут с собой всю машину. Применить Force Quit обильно.

(обновление: это на самом деле не было правдой, так как XCode6 вышел из бета-версии. Он редко висит больше)

обновление

  1. 6.3.1 кажется, не нравится KVO в версии IB. Теперь вам, похоже, нужен флаг, чтобы поймать Interface Builder и не настраивать KVOs. Это нормально, как prepareForInterfaceBuilder метод эффективно KVOs все IBInspectable свойства. К сожалению, это поведение не отражается как-то во время выполнения, что требует ручного KVO. См. обновленный пример кода ниже.

подкласс UIButton пример

Ниже приведен пример кода рабочего IBDesignable UIButton подкласс. ~~Примечание prepareForInterfaceBuilder на самом деле не требуется, поскольку KVO прослушивает изменения наших соответствующих свойств и запускает перерисовку.~~ обновление: см. пункт 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

этот ответ связан с переопределением drawRect, но, возможно, он может дать некоторые идеи:

у меня есть пользовательский класс UIView, который имеет сложные чертежи в drawRect. Вы должны позаботиться о ссылках, которые не доступны во время разработки, т. е. UIApplication. Для этого я переопределяю prepareForInterfaceBuilder где я устанавливаю логический флаг, который я использую в drawRect, чтобы различать время выполнения и время разработки:

@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
            ...
        }

    }

}

вот как это выглядит InterfaceBuilder:

enter image description here

вам не нужно использовать drawRect, вместо этого вы можете создать свой пользовательский интерфейс в файле xib, загрузить его в initWithCoder и initWithFrame, и это будет живой рендеринг в IB после добавления IBDesignable. Проверьте этот краткий учебник:https://www.youtube.com/watch?v=L97MdpaF3Xg

Я думаю, layoutSubviews-это самый простой механизм.

вот (гораздо) более простой пример в 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
    }
}

Я не тестировал этот фрагмент, но раньше использовал такие шаблоны. Обратите внимание на использование свойства lazy, делегирующего вычисляемому свойству, для упрощения начальной конфигурации.

уточнение ответ Хари Карам Сингха, это слайд-шоу объясняет далее:

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

затем, если вы не видите, что ваши изменения отображаются в Interface Builder, попробуйте следующие меню:

  • Xcode - > Редактор - > Автоматическое Обновление Представлений
  • Xcode - > Редактор - > Обновить Все Представления
  • Xcode - > Редактор - > Выбрана Отладка Просмотров

к сожалению, отладка моего представления заморозила Xcode, но она должна работать для небольших проектов (YMMV).

в моем случае было две проблемы:

  1. Я не реализовал initWithFrame в пользовательский вид: (обычно initWithCoder: вызывается при инициализации через IB, но по какой-то причине initWithFrame: нужен IBDesignable только. Не вызывается во время выполнения при реализации через IB)

  2. перо моего пользовательского представления загружалось из mainBundle:.

Я считаю, что вы можете реализовать prepareForInterfaceBuilder и сделайте свою основную анимацию там, чтобы она появилась в IB. Я сделал некоторые причудливые вещи с подклассами UIButton, которые выполняют свою собственную работу с основным анимационным слоем для рисования границ или фонов, и они живут в interface builder просто отлично, поэтому я думаю, что если вы напрямую подклассируете UIView, то prepareForInterfaceBuilder все, что вам нужно сделать по-другому. Имейте в виду, однако, что метод выполняется только когда-либо ИБ

отредактировано, чтобы включить код по запросу

у меня есть что-то похожее, но не совсем так (извините, я не могу дать вам то, что я действительно делаю, но это работа)

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
            }
        }
    }
}

Я переопределить как initWithCoder и initWithFrame потому что я хочу иметь возможность использовать компонент в коде или в IB (и как другие ответы состояния, вы должны реализовать initWithFrame чтобы сделать ИБ счастливым.

затем в commonInit Я настроил основную анимацию, чтобы нарисовать граница и сделать его красивым.

Я также реализую willSet для выделенной переменной, чтобы изменить цвет фона, потому что я ненавижу, когда кнопки рисуют границы, но не обеспечивают обратную связь при нажатии (я ненавижу, когда нажатая кнопка выглядит как несжатая кнопка)

Swift 3 макрос

#if TARGET_INTERFACE_BUILDER
#else
#endif

и класс с функцией, которая вызывается, когда IB отображает раскадровку

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

            #else

            #endif
        }
    }

    override func prepareForInterfaceBuilder() {
        // code
    }
}

IBInspectable может использоваться с типами ниже

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