UIView를 강제로 다시 그리는 가장 강력한 방법은 무엇입니까?


117

항목 목록이있는 UITableView가 있습니다. 항목을 선택하면 viewController가 푸시되고 다음을 수행합니다. viewDidLoad 메서드에서 내 하위 뷰 (drawRect가 재정의 된 UIView 하위 클래스)에 필요한 데이터에 대한 URLRequest를 실행합니다. 클라우드에서 데이터가 도착하면 뷰 계층 구조를 구축하기 시작합니다. 문제의 하위 클래스가 데이터를 전달 받고 이제 drawRect 메서드에 렌더링에 필요한 모든 것이 있습니다.

그러나.

나는 drawRect를 명시 적으로 호출하지 않기 때문에-Cocoa-Touch가 그것을 처리합니다.-저는 Cocoa-Touch에게 정말로이 UIView 서브 클래스가 렌더링하기를 원한다는 것을 알릴 방법이 없습니다. 언제? 이제 좋을 것입니다!

[myView setNeedsDisplay]를 시도했습니다. 이것은 가끔 작동합니다. 매우 반점이 있습니다.

나는 몇 시간 동안 이것과 씨름하고 있습니다. UIView를 다시 렌더링하도록 강요하는 견고하고 보장 된 접근 방식을 제발 제발 제발 제공 할 수 있습니까?

다음은 뷰에 데이터를 제공하는 코드 스 니펫입니다.

// Create the subview
self.chromosomeBlockView = [[[ChromosomeBlockView alloc] initWithFrame:frame] autorelease];

// Set some properties
self.chromosomeBlockView.sequenceString     = self.sequenceString;
self.chromosomeBlockView.nucleotideBases    = self.nucleotideLettersDictionary;

// Insert the view in the view hierarchy
[self.containerView          addSubview:self.chromosomeBlockView];
[self.containerView bringSubviewToFront:self.chromosomeBlockView];

// A vain attempt to convince Cocoa-Touch that this view is worthy of being displayed ;-)
[self.chromosomeBlockView setNeedsDisplay];

건배, 더그

답변:


193

a UIView를 다시 렌더링 하도록하는 확실한 확실한 방법 은 [myView setNeedsDisplay]. 문제가있는 경우 다음 문제 중 하나가 발생할 수 있습니다.

  • 실제로 데이터를 갖기 전에 호출하거나 -drawRect:무언가를 오버 캐싱합니다.

  • 이 메서드를 호출하는 순간 뷰가 그려 질 것으로 예상합니다. Cocoa 그리기 시스템을 사용하여 의도적으로 "지금 당장이 순간에 그리기"를 요구할 방법이 없습니다. 이는 전체 뷰 합성 시스템, 쓰레기 성능을 방해하고 모든 종류의 아티팩트를 생성 할 수 있습니다. "다음 그리기주기에 그려야합니다."라고 말하는 방법 만 있습니다.

필요한 것이 "일부 논리, 그리기, 더 많은 논리"인 경우 "일부 더 많은 논리"를 별도의 메서드에 넣고 다음을 사용하여 호출해야합니다. -performSelector:withObject:afterDelay: 넣고 지연 0으로 합니다. 그러면 "일부 논리"가 뒤에 추가됩니다. 다음 그리기주기. 이러한 종류의 코드의 예와 필요할 수있는 경우에 대해서는 이 질문 을 참조하십시오 (코드가 복잡해 지므로 가능하면 다른 솔루션을 찾는 것이 가장 좋습니다).

일이 그려지지 않는다고 생각되면 중단 점을 설정 -drawRect:하고 언제 전화가 오는지 확인하십시오. 을 호출 -setNeedsDisplay하고 있지만 -drawRect:다음 이벤트 루프에서 호출되지 않는 경우 뷰 계층 구조를 자세히 살펴보고 어딘가에 현명하지 않도록하십시오. 지나치게 영리함은 내 경험에서 나쁜 그림의 # 1 원인입니다. 시스템이 원하는 작업을 수행하도록 속이는 방법을 가장 잘 안다고 생각할 때 일반적으로 원하지 않는 작업을 정확하게 수행하게됩니다.


