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

Ответ 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()
}
}
}
- Создайте файл BoxView.swift выше.
- Выберите UIView в InterfaceBuilder.
- В
Identity InspectorвыберитеCustom Class → Class to "BoxView". - В
Attributes InspectorустановитеborderColor,borderWidthиborderRadius.