web-dev-qa-db-ja.com

Interface Builderが、drawRectをオーバーライドしないIBDesignableビューをレンダリングする方法はありますか?

UIViewサブクラスでdrawRectをオーバーライドすることはほとんどありません。通常はlayer.contents事前レンダリング画像を使用し、多くの場合、複数のサブレイヤーまたはサブビューを使用し、入力パラメーターに基づいてこれらを操作します。 IBがこれらのより複雑なビュースタックをレンダリングする方法はありますか?

60

@zisoftがprepareForInterfaceBuilderを教えてくれてありがとう。 Interface Builderのレンダリングサイクルにはいくつかのニュアンスがあり、それが私の問題の原因であり、注目に値します...

  1. 確認済み:-drawRectを使用する必要はありません

UIButtonコントロールの状態に画像を設定すると機能します。いくつかのことを念頭に置くと、任意のレイヤースタックが機能するように見えます...

  1. IBはinitWithFrame:を使用

..not initWithCoderawakeFromNibも呼び出されません。

  1. init...はセッションごとに1回だけ呼び出されます

つまりファイルに変更を加えるたびに、再コンパイルごとに1回。 IBInspectableプロパティを変更すると、initは再度呼び出されません。しかしながら...

  1. prepareForInterfaceBuilderはプロパティが変更されるたびに呼び出されます

これは、すべてのIBInspectablesおよびその他の組み込みプロパティにKVOを設定するようなものです。最初に_setupメソッドからのみinit..メソッドを呼び出すことで、これを自分でテストできます。 IBInspectableを変更しても効果はありません。次に、prepareForInterfaceBuilderにも呼び出しを追加します。ファラ!ランタイムコードはprepareForIBメソッドを呼び出さないため、おそらく追加のKVOが必要になることに注意してください。これについての詳細は...

  1. init...は描画、レイヤーコンテンツの設定などが早すぎます

少なくとも私のUIButtonサブクラスでは、[self setImage:img forState:UIControlStateNormal]を呼び出してもIBでは効果がありません。 prepareForInterfaceBuilderから、またはKVOフック経由で呼び出す必要があります。

  1. IBがレンダリングに失敗した場合、コンポーネントをブランクにするのではなく、最後に成功したバージョンを保持します。

効果のない変更を行っているときに混乱する可能性があります。ビルドログを確認します。

  1. ヒント:アクティビティモニターを近くに置く

私は常にいくつかの異なるサポートプロセスでハングアップし、それらはマシン全体をダウンさせます。 Force Quitを自由に適用します。

(更新:XCode6がベータ版からリリースされて以来、これは本当ではありませんでした。もうハングすることはほとんどありません)

[〜#〜] update [〜#〜]

  1. 6.3.1はIBバージョンのKVOを好まないようです。 Interface Builderをキャッチし、KVOをセットアップしないためには、フラグが必要なようです。 prepareForInterfaceBuilderメソッドはすべてのIBInspectableプロパティを効果的にKVOするため、これは問題ありません。残念ながら、この動作が実行時に何らかの形でミラーリングされないため、手動のKVOが必要になります。以下の更新されたサンプルコードを参照してください。

UIButtonサブクラスの例

以下は、動作中のIBDesignable UIButtonサブクラスのサンプルコードです。 ~~KVOは関連するプロパティの変更をリッスンし、再描画をトリガーするため、prepareForInterfaceBuilderは実際には必要ありません。~~ 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
132

この答えはdrawRectのオーバーライドに関連していますが、おそらくいくつかのアイデアを与えることができます。

DrawRectに複雑な描画を持つカスタムUIViewクラスがあります。設計時に使用できない参照、つまり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

15
zisoft

DrawRectを使用する必要はありません。代わりに、カスタムインターフェイスをxibファイルに作成し、initWithCoderおよびinitWithFrameにロードすると、IBDesignableを追加した後、IBでライブレンダリングが可能になります。この短いチュートリアルを確認してください: https://www.youtube.com/watch?v=L97MdpaF3Xg

9
Leszek Szary

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
    }
}

このスニペットをテストしたことはありませんが、以前にこのようなパターンを使用しました。初期構成を簡素化するために、計算プロパティに委任する遅延プロパティの使用に注意してください。

7
Chris Conover

私の場合、2つの問題がありました。

  1. カスタムビューでinitWithFrameを実装しませんでした:(通常initWithCoder:は、IB経由で初期化するときに呼び出されますが、何らかの理由でinitWithFrame:IBDesignableにのみ必要です。 IB経由で実装する場合、実行時に呼び出されません)

  2. カスタムビューのペン先はmainBundle:からロードしていました[NSBundle bundleForClass:[self class]]が必要でした。

4
Jakub Truhlář

Hari Karam Singhの答え について詳しく説明するために、このスライドショーではさらに説明します。

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

Interface Builderに変更が表示されない場合は、次のメニューを試してください。

  • Xcode-> Editor-> Automatically Refresh Views
  • Xcode-> Editor-> Refresh All Views
  • Xcode-> Editor-> Debug Selected Views

残念ながら、私のビューをデバッグするとXcodeがフリーズしましたが、小さなプロジェクト(YMMV)で機能するはずです。

3
Zack Morris

prepareForInterfaceBuilderを実装し、そこでコアアニメーションを実行して、IBに表示できると思います。私はUIButtonのサブクラスでいくつかの派手なことをしました.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
            }
        }
    }
}

initWithCoderinitWithFrameの両方をオーバーライドします。これは、コードまたはIBでコンポーネントを使用できるようにしたいためです(他の回答状態として、initWithFrameを実装して、 IBハッピー。

次に、commonInitで、境界線を描画してきれいにするために、コアアニメーションをセットアップしました。

また、強調表示された変数にwillSetを実装して背景色を変更します。これは、ボタンが境界線を描くときに嫌いですが、押されたときにフィードバックを提供しないためです(押されたボタンが押されていないボタンのように見える場合は嫌いです)

2
Alpine

Swiftマクロ

#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
1
Ako