Rob, 여기 내 체크리스트입니다. 1) 데이터가 있습니까? 예. 저는 connectionDidFinishLoading의 메인 스레드에서 호출되는 hideSpinner 메서드에서 뷰를 생성합니다. 따라서 : [self performSelectorOnMainThread : @selector (hideSpinner) withObject : nil waitUntilDone : NO]; 2) 즉시 그림을 그릴 필요가 없습니다. 그냥 필요 해요. 오늘. 현재, 그것은 완전히 무작위이며 내가 통제 할 수 없습니다. [myView setNeedsDisplay]는 완전히 신뢰할 수 없습니다. viewDidAppear :에서 [myView setNeedsDisplay]를 호출하기까지했습니다. Nuthin '. 코코아는 단순히 나를 무시합니다. 미치게 하는!!
dugla 2009.10.01

1
나는이 방법으로 사이에 지연이 종종 있다는 걸 발견했습니다 setNeedsDisplay과의 실제 통화 drawRect:. 호출 안정성이 여기에 있지만이를 "가장 강력한"솔루션이라고 부르지는 않겠습니다. 강력한 그리기 솔루션은 이론적으로 그리기를 요구하는 클라이언트 코드로 돌아 가기 직전에 모든 그리기를 수행해야합니다.
Slipp D. Thompson 2013

1
자세한 내용은 귀하의 답변에 대해 아래에 언급했습니다. 문서에서 호출하지 말라고 명시 적으로 언급 한 메서드를 호출하여 그리기 시스템을 강제로 실행하는 것은 강력하지 않습니다. 기껏해야 정의되지 않은 동작입니다. 성능과 그리기 품질이 저하 될 가능성이 높습니다. 최악의 경우 교착 상태 또는 충돌이 발생할 수 있습니다.
Rob Napier

1
돌아 가기 전에 그림을 그려야한다면 이미지 컨텍스트 (많은 사람들이 기대하는 캔버스처럼 작동 함)로 그립니다. 그러나 합성 시스템을 속이려고하지 마십시오. 최소한의 리소스 오버 헤드로 고품질 그래픽을 매우 빠르게 제공하도록 설계되었습니다. 그 중 일부는 런 루프의 끝에서 드로잉 단계를 통합하는 것입니다.
Rob Napier

1
@RobNapier 나는 당신이 왜 그렇게 방어 적이되는지 이해하지 못합니다. 다시 확인 해주세요; API 위반이 없으며 (자신의 방법을 호출하는 것이 위반이 아니라고 주장하는 귀하의 제안에 따라 정리되었지만) 교착 상태 또는 충돌 가능성이 없습니다. 또한 "속임수"가 아닙니다. CALayer의 setNeedsDisplay / displayIfNeeded를 정상적인 방식으로 사용합니다. 게다가 GLKView / OpenGL과 비슷한 방식으로 Quartz를 사용하여 그림을 그리는 데 꽤 오랫동안 사용해 왔습니다. 나열된 솔루션보다 안전하고 안정적이며 빠른 것으로 입증되었습니다. 나를 믿지 않는다면 시도해보세요. 여기서 잃을 것이 없습니다.
Slipp D. Thompson 2013

52

setNeedsDisplay와 drawRect : (5 초) 호출 사이에 큰 지연이 발생했습니다. 메인 스레드가 아닌 다른 스레드에서 setNeedsDisplay를 호출했습니다. 이 호출을 주 스레드로 이동 한 후 지연이 사라졌습니다.

이것이 도움이되기를 바랍니다.


이것은 확실히 내 버그였습니다. 나는 내가 메인 스레드에서 실행하고 있다는 인상을 받았지만 NSLogged NSThread.isMainThread 전까지는 메인 스레드에서 레이어를 변경하지 않는 코너 케이스가 있다는 것을 깨달았습니다. 머리를 뽑지 않도록 도와 주셔서 감사합니다!
미스터 T

14

환불이 보장되고 강화 된 콘크리트 견고한 방법으로 뷰를 동 기적으로 (호출 코드로 돌아 가기 전에) 그리 도록 강제하는 방법 은 CALayerUIView하위 클래스 와의 상호 작용 을 구성하는 것 입니다.

