1

「Instagram」と同じUIのアプリを開発中です。instagram のように、ユーザーがスクロールして友達が追加した新しい写真を見るフィードウォールがあります。各 UITableViewcell には、1 つの画像と独自のセクション ヘッダー ビューがあります。各セルのセクションを作成することでこれを達成しました。でもメモリリークが気になる

これが私のコードです

func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

        let headerView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 45))
        headerView.tag = 101

        let imageView :UIImageView   = UIImageView(frame: UtilityManager.setFrameWithOutHeight(CGRectMake(22, 5 , 30, 30)))
        imageView.backgroundColor = UIColor.blueColor()
        imageView.tag = 101
        imageView.layer.cornerRadius = 15.0
        imageView.clipsToBounds = true
        imageView.layer.borderWidth = 1
        imageView.layer.borderColor = UIColor.grayColor().CGColor
        headerView.addSubview(imageView)

        var label = UILabel(frame: UtilityManager.setFrameWithOutHeight(CGRectMake(50, 5, 320 - 60 , 35)))
        label.textAlignment = NSTextAlignment.Left
        label.backgroundColor = UIColor.clearColor()
        label.textColor = UIColor(red: 1, green: 0.28, blue: 0.27, alpha: 1)
        label.text = "I'am a test label"
        label.font = UIFont(name: Constants.FONT_MEDIUM, size: 12)
        headerView.addSubview(imageView)
        headerView.backgroundColor = UIColor.clearColor()

    return headerView
}

ところで、私はSwiftを使用しています。とてもクールですね ;) . カスタム セクションのヘッダーを作成するデリゲート関数について詳しく説明します。セルまたはセクションのいずれかが画面に表示されるたびに、headerView が既に割り当てられているセクションにスクロールして戻っても、このデリゲート メソッドが呼び出されます。そのため、スクロール中に1つのセクションに対してheaderViewが複数回作成されています。しかし、私はそれが間違った方法であることを知っています。天気のheaderViewがすでに作成されているかどうかを確認するには、何かが必要です。私のビューがメモリに存在する場合、コードにチェックを入れる方法はありますか?メモリを再度割り当てる必要はありません。誰か助けてください:)。少しでもお役に立てれば幸いです。

4

2 に答える 2

5

通常、Cocoa がテーブル セルに提供するものと同じメモリ管理とセル再利用パターンが必要なので、 を使用しますUITableViewHeaderFooterViewviewDidLoad:通話中tableView.registerClass(UITableViewHeaderFooterView.classForCoder(), forHeaderFooterReuseIdentifier:"Header")

次のようなデリゲート メソッドで、これらのビューをオンデマンドでデキューできるようになりました。

func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

    let headerView: UITableViewHeaderFooterView
    if let reusedView = tableView.dequeueReusableHeaderFooterViewWithIdentifier("Header") as? UITableViewHeaderFooterView {
        // Remove the existing contents of the reused view
        map(reusedView.contentView.subviews) { 
            $0.removeFromSuperview() 
        }
        headerView = reusedView
    } else {
        headerView = UITableViewHeaderFooterView(reuseIdentifier:"Header")
    }

    let imageView = ... // Set up image view
    let label = ... // Set up label  

    headerView.contentView.addSubview(imageView)
    headerView.contentView.addSubview(label)

    return headerView
}

Cocoa は、必要に応じてこれらのヘッダー ビューを破棄して再利用します。

画像とラベルのプロパティをサブクラス化して追加すると、この実装ははるかに簡単にUITableViewHeaderFooterViewなります (その後、サブビューを削除してからデキューされたビューに追加する必要はなく、名前で置き換えるだけです)。また、クラスのペン先を設計したり、ストーリーボードを介してビューにテーブル ビュー セルを使用したりする場合、デキューは決して nil を返しません。

「無限のセル」がある可能性があると述べているため、データソースをメモリに保持しすぎることを心配している可能性があります(これらの画像ビューに膨大な数の大きな画像がある可能性があります)。その場合、ある種のキャッシュを設定する必要があります。NSCacheその点に到達したら見てください。独自のキャッシュを実装する必要はありません。

OP の要求に応じて、このデキュー システムが Cocoa でどのように機能するかについて少し詳しく説明します。おそらくあなたのtableView:cellForRowAtIndexPath:デリゲート メソッド。ユーザーがテーブル ビューを下にスクロールしているとします。テーブル ビューの上部でセルとヘッダーが画面外に移動し、テーブル ビューの下部に新しいセルとヘッダーが表示されます。これらのセルはすべて同様の形式です。ここで、Cocoa が特別なことを何もしない (セルのキューがない) と仮定します。消えたセルは割り当てが解除され、表示されたセルは最初から作成されます。代わりに、Cocoa は効率的であるように努め、これらのセル/ヘッダーのキューを手元に保持します。一度に画面に表示する必要がある以上は作成されません。セルの 1 つがオフスクリーンになると、そのセルはキューに入れられ、そのセルは後でデキュー (キューから取り出す) できるようになります。セルまたはヘッダーをデキューすると (再利用識別子文字列に従って要求します)、画面外(またはそのうちの1つ)になった正確なビューが表示されます。取得したら、必要に応じて構成します。はい、上記のコードでは、まだ作成する必要がありますUIImageViewおよびUILabel(以下で、さらに優れた方法を説明します)。しかし、UIView変更する必要のないメイン ビューとあらゆる種類の装飾ビューは引き続き存在します (それらを再利用するための計算コストは​​かかりません)。基本的に、Cocoa はこれらのビューをリサイクルするのに役立ち、物事を非常に効率的かつ安全にします。

