prepareForInterfaceBuilder
线索。在Interface Builder的渲染周期中有一些细微差别,这是我的问题来源,并值得注意...
-drawRect
。设置UIButton控件状态中的图像有效。如果记住了一些事情,任意层堆栈似乎也有效...
initWithFrame:
..而不是initWithCoder
。 awakeFromNib
也没有被调用。
init...
每个会话只调用一次即每次在文件中进行更改时重新编译时调用一次。当更改IBInspectable属性时,不会再次调用init。但是...
prepareForInterfaceBuilder
在每次属性更改时都会被调用就像在所有IBInspectables以及其他内置属性上都有KVO一样。您可以通过首先仅从init..
方法调用您的_setup
方法来测试此功能。更改IBInspectable将不起作用。然后还要添加调用到prepareForInterfaceBuilder
。Whahla!请注意,您的运行时代码可能需要一些额外的KVO,因为它不会调用prepareForIB
方法。有关此问题的更多信息,请参见下文...
init...
太早绘制、设置层内容等。至少对于我的UIButton
子类,在IB中调用[self setImage:img forState:UIControlStateNormal]
没有效果。您需要从prepareForInterfaceBuilder
或通过KVO挂钩调用它。
有时在进行没有效果的更改时可能会令人困惑。检查构建日志。
我经常在几个不同的支持进程上遇到停顿,并且它们会将整个机器拖垮。自由使用Force Quit
。
(更新:自XCode6退出beta以来,这并不真实。它很少再挂起了)
更新
prepareForInterfaceBuilder
方法有效地KVO了所有的IBInspectable
属性。遗憾的是,这种行为在运行时没有以某种方式反映出来,因此需要手动进行KVO。请参见下面更新后的示例代码。下面是一个工作中的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
setAttributedTitle
应该可以解决,但它有点复杂。在设置新字体后,也尝试重置标题。 - Hari HonorUIButton
的子类绘制背景图像时遇到“超时”错误。 - skywinderinitWithFrame:
,而在OS X上使用initWithCoder:
。是的,这是令人困惑的,并且在Radar中有记录(请参见http://www.openradar.me/19901337)。在Cocoa文档中,您可以在标识检查器中选择“Prefer coder”来使运行时(以前默认为initWithFrame:
)与设计时匹配。对于iOS文档,这个不匹配仍然存在。 - Quinn Taylor这个回答与重写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 中的样子:
#if !TARGET_INTERFACE_BUILDER
。 - Hari Honor您不必使用drawRect,而是可以在xib文件中创建自定义接口,在initWithCoder和initWithFrame中加载它,添加IBDesignable后将在IB中进行实时渲染。查看这个简短的教程: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属性委托到计算属性来简化初始配置。
为了阐述Hari Karam Singh的答案,这个幻灯片向您进一步解释:
http://www.splinter.com.au/presentations/ibdesignable/
如果您在Interface Builder中没有看到您的更改,请尝试以下菜单:
不幸的是,调试我的视图会使Xcode冻结,但对于小型项目应该可以正常工作(YMMV)。
我没有在自定义视图中实现 initWithFrame
:(通常情况下,通过IB初始化时会调用initWithCoder:
,但由于某些原因,只有在IBDesignable
中才需要调用initWithFrame:
。在通过IB实现时不会在运行时调用)
我的自定义视图的nib文件是从mainBundle
加载的:需要使用[NSBundle bundleForClass:[self class]]
。
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 happy)。接着,在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