UIView 하위 클래스 displayNow()에서 레이어에“ set course for display ”다음“ make it so ” 를 지시 하는 메서드를 만듭니다 .

빠른

/// Redraws the view's contents immediately.
/// Serves the same purpose as the display method in GLKView.
public func displayNow()
{
    let layer = self.layer
    layer.setNeedsDisplay()
    layer.displayIfNeeded()
}

목표 -C

/// Redraws the view's contents immediately.
/// Serves the same purpose as the display method in GLKView.
- (void)displayNow
{
    CALayer *layer = self.layer;
    [layer setNeedsDisplay];
    [layer displayIfNeeded];
}

또한 draw(_: CALayer, in: CGContext)개인 / 내부 드로잉 메서드를 호출 할 메서드를 구현합니다 (every UIView이 a 이므로 작동 함 CALayerDelegate) .

빠른

/// Called by our CALayer when it wants us to draw
///     (in compliance with the CALayerDelegate protocol).
override func draw(_ layer: CALayer, in context: CGContext)
{
    UIGraphicsPushContext(context)
    internalDraw(self.bounds)
    UIGraphicsPopContext()
}

목표 -C

/// Called by our CALayer when it wants us to draw
///     (in compliance with the CALayerDelegate protocol).
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
{
    UIGraphicsPushContext(context);
    [self internalDrawWithRect:self.bounds];
    UIGraphicsPopContext();
}

그리고 internalDraw(_: CGRect)안전 장치와 함께 사용자 지정 방법을 만듭니다 draw(_: CGRect).

빠른

/// Internal drawing method; naming's up to you.
func internalDraw(_ rect: CGRect)
{
    // @FILLIN: Custom drawing code goes here.
    //  (Use `UIGraphicsGetCurrentContext()` where necessary.)
}

/// For compatibility, if something besides our display method asks for draw.
override func draw(_ rect: CGRect) {
    internalDraw(rect)
}

목표 -C

/// Internal drawing method; naming's up to you.
- (void)internalDrawWithRect:(CGRect)rect
{
    // @FILLIN: Custom drawing code goes here.
    //  (Use `UIGraphicsGetCurrentContext()` where necessary.)
}

/// For compatibility, if something besides our display method asks for draw.
- (void)drawRect:(CGRect)rect {
    [self internalDrawWithRect:rect];
}

그리고 이제 myView.displayNow()그릴 때 정말로 필요할 때마다 호출하십시오 (예 : CADisplayLink콜백에서) . 우리의 displayNow()메서드는 CALayerto 에게 알려줄 displayIfNeeded()것입니다. 그러면 동기식으로 다시 호출 draw(_:,in:)하여에서 그리기를 수행하여 internalDraw(_:)계속 진행하기 전에 컨텍스트에 그려진 내용으로 시각적 개체를 업데이트합니다.


이 접근 방식은 위의 @RobNapier와 유사하지만 displayIfNeeded()을 추가로 호출 하여 setNeedsDisplay()동기식으로 만드는 이점이 있습니다.

이것은 s가하는 CALayer것보다 더 많은 그리기 기능을 노출 하기 때문에 가능합니다. UIView레이어는 뷰보다 낮은 수준이고 레이아웃 내에서 고도로 구성 가능한 그리기를 위해 명시 적으로 설계되었으며 (Cocoa의 많은 것들과 마찬가지로) 유연하게 사용되도록 설계되었습니다. 부모 클래스, 위임자 또는 다른 드로잉 시스템에 대한 브리지 또는 자체적으로). CALayerDelegate프로토콜 의 적절한 사용은 이 모든 것을 가능하게합니다.

의 구성 가능성에 대한 자세한 내용은 Core Animation Programming GuideCALayerSetting Up Layer Objects 섹션 에서 찾을 수 있습니다 .


