2009-10-01 93 views
100

我有一個UITableView項目列表。選擇一個項目將推送一個viewController,然後繼續執行以下操作。 from method viewDidLoad我爲我的子視圖的on所需的數據啓動了一個URLRequest - 一個drawRect覆蓋的UIView子類。當數據從雲端到達時,我開始構建我的視圖層次結構。有問題的子類傳遞數據,它的drawRect方法現在擁有了它需要呈現的所有東西。什麼是強制UIView重繪的最強大的方法?

但是。

因爲我沒有明確地調用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]; 

乾杯, 道格

回答

176

的保證,岩石固的方式來強制UIView重新渲染是[myView setNeedsDisplay]。如果您遇到麻煩,你可能遇到了這些問題之一:

  • 你說它你確實有數據之前,或者你-drawRect:超過緩存的東西。

  • 您期待在您調用此方法的時刻繪製視圖。使用Cocoa繪圖系統故意沒有辦法要求「現在畫第二個」。這將破壞整個視圖合成系統,垃圾性能並可能會創建各種製造工藝。只有方法可以說「這需要在下一個抽獎週期中進行。」

如果你需要的是「一些邏輯,畫,多了一些邏輯,」那麼你需要把「一些更多的邏輯」,在一個單獨的方法以及使用-performSelector:withObject:afterDelay:具有0延遲調用它那在下一個抽獎週期結束後會提出「更多的邏輯」。請參閱this question以獲取該類代碼的示例以及可能需要的示例(儘管通常最好是在可能的情況下尋找其他解決方案,因爲它會使代碼複雜化)。

如果您不認爲事情正在進行中,請在-drawRect:中放置一個斷點並查看您何時接到電話。如果你打電話給-setNeedsDisplay,但-drawRect:沒有在下一個事件循環中調用,那麼深入你的視圖層次結構,並確保你沒有嘗試智勝是在某個地方。根據我的經驗,過分聰明是導致糟糕繪畫的首要原因。當你認爲自己最清楚如何欺騙系統做你想做的事情時,你通常會把它做到你不想要的地步。

+0

羅布, 這裏是我的清單。 1)我有數據嗎?是。我在一個方法中創建視圖 - hideSpinner - 從connectionDidFinishLoading在主線程上調用:因此: [self performSelectorOnMainThread:@selector(hideSpinner)withObject:nil waitUntilDone:NO]; 2)我不需要立即繪製。我只需要它。今天。目前,它完全是隨機的,並且不受我的控制。 [myView setNeedsDisplay]是完全不可靠的。 我甚至竟然在viewDidAppear:中調用[myView setNeedsDisplay]。 Nuthin'。可可根本無視我。 Maddening !! – dugla 2009-10-01 13:33:01

+0

看着你的代碼,你想確認的第一件事是,contatinerView本身就在屏幕上。把其他東西(例如UILabel),看看是否吸引。其次,確保self.containerView不是零。 「沒有任何反應」的主要原因是向nil發送消息。你不應該在這裏需要setNeedsDisplay,但是如果你這樣做了,將它發送到containerView而不是chromosomeBlockView會更典型。你問容器視圖重繪自己和它的子視圖(你已經重新排列),其中一個碰巧是chromosomeBlockView。 – 2009-10-01 17:37:28

+0

我發現,通過這種方法,'setNeedsDisplay'和'drawRect:'的實際調用之間通常會有一段延遲。雖然通話可靠性在這裏,但我不會稱之爲「最強大的」解決方案 - 理論上,強大的繪圖解決方案應該在返回要求抽取的客戶端代碼之前立即完成所有繪圖。 – 2013-05-08 02:36:52

49

我在調用setNeedsDisplay和drawRect之間有一個很大的延遲問題:(5秒)。原來,我在不同於主線程的線程中調用了setNeedsDisplay。將此呼叫移至主線後,延遲就消失了。

希望這有一些幫助。

+0

這絕對是我的錯誤。我的印象是,我在主線程上運行,但直到我NSLogged NSThread.isMainThread,我意識到有一個角落的情況下,我沒有在主線程上進行圖層更改。謝謝你挽救我拔出我的頭髮! – 2013-06-03 05:34:29

4

我有同樣的問題,所有來自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就沒有問題。

我希望它能幫助那些在那裏迷路的人,就像我一樣。

+1

謝謝,經過2個小時的研究後,我幫了我的忙。:) – Alex 2018-01-25 00:17:10

12

的退款保證,鋼筋混凝土,實心辦法迫使着眼於畫同步是配置CALayer的你UIView子相互作用(返回到調用代碼之前)。

在你的UIView子類,創建一個- display方法,告訴層「是的,它需要顯示」,然後在「做出這等」:

/// Redraws the view's contents immediately. 
/// Serves the same purpose as the display method in GLKView. 
/// Not to be confused with CALayer's `- display`; naming's really up to you. 
- (void)display 
{ 
    CALayer *layer = self.layer; 
    [layer setNeedsDisplay]; 
    [layer displayIfNeeded]; 
} 

而且實現- drawLayer:inContext:方法「會打電話給您的私人/內部繪製方法(工作,因爲每一個UIView的是CALayerDelegate)

/// 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(); 
} 

並創建自定義的- internalDrawWithRect:方法,故障安全- drawRect:一起:

/// 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 display]只要你真的,真的需要它來繪製。 - display將告訴CALayerdisplayIfNeeded,它將同步回撥到我們的- drawLayer:inContext:中,並執行- internalDrawWithRect:中的繪圖,在繼續之前更新與繪製到上下文中的內容相關的視覺。


這種方法類似於@ RobNapier頭頂,但除了呼籲- displayIfNeeded- setNeedsDisplay,這使得它同步的優勢。

這是可能的,因爲CALayer的expose多個圖形官能度大於UIView小號DO-層是電平低於視圖和明確的佈局內的高度可配置的圖的目的而設計的,和(在可可等許多事情)是旨在靈活使用(作爲父級,或作爲代理人,或作爲與其他繪圖系統的橋樑,或者僅僅是自己使用)。

關於CALayer s的可配置性的更多信息可以在Setting Up Layer Objects section of the Core Animation Programming Guide中找到。

+0

請注意,'drawRect:'的文檔明確指出「你不應該直接調用這個方法。」另外,CALayer的'display'明確地說「不要直接調用這個方法」。如果你想直接同時在圖層內容上繪製,就不需要違反這些規則。您可以隨時在任何時候繪製圖層的「內容」(即使在後臺線程中)。只需爲該視圖添加一個子圖層即可。但這不同於將它放在屏幕上,需要等待正確的合成時間。 – 2013-05-08 04:05:22

+0

我說要添加一個子圖層,因爲文檔也直接與UIView的圖層的內容進行混淆。(「如果圖層對象綁定到視圖對象,則應該避免直接設置該屬性的內容。而層通常會導致視圖在隨後的更新期間替換此屬性的內容。「)我並不特別推薦這種方法;過早拉伸會影響性能和拉絲質量。但如果你出於某種原因需要它,那麼'contents'就是如何得到它的。 – 2013-05-08 04:13:10

+0

@RobNapier直接調用'drawRect:'得到的點。沒有必要證明這種技術,並已修復。 – 2013-05-08 16:48:03

0

嗯,我知道這可能是一個很大的變化,或者甚至不適合您的項目,但是您是否認爲在您已經擁有數據之前不會執行推送?這樣你只需要畫一次視圖,用戶體驗也會更好 - 推動會在已經加載的情況下移動。

你這樣做的方式是在UITableViewdidSelectRowAtIndexPath你異步要求的數據。一旦收到響應,您就手動執行segue並將數據傳遞給prepareForSegue中的viewController。 同時您可能希望顯示一些活動的指標,爲簡單的加載指示燈檢查https://github.com/jdg/MBProgressHUD