web-dev-qa-db-ja.com

UIViewを強制的に再描画する最も堅牢な方法は何ですか?

アイテムのリストを含むUITableViewがあります。アイテムを選択すると、viewControllerがプッシュされ、次の操作が行われます。メソッドviewDidLoadから、サブビュー(drawRectをオーバーライドしたUIViewサブクラス)で必要なデータのURLRequestを起動します。データがクラウドから到着すると、ビュー階層の構築を開始します。問題のサブクラスにはデータが渡され、そのdrawRectメソッドにはレンダリングに必要なものがすべて含まれるようになります。

しかし。

DrawRectを明示的に呼び出さないため、Cocoa-Touchはそれを処理します。このUIViewサブクラスを本当にレンダリングしたいことをCocoa-Touchに通知する方法がありません。いつ?今ならいいでしょう!

[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];

乾杯、ダグ

111
dugla

UIViewを強制的に再レン​​ダリングするための保証された確実な方法は、[myView setNeedsDisplay]です。問題がある場合は、次のいずれかの問題に直面している可能性があります。

  • 実際にデータを取得する前に呼び出しているか、-drawRect:が何かをキャッシュしすぎています。

  • このメソッドを呼び出す時点でビューが描画されることを期待しています。 Cocoa描画システムを使用して、「今すぐこの描画」を要求する方法は意図的にありません。これは、ビュー合成システム全体を破壊し、パフォーマンスを低下させ、あらゆる種類のアーティファクトを作成する可能性があります。 「これは次の描画サイクルで描画する必要がある」と言う方法しかありません。

必要なのが「ロジック、描画、ロジック」である場合、「ロジック」を別のメソッドに配置し、-performSelector:withObject:afterDelay:を使用して遅延0で呼び出す必要があります。次の描画サイクルの後に、もう少しロジックを追加します。 この質問 を参照して、その種のコードの例と、それが必要になる場合があります(ただし、通常はコードが複雑になるため、可能であれば他のソリューションを探すのが最善です)。

物事が描かれていると思わない場合は、-drawRect:にブレークポイントを設定し、いつ呼び出されるかを確認します。 -setNeedsDisplayを呼び出しているが、次のイベントループで-drawRect:が呼び出されていない場合は、ビュー階層を調べて、どこかに隠れようとしていないことを確認してください。私の経験では、過度に賢いことが描画の悪さの一番の原因です。システムをだまして望んでいることを実行する方法を最もよく知っていると思うとき、通常は望んでいないことを正確に実行します。

186
Rob Napier

SetNeedsDisplayとdrawRectの呼び出しの間に大きな遅延がある問題がありました:(5秒)。メインスレッドとは異なるスレッドでsetNeedsDisplayを呼び出しました。この呼び出しをメインスレッドに移動すると、遅延はなくなりました。

これが助けになることを願っています。

51

ビューを同期的に描画するよう強制する、返金保証付きの強化された具体的な方法(呼び出し元のコードに戻る前に)CALayerUIViewサブクラスとの相互作用を構成します。

UIViewサブクラスで、レイヤーに「yes display」が必要であることを伝える- 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]を呼び出すだけです。 - displayCALayerdisplayIfNeededを伝え、- drawLayer:inContext:に同期的にコールバックし、- internalDrawWithRect:で描画を行い、描画されたものでビジュアルを更新します進む前のコンテキスト。


このアプローチは上記の@RobNapierに似ていますが、- displayIfNeededに加えて- setNeedsDisplayを呼び出すという利点があり、同期化されます。

これは、CALayersがUIViewsよりも多くの描画機能を公開するために可能です。レイヤーはビューよりも下位レベルであり、レイアウト内の高度に構成可能な描画のために明示的に設計されています。 )柔軟に使用できるように設計されています(親クラスとして、または委任者として、または他の描画システムへの橋渡しとして、または単独で)。

CALayersの構成可能性に関する詳細は、 Core Animation Programming Guideのセットアップレイヤーオブジェクトセクション にあります。

13

同じ問題が発生し、SOまたはGoogleのすべてのソリューションが機能しませんでした。通常、setNeedsDisplayは機能しますが、機能しない場合は...
ビューのsetNeedsDisplayを、考えられるすべてのスレッドや要素から可能な限りすべての方法で呼び出してみましたが、まだ成功していません。ロブが言ったように、

「これは次の描画サイクルで描画する必要があります。」

しかし、何らかの理由で、今回は描画しません。そして、私が見つけた唯一の解決策は、しばらくして手動で呼び出して、ドローをブロックするものを次のように通過させることです:

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を呼び出すだけで問題はありません。

私がそうであったように、それがそこで失われた誰かを助けることを願っています。

5
dreamzor

これは大きな変更であるか、プロジェクトに適さないこともありますが、既にデータを取得するまでプッシュを実行しないことを考慮しましたか?そうすれば、ビューを一度描画するだけで、ユーザーエクスペリエンスも向上します。プッシュは既に読み込まれた状態で移動します。

これを行う方法は、UITableViewdidSelectRowAtIndexPathにあり、非同期でデータを要求します。応答を受け取ったら、手動でセグエを実行し、prepareForSegueのviewControllerにデータを渡します。一方、単純な読み込みインジケータのチェックのために、アクティビティインジケータを表示することもできます https://github.com/jdg/MBProgressHUD

0
Miki