에 대한 설명서 drawRect:에는 "이 메서드를 직접 호출해서는 안됩니다."라고 명시되어 있습니다. 또한 CALayer display는 "이 메소드를 직접 호출하지 마십시오."라고 명시 적으로 말합니다. 레이어에 contents직접 동기식으로 그리려는 경우 이러한 규칙을 위반할 필요가 없습니다. contents원할 때 언제든지 레이어에 그릴 수 있습니다 (백그라운드 스레드에서도). 뷰에 하위 레이어를 추가하기 만하면됩니다. 그러나 그것은 정확한 합성 시간까지 기다려야하는 화면에 올리는 것과는 다릅니다.
Rob Napier

문서가 UIView의 레이어를 직접 엉망으로 만드는 것에 대해 경고하기 때문에 하위 레이어를 추가한다고 말합니다 contents( "레이어 개체가보기 개체에 연결되어 있으면이 속성의 내용을 직접 설정하지 않아야합니다.보기와 레이어 간의 상호 작용은 일반적으로 발생합니다. 이후 업데이트 중에이 속성의 내용을 교체하는 관점에서. ")이 방법을 특별히 권장하지 않습니다. 조기 드로잉은 성능과 드로잉 품질을 저하시킵니다. 그러나 어떤 이유로 필요한 경우 contents얻는 방법입니다.
Rob Napier

@RobNapier 포인트를 drawRect:직접 호출 하여 촬영 합니다. 이 기술을 시연 할 필요는 없었으며 수정되었습니다.
Slipp D. Thompson 2013

@RobNapier contents당신이 제안 하는 기술에 관해서는 … 처음에는 이와 같은 것을 시도했지만 작동하지 않았고 위의 솔루션이 성능이 좋지 않을 이유없이 코드가 훨씬 적다는 것을 알았습니다. 그러나 contents접근 방식에 대한 작업 솔루션이 있다면 읽어보고 싶을 것입니다 (이 질문에 대해 두 가지 답을 얻을 수없는 이유는 없습니다.)
Slipp D. Thompson

@RobNapier 참고 : 이것은 CALayer의 display. GLKView (또 다른 UIView 하위 클래스와 DRAW NOW! 기능 을 필요로하는 유일한 Apple 작성 방법)에서 수행되는 작업과 마찬가지로 UIView 하위 클래스의 사용자 지정 공용 메서드입니다 .
Slipp D. Thompson 2013

5

나는 같은 문제가 있었고 SO 또는 Google의 모든 솔루션이 나를 위해 작동하지 않았습니다. 일반적으로 setNeedsDisplay작동하지만 작동하지 않을 때 ... 가능한 모든 스레드와 항목에서 가능한 모든 방법으로 뷰를
호출하려고 시도했지만 setNeedsDisplay여전히 성공하지 못했습니다. Rob이 말했듯이

"다음 그리기주기에 그려야합니다."

그러나 어떤 이유로 이번에는 그리지 않을 것입니다. 그리고 내가 찾은 유일한 해결책은 다음과 같이 무승부를 차단하는 모든 것이 사라지도록 잠시 후 수동으로 호출하는 것입니다.

dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 
                                        (int64_t)(0.005 * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
    [viewToRefresh setNeedsDisplay];
});

자주 다시 그리는 데 뷰가 필요하지 않은 경우 좋은 솔루션입니다. 그렇지 않으면 움직이는 (액션) 작업을 수행하는 경우 일반적으로을 호출하는 데 문제가 없습니다 setNeedsDisplay.

저처럼 그곳에서 길을 잃은 사람에게 도움이되기를 바랍니다.


0

이것이 큰 변화이거나 프로젝트에 적합하지 않을 수도 있다는 것을 알고 있지만 이미 데이터를 확보 할 때까지 푸시를 수행하지 않는 것을 고려 했 습니까? 이렇게하면 뷰를 한 번만 그리면 사용자 경험도 향상됩니다. 푸시가 이미로드 된 상태로 이동합니다.

이를 수행하는 방법 UITableView didSelectRowAtIndexPath은 데이터를 비동기 적으로 요청하는 것입니다. 응답을 받으면 수동으로 segue를 수행하고 .NET의 viewController에 데이터를 전달합니다 prepareForSegue. 한편 간단한 로딩 표시기를 위해 활동 표시기를 표시하고 싶을 수 있습니다 https://github.com/jdg/MBProgressHUD

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.