iOS : UIView의 'drawRect :'대 레이어의 대리자 'drawLayer : inContext :'사용
의 하위 클래스 인 클래스가 UIView
있습니다. drawRect
메서드를 구현하거나 drawLayer:inContext:
.NET Core의 대리자 메서드를 구현하여 뷰 내부에 항목을 그릴 수 CALayer
있습니다.
두 가지 질문이 있습니다.
- 사용할 접근 방식을 결정하는 방법은 무엇입니까? 각각에 대한 사용 사례가 있습니까?
을 구현
drawLayer:inContext:
하면 다음 을 사용하여drawRect
내 뷰를CALayer
대리자 로 할당하지 않아도 호출됩니다 ( 적어도 중단 점을 두는 한 알 수없는 한).[[self layer] setDelegate:self];
내 인스턴스가 레이어의 델리게이트로 정의되지 않은 경우 어떻게 델리게이트 메서드가 호출됩니까?
drawRect
호출되는 경우drawLayer:inContext:
호출 되는 것을 방지하는 메커니즘 은 무엇입니까?
사용할 접근 방식을 결정하는 방법은 무엇입니까? 각각에 대한 사용 사례가 있습니까?
항상 사용 drawRect:
하고, 사용하지 않습니다 UIView
어떤을위한 그리기 대리인으로 CALayer
.
내 인스턴스가 레이어의 델리게이트로 정의되지 않은 경우 어떻게 델리게이트 메서드가 호출됩니까? 그리고 호출되는 경우 drawRect가 호출되는 것을 방지하는 메커니즘
drawLayer:inContext:
은 무엇입니까?
모든 UIView
인스턴스는 백업을위한 드로잉 델리게이트입니다 CALayer
. 그래서 [[self layer] setDelegate:self];
아무것도하지 않는 것 같았습니다. 중복됩니다. 이 drawRect:
방법은 사실상 뷰의 레이어에 대한 그리기 위임 방법입니다. 내부적으로 UIView
구현 drawLayer:inContext:
그 다음 자신의 물건의 일부를 수행하고 호출합니다 drawRect:
. 디버거에서 볼 수 있습니다.
이것이 drawRect:
구현할 때 호출되지 않은 이유 drawLayer:inContext:
입니다. 또한 CALayer
사용자 지정 UIView
하위 클래스 에서 그리기 대리자 메서드를 구현해서는 안됩니다 . 또한 어떤 뷰도 다른 레이어의 드로잉 대리자로 만들면 안됩니다. 그것은 모든 종류의 괴팍함을 유발할 것입니다.
drawLayer:inContext:
에 액세스해야하기 때문에 구현 하는 경우을 호출 CGContextRef
하여 내부에서 가져올 수 있습니다 .drawRect:
UIGraphicsGetCurrentContext()
drawRect
절대적으로 필요한 경우에만 구현해야합니다. 의 기본 구현 drawRect
에는 뷰의 렌더링을 지능적으로 캐싱하는 것과 같은 여러 스마트 최적화 가 포함됩니다. 재정의하면 이러한 모든 최적화가 우회됩니다. 그 나쁜. 레이어 그리기 방법을 효과적으로 사용하면 거의 항상 사용자 지정 drawRect
. Apple UIView
은를 대리자로 사용합니다 CALayer
. 사실 모든 UIView는 해당 레이어의 대리자입니다 . ZoomingPDFViewer를 포함한 여러 Apple 샘플에서 UIView 내부의 레이어 그리기를 사용자 정의하는 방법을 볼 수 있습니다.
사용 drawRect
이 일반적이지만, 적어도 2002/2003, IIRC 이후 권장되지 않는 관행입니다. 그 길로 가야 할 좋은 이유가 많지 않습니다.
iPhone OS의 고급 성능 최적화 (슬라이드 15)
기술 Q & A QA1708 : iOS에서 이미지 그리기 성능 향상
다음은 Apple의 샘플 ZoomingPDFViewer 코드입니다.
-(void)drawRect:(CGRect)r
{
// UIView uses the existence of -drawRect: to determine if it should allow its CALayer
// to be invalidated, which would then lead to the layer creating a backing store and
// -drawLayer:inContext: being called.
// By implementing an empty -drawRect: method, we allow UIKit to continue to implement
// this logic, while doing our real drawing work inside of -drawLayer:inContext:
}
-(void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context
{
...
}
사용 여부 drawLayer(_:inContext:)
또는 drawRect(_:)
코드를 그리기 사용자 정의를 위해 (또는 둘 것은)가 애니메이션되는 동안은 레이어 속성의 현재 값에 액세스 할 필요가 있는지 여부에 따라 달라집니다.
나는 오늘 내 자신의 Label 클래스를 구현할 때이 두 함수와 관련된 다양한 렌더링 문제로 어려움을 겪고있었습니다 . 문서를 확인하고 시행 착오를 거쳐 UIKit을 디 컴파일하고 Apple의 Custom Animatable Properties 예제를 조사한 후 작동 방식에 대해 좋은 감각을 얻었습니다.
drawRect(_:)
애니메이션 중에 레이어 / 뷰 속성의 현재 값에 액세스 할 필요가없는 경우 간단히을 사용 drawRect(_:)
하여 사용자 지정 드로잉을 수행 할 수 있습니다 . 모든 것이 잘 작동합니다.
override func drawRect(rect: CGRect) {
// your custom drawing code
}
drawLayer(_:inContext:)
예를 들어 backgroundColor
사용자 정의 그리기 코드에서 사용하려는 경우 :
override func drawRect(rect: CGRect) {
let colorForCustomDrawing = self.layer.backgroundColor
// your custom drawing code
}
When you test your code you'll notice that backgroundColor
does not return the correct (i.e. the current) value while an animation is in-flight. Instead it returns the final value (i.e. the value for when the animation is completed).
In order to get the current value during the animation, you must access the backgroundColor
of the layer
parameter passed to drawLayer(_:inContext:)
. And you must also draw to the context
parameter.
It is very important to know that a view's self.layer
and the layer
parameter passed to drawLayer(_:inContext:)
are not always the same layer! The latter might be a copy of the former with partial animations already applied to its properties. That way you can access correct property values of in-flight animations.
Now the drawing works as expected:
override func drawLayer(layer: CALayer, inContext context: CGContext) {
let colorForCustomDrawing = layer.backgroundColor
// your custom drawing code
}
But there are two new issue: setNeedsDisplay()
and several properties like backgroundColor
and opaque
do no longer work for your view. UIView
does no longer forward calls and changes to its own layer.
setNeedsDisplay()
does only do something if your view implements drawRect(_:)
. It doesn't matter if the function actually does something but UIKit uses it to determine whether you do custom drawing or not.
The properties likely don't work anymore because UIView
's own implementation of drawLayer(_:inContext:)
is no longer called.
So the solution is quite simple. Just call the superclass' implementation of drawLayer(_:inContext:)
and implement an empty drawRect(_:)
:
override func drawLayer(layer: CALayer, inContext context: CGContext) {
super.drawLayer(layer, inContext: context)
let colorForCustomDrawing = layer.backgroundColor
// your custom drawing code
}
override func drawRect(rect: CGRect) {
// Although we use drawLayer(_:inContext:) we still need to implement this method.
// UIKit checks for its presence when it decides whether a call to setNeedsDisplay() is forwarded to its layer.
}
Summary
Use drawRect(_:)
as long as you don't have the problem that properties return wrong values during an animation:
override func drawRect(rect: CGRect) {
// your custom drawing code
}
Use drawLayer(_:inContext:)
and drawRect(_:)
if you need to access the current value of view/layer properties while they are being animated:
override func drawLayer(layer: CALayer, inContext context: CGContext) {
super.drawLayer(layer, inContext: context)
let colorForCustomDrawing = layer.backgroundColor
// your custom drawing code
}
override func drawRect(rect: CGRect) {
// Although we use drawLayer(_:inContext:) we still need to implement this method.
// UIKit checks for its presence when it decides whether a call to setNeedsDisplay() is forwarded to its layer.
}
On iOS, the overlap between a view and its layer is very large. By default, the view is the delegate of its layer and implements the layer's drawLayer:inContext:
method. As I understand it, drawRect:
and drawLayer:inContext:
are more or less equivalent in this case. Possibly, the default implementation of drawLayer:inContext:
calls drawRect:
, or drawRect:
is only called if drawLayer:inContext:
is not implemented by your subclass.
How to decide which approach to use? Is there a use case for each one?
It doesn't really matter. To follow the convention, I would normally use drawRect:
and reserve the use of drawLayer:inContext:
when I actually have to draw custom sublayers that are not part of a view.
The Apple Documentation has this to say: "There are also other ways to provide a view’s content, such as setting the contents of the underlying layer directly, but overriding the drawRect: method is the most common technique."
But it doesn't go into any details, so that should be a clue: don't do it unless you really want to get your hands dirty.
The UIView's layer's delegate is pointed at the UIView. However, the UIView does behave differently depending on whether or not drawRect: is implemented. For example, if you set the properties on the layer directly (such as its background color or its corner radius), these values are overwritten if you have a drawRect: method - even if its completely empty (i.e. doesnt even call super).
사용자 정의 컨텐츠가있는 레이어 기반 뷰의 경우 도면을 수행하기 위해 뷰의 메서드를 계속 재정의해야합니다. 레이어 지원 뷰는 자동으로 자신을 해당 레이어의 대리자로 만들고 필요한 대리자 메서드를 구현하므로 해당 구성을 변경해서는 안됩니다. 대신 콘텐츠를 그리기 위해 뷰의 drawRect : 메서드를 구현해야합니다. 핵심 애니메이션 프로그래밍 가이드
'Program Tip' 카테고리의 다른 글
RedirectToAction 메서드로 쿼리 문자열 값을 추가하는 방법은 무엇입니까? (0) | 2020.10.26 |
---|---|
엔터티 키를 사용하여 GQL에서 쿼리하는 방법 (0) | 2020.10.26 |
행렬 곱셈 : 행렬 크기의 작은 차이, 타이밍의 큰 차이 (0) | 2020.10.26 |
비평 탄 인덱스를 반환하는 numpy 배열의 Argmax (0) | 2020.10.26 |
CSS 애니메이션 속성이 애니메이션 후 유지됨 (0) | 2020.10.26 |