if let上記のコードがセルをデキューしようとするとき、それが nil かどうかをテストするステートメントにあることに気付くでしょう。テーブルビューをロードしているだけの場合、キューにセル/ビューがないため、これは重要です。そのため、nil の場合は、新しいビューを最初からインスタンス化します。インスタンス化するときは、再利用識別子文字列を指定します。このビューが後でオフスクリーンになった場合、Cocoa はそれを特別なキューに入れて後で返すようにします。ただし、上で述べたように、ビューがニブまたはストーリーボードからロードされている場合、デキューは常に非 nil を返します。nib からのロードに関する最後の詳細として、 のregisterNib:forHeaderFooterReuseIdentifer:メソッドを使用して再利用識別子を登録する必要がありますUITableView

ユーザーがテーブルビューをスクロールするたびに新しいビューをインスタンス化する必要がないため、Cocoa がこれらのセル/ビューを非常に効率的に処理する方法を確認できます。古いサブビューをすべて削除している上記のコードに少し失望するかもしれません。そして、新しい と をインスタンス化UIImageViewしますUILabel。実際、がっかりするはずです。これを回避するための推奨される方法は、 をサブクラス化することです。UITableViewHeaderFooterViewまたは、任意の を実際にサブクラス化することもできますUIView。imageView と label のプロパティを作成します。サブビューを古いコンテンツで破棄してからこれらのビューを再度作成する代わりに、それらのコンテンツを新しい適切なコンテンツ (imageView.image = newUIImageおよびlabel.text = newString) に置き換えるだけです。信じられないかもしれませんが、これにより多くの計算が節約されます。 UIViews の作成には多少のコストがかかります。サブクラス化のずさんな (ただしより柔軟な可能性がある) 代替方法は、tagプロパティを使用して挿入するこれらのビューにタグを付け、後で を使用してこれらのビューへの参照を取得することheaderView.viewWithTag(anInt)です。

そして、このヘッダーの話がすべて無駄になるのは嫌ですが、最後に 1 つだけ意見を述べます。すべてのセルに独自のヘッダーがあると言っていることに気付きました。これが当てはまる場合は、1 つの大きなテーブル セルを作成するだけのほうがよいでしょう (ヘッダー コンテンツを通常のテーブル ビュー セル コンテンツの上に配置します)。ヘッダーをまとめて使用することをスキップできます。私はあなたのアプリで何が起こっているかをすべて知っているわけではないので、その判断はあなたに任せます。

@Ankushに頑張ってください!ロープを知ることはUITableViewiOS の優れたスキルであり、将来役に立つと確信しています!

于 2015-07-22T23:16:02.420 に答える
2

独自にヘッダー ビューのキャッシュを実装する必要があります。フレームワークはそれを行いません。配列インスタンス変数を作成して、割り当てられたすべてのビューを保持することができます。次にデリゲート メソッドで、ヘッダー ビューを作成してキャッシュするか、単に返すだけです。

var headerViews = [UIView]()

func createHeaderView() -> UIView {
    let headerView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 45))

    ...

    return headerView
}

func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    while section >= headerViews.count {
        headerViews.append(createHeaderView())
    }

    var headerView = headerViews[section]

    // Set up the header view (image, text, ...)

    return headerView
}

これはおそらく最も効率的な方法ではないことに注意してください。これは、作成されたすべてのヘッダー ビューを保持するためです。同時に表示されるヘッダー ビューの最大数がわかっている場合は、ある種のキャッシュ エビクション戦略を組み込み、既存のヘッダー ビューを再利用できます。


アップデート

以下は、最大 20 個のヘッダー ビューを再利用する上記のコードのバリエーションです。これは、互いに 20 セクション離れたヘッダーが同時に画面に表示されない限り機能します。

var headerViews = [UIView]()

func createHeaderView() -> UIView {
    let headerView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 45))

    ...

    return headerView
}

func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let maxNumHeaderViews = 20
    let index = section % maxNumHeaderViews

    while index >= headerViews.count {
        headerViews.append(createHeaderView())
    }

    var headerView = headerViews[index]

    // Set up the header view (image, text, ...)

    return headerView
}

ただし、重要な点は、質問に投稿したコードにメモリ リークがないことです。Cocoa は、返されたビューが表示されている限り保持し、必要がなくなったら解放します。

于 2015-07-20T19:11:18.013 に答える