Есть ли способ для Interface Builder отображать IBDesignable представления, которые не переопределяют drawRect:
Я очень редко переопределить drawRect в мой наследник UIView подклассы, как правило, предпочитая установить layer.contents
с предварительным рендерингом изображений и часто с использованием нескольких подслоев или подвидов и манипулированием ими на основе входных параметров. Есть ли способ для IB отобразить эти более сложные стеки представлений?
8 ответов:
спасибо, @zisoft за то, что втянул меня в
prepareForInterfaceBuilder
. Есть несколько нюансов с циклом рендеринга Interface Builder, которые были источником моих проблем и заслуживают внимания...
- подтверждается: вам не нужно использовать
-drawRect
.установка изображений на UIButton состояния управления работает. Произвольные стеки слоев, похоже, работают, если иметь в виду несколько вещей...
- IB использует
initWithFrame:
..не
initWithCoder
.awakeFromNib
тоже не назовешь.
init...
вызывается только один раз за сеанс!--26-->т. е. один раз за повторную компиляцию всякий раз, когда вы вносите изменения в файл. При изменении свойств IBInspectable init больше не вызывается. Однако...
prepareForInterfaceBuilder
вызывается при каждом изменении свойстваэто как иметь кво на всех ваших IBInspectables, а также другие встроенные свойства. Вы можете проверить это самостоятельно, имея свой
_setup
метод вызывается, сначала только из вашегоinit..
метод. Изменение IBInspectable не будет иметь никакого эффекта. Затем добавьте вызов также вprepareForInterfaceBuilder
. Уахла! Обратите внимание, что ваш код времени выполнения, вероятно, потребуется некоторое дополнительное KVO, так как он не будет вызыватьprepareForIB
метод. Подробнее об этом ниже...
init...
слишком рано рисовать, установить содержание слоя, так далее.по крайней мере с моей
UIButton
подкласс, называя[self setImage:img forState:UIControlStateNormal]
не имеет никакого эффекта В IB. Вам нужно позвонить изprepareForInterfaceBuilder
или через крючок кво.
- когда IB не удается отобразить, он не очищает наш компонент your, а сохраняет последнюю успешную версию.
может быть запутанным время от времени, когда вы делаете изменения, которые не имеют никакого эффекта. Проверьте сборку системный журнал.
совет: держите Монитор активности рядом
я все время зависаю на нескольких разных процессах поддержки, и они берут с собой всю машину. ПрименитьForce Quit
обильно.(обновление: это на самом деле не было правдой, так как XCode6 вышел из бета-версии. Он редко висит больше)
обновление
- 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:
вам не нужно использовать 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).
в моем случае было две проблемы:
Я не реализовал
initWithFrame
в пользовательский вид: (обычноinitWithCoder:
вызывается при инициализации через IB, но по какой-то причинеinitWithFrame:
нуженIBDesignable
только. Не вызывается во время выполнения при реализации через IB)перо моего пользовательского представления загружалось из
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