Я очень редко переопределяю 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
.