Program Tip

Interface Builder가 drawRect를 재정의하지 않는 IBDesignable 뷰를 렌더링하는 방법이 있습니까?

programtip 2020. 11. 29. 12:08
반응형

Interface Builder가 drawRect를 재정의하지 않는 IBDesignable 뷰를 렌더링하는 방법이 있습니까?


저는 UIView 하위 클래스에서 drawRect를 거의 재정의하지 않습니다. 일반적 layer.contents으로 미리 렌더링 된 이미지 로 설정 하고 종종 여러 하위 계층 또는 하위보기를 사용하고 입력 매개 변수를 기반으로이를 조작 하는 것을 선호합니다 . IB가 더 복잡한 뷰 스택을 렌더링 할 수있는 방법이 있습니까?


에 대한 단서에 대해 @zisoft에게 감사드립니다 prepareForInterfaceBuilder. 내 문제의 원인이었고 주목할 가치가있는 Interface Builder의 렌더링주기에는 몇 가지 뉘앙스가 있습니다.

  1. 확인 됨 :을 사용할 필요가 없습니다 -drawRect.

UIButton 컨트롤 상태에 대한 이미지 설정이 작동합니다. 몇 가지 사항을 염두에두면 임의의 레이어 스택이 작동하는 것 같습니다.

  1. IB 사용 initWithFrame:

.. 아님 initWithCoder. awakeFromNib또한 호출되지 않습니다.

  1. init... 세션 당 한 번만 호출됩니다.

즉, 파일을 변경할 때마다 다시 컴파일 할 때마다 한 번씩. IBInspectable 속성을 변경하면 init가 다시 호출되지 않습니다. 하나...

  1. prepareForInterfaceBuilder 속성이 변경 될 때마다 호출됩니다.

모든 IBInspectable 및 기타 내장 속성에 KVO를 사용하는 것과 같습니다. _setup먼저 init..메서드 에서만 메서드를 호출 하여 직접 테스트 할 수 있습니다 . IBInspectable을 변경해도 효과가 없습니다. 그런 다음에 통화를 추가합니다 prepareForInterfaceBuilder. 헐! 런타임 코드는 prepareForIB메서드를 호출하지 않으므로 추가 KVO가 필요할 수 있습니다 . 아래에 더 자세히 ...

  1. init... 그리기, 레이어 내용 설정 등을하기에는 너무 이르다.

적어도 내 UIButton하위 클래스에서는 호출 [self setImage:img forState:UIControlStateNormal]이 IB에 영향을 미치지 않습니다. prepareForInterfaceBuilderKVO 후크 에서 또는 KVO 후크를 통해 호출해야합니다 .

  1. IB가 렌더링에 실패하면 구성 요소를 비우지 않고 마지막으로 성공한 버전을 유지합니다.

효과가없는 변경을 할 때 혼란 스러울 수 있습니다. 빌드 로그를 확인하십시오.

  1. 팁 : Activity Monitor를 근처에 두십시오.

나는 몇 가지 다른 지원 프로세스에 항상 매달리고 그들은 전체 시스템을 함께 떨어 뜨립니다. Force Quit자유롭게 적용하십시오 .

(업데이트 : 이것은 XCode6가 베타에서 나온 이후로 사실이 아닙니다. 더 이상 멈추지 않습니다.)

최신 정보

  1. 6.3.1은 IB 버전에서 KVO를 좋아하지 않는 것 같습니다. 이제 KVO를 설정하지 않고 Interface Builder를 잡기 위해 플래그가 필요한 것 같습니다. 방법이 prepareForInterfaceBuilder모든 IBInspectable속성을 효과적으로 KVO하므로 괜찮습니다 . 이 동작은 런타임에 어떻게 든 미러링되지 않으므로 수동 KVO가 필요합니다. 아래 업데이트 된 샘플 코드를 참조하십시오.

UIButton 하위 클래스 예제

다음은 작동하는 IBDesignable UIButton하위 클래스 의 몇 가지 예제 코드입니다 . ~~ 참고, prepareForInterfaceBuilderKVO는 관련 속성의 변경 사항을 수신하고 다시 그리기를 트리거하므로 실제로 필요하지 않습니다. ~~ 업데이트 : 위의 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 재정의와 관련이 있지만 몇 가지 아이디어를 줄 수 있습니다.

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에서 어떻게 보이는지입니다.

여기에 이미지 설명 입력


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

I haven't tested this snippet, but have used patterns like this before. Note the use of the lazy property delegating to a computed property to simplify initial configuration.


In my case, there were two problems:

  1. I did not implement initWithFrame in custom view: (Usually initWithCoder: is called when you initialize via IB, but for some reason initWithFrame: is needed for IBDesignable only. Is not called during runtime when you implement via IB)

  2. My custom view's nib was loading from mainBundle: [NSBundle bundleForClass:[self class]] was needed.


To elaborate upon Hari Karam Singh's answer, this slideshow explains further:

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

Then if you aren't seeing your changes show up in Interface Builder, try these menus:

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

Unfortunately, debugging my view froze Xcode, but it should work for small projects (YMMV).


I believe you can implement prepareForInterfaceBuilder and do your core animation work in there to get it to show up in IB. I've done some fancy things with subclasses of UIButton that do their own core animation layer work to draw borders or backgrounds, and they live render in interface builder just fine, so i imagine if you're subclassing UIView directly, then prepareForInterfaceBuilder is all you'll need to do differently. Keep in mind though that the method is only ever executed by IB

Edited to include code as requested

I have something similar to, but not exactly like this (sorry I can't give you what I really do, but it's a work thing)

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

I override both initWithCoder and initWithFrame because I want to be able to use the component in code or in IB (and as other answers state, you have to implement initWithFrame to make IB happy.

Then in commonInit I set up the core animation stuff to draw a border and make it pretty.

I also implement a willSet for the highlighted variable to change the background color because I hate when buttons draw borders, but don't provide feedback when pressed (i hate it when the pressed button looks like the unpressed button)


Swift 3 macro

#if TARGET_INTERFACE_BUILDER
#else
#endif

and class with function which is called when IB renders storyboard

@IBDesignable
class CustomView: UIView
{
    @IBInspectable
    public var isCool: Bool = true {
        didSet {
            #if TARGET_INTERFACE_BUILDER

            #else

            #endif
        }
    }

    override func prepareForInterfaceBuilder() {
        // code
    }
}

IBInspectable can be used with types below

Int, CGFloat, Double, String, Bool, CGPoint, CGSize, CGRect, UIColor, UIImage

참고URL : https://stackoverflow.com/questions/26197582/is-there-a-way-for-interface-builder-to-render-ibdesignable-views-which-dont-ov

반응형