Подтвердить что ты не робот

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

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

4b9b3361

Ответ 1

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

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

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

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

.. не initWithCoder. awakeFromNib также НЕ вызывается.

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

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

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

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

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

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

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

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

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

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

(ОБНОВЛЕНИЕ: Это действительно не так, поскольку XCode6 вышел из бета-версии. Он редко висит)

UPDATE

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

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

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

Ответ 2

Этот ответ связан с переопределением 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

Ответ 3

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

Ответ 4

Я думаю, что 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-свойства делегирует вычисляемое свойство для упрощения начальной конфигурации.

Ответ 5

В моем случае возникли две проблемы:

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

  • Мой пользовательский вид nib загружался с mainBundle: [NSBundle bundleForClass:[self class]].

Ответ 6

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

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

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

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

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

Ответ 7

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

Отредактировано для включения кода по запросу

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

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, чтобы сделать IB счастливым.

Затем в commonInit я настроил основной материал анимации, чтобы нарисовать границу и сделать ее красивой.

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

Ответ 8

Макрос 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

Ответ 9

Swift 4.2 проверен и работает. Это самое чистое решение.

В XCode удостоверьтесь, что Editor → Automatically refresh views проверен.

import UIKit

@IBDesignable
class BoxView: UIView {

    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        layer.borderColor = borderColor?.cgColor
        layer.borderWidth = borderWidth
        layer.cornerRadius = cornerRadius
    }

    @IBInspectable var borderColor: UIColor? {
        didSet {
            layer.borderColor = borderColor?.cgColor
            setNeedsLayout()
        }
    }

    @IBInspectable var borderWidth: CGFloat = 0.0 {
        didSet {
            layer.borderWidth = borderWidth
            setNeedsLayout()
        }
    }

    @IBInspectable var cornerRadius: CGFloat = 0.0 {
        didSet {
            layer.cornerRadius = cornerRadius
            setNeedsLayout()
        }
    }
}
  1. Создайте файл BoxView.swift выше.
  2. Выберите UIView в InterfaceBuilder.
  3. В Identity Inspector выберите Custom Class → Class to "BoxView".
  4. В Attributes Inspector установите borderColor, borderWidth и borderRadius.