Cara terbaik untuk memeriksa apakah UITableViewCell benar-benar terlihat


100

Saya memiliki UITableView dengan sel-sel dengan ketinggian yang berbeda dan saya perlu tahu kapan mereka benar - benar terlihat atau tidak.

Saat ini saya mengulang setiap sel dalam daftar sel yang terlihat untuk memeriksa apakah benar-benar terlihat setiap kali tampilan digulir. Apakah ini pendekatan terbaik?

Ini kode saya:

- (void)scrollViewDidScroll:(UIScrollView *)aScrollView {

    CGPoint offset = aScrollView.contentOffset;
    CGRect bounds = aScrollView.bounds;    
    NSArray* cells = myTableView.visibleCells;

    for (MyCustomUITableViewCell* cell in cells) {

        if (cell.frame.origin.y > offset.y &&
            cell.frame.origin.y + cell.frame.size.height < offset.y + bounds.size.height) {

            [cell notifyCompletelyVisible];
        }
        else {

            [cell notifyNotCompletelyVisible];
        }
    }
}

Edit:

Harap dicatat bahwa * - (NSArray ) visibleCells mengembalikan sel yang terlihat yang keduanya benar-benar terlihat dan sebagian terlihat.

Edit 2:

Ini adalah kode yang direvisi setelah menggabungkan solusi dari lnafziger dan Vadim Yelagin :

- (void)scrollViewDidScroll:(UIScrollView *)aScrollView {
    NSArray* cells = myTableView.visibleCells;
    NSArray* indexPaths = myTableView.indexPathsForVisibleRows;

    NSUInteger cellCount = [cells count];

    if (cellCount == 0) return;

    // Check the visibility of the first cell
    [self checkVisibilityOfCell:[cells objectAtIndex:0] forIndexPath:[indexPaths objectAtIndex:0]];

    if (cellCount == 1) return;

    // Check the visibility of the last cell
    [self checkVisibilityOfCell:[cells lastObject] forIndexPath:[indexPaths lastObject]];

    if (cellCount == 2) return;

    // All of the rest of the cells are visible: Loop through the 2nd through n-1 cells
    for (NSUInteger i = 1; i < cellCount - 1; i++)
        [[cells objectAtIndex:i] notifyCellVisibleWithIsCompletelyVisible:YES];
}

- (void)checkVisibilityOfCell:(MultiQuestionTableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath {
    CGRect cellRect = [myTableView rectForRowAtIndexPath:indexPath];
    cellRect = [myTableView convertRect:cellRect toView:myTableView.superview];
    BOOL completelyVisible = CGRectContainsRect(myTableView.frame, cellRect);

    [cell notifyCellVisibleWithIsCompletelyVisible:completelyVisible];
}

3
Sebagai catatan tambahan, Anda harus melihat semua pertanyaan Anda sebelumnya dan menerima jawaban dari mereka yang telah membantu Anda.
— Baub

4
Terima kasih sudah memberi tahu saya! Saya sudah memberi mereka +1 tetapi saya lupa tentang fitur set jawaban yang diterima.
— RohinNZ

Kode Anda terlihat benar bagi saya, dan meskipun rumit, tetap berfungsi. Jangan perbaiki apa yang tidak rusak, eh?
— CodaFi

Jawaban:


125

Anda bisa mendapatkan persegi sel dengan rectForRowAtIndexPath:metode dan membandingkannya dengan persegi batas tableview menggunakan CGRectContainsRectfungsi.

Perhatikan bahwa ini tidak akan membuat instance sel jika tidak terlihat, dan karenanya akan menjadi lebih cepat.

Cepat

let cellRect = tableView.rectForRowAtIndexPath(indexPath)
let completelyVisible = tableView.bounds.contains(cellRect)

Obj-C

CGRect cellRect = [tableView rectForRowAtIndexPath:indexPath];
BOOL completelyVisible = CGRectContainsRect(tableView.bounds, cellRect);

Tentu saja ini tidak akan menganggap tampilan tabel terpotong oleh tampilan super atau dikaburkan oleh tampilan lain.


Terima kasih! Saya telah menggabungkan kode ini dengan solusi lnafziger.
— RohinNZ

11
Setelah dipikir-pikir, ini bisa jadi hanyaCGRectContainsRect(tableView.bounds, [tableView rectForRowAtIndexPath:indexPath])
— Vadim Yelagin

1
mengapa origin.x = 0, origin.y = 0, lebar = 0, tinggi = 0 saya? meskipun di UI tidak semuanya 0, saya menggunakan tata letak otomatis, ada ide?
— RainCast

7
Swift 3:let completelyVisible = tableView.bounds.contains(tableView.rectForRow(at: indexPath))
— cbartel

Bagaimana kami dapat menangani fungsionalitas serupa untuk UICollectionView?
— Satyam

64

Saya akan mengubahnya seperti ini:

- (void)checkVisibilityOfCell:(MyCustomUITableViewCell *)cell inScrollView:(UIScrollView *)aScrollView {
    CGRect cellRect = [aScrollView convertRect:cell.frame toView:aScrollView.superview];

    if (CGRectContainsRect(aScrollView.frame, cellRect))
        [cell notifyCompletelyVisible];
    else
        [cell notifyNotCompletelyVisible];
}

- (void)scrollViewDidScroll:(UIScrollView *)aScrollView { 
    NSArray* cells = myTableView.visibleCells;

    NSUInteger cellCount = [cells count];
    if (cellCount == 0)
        return;

    // Check the visibility of the first cell
    [self checkVisibilityOfCell:[cells firstObject] inScrollView:aScrollView];
    if (cellCount == 1)
        return;

    // Check the visibility of the last cell
    [self checkVisibilityOfCell:[cells lastObject] inScrollView:aScrollView];
    if (cellCount == 2)
        return;

    // All of the rest of the cells are visible: Loop through the 2nd through n-1 cells
    for (NSUInteger i = 1; i < cellCount - 1; i++)
        [[cells objectAtIndex:i] notifyCompletelyVisible];
}

<Dan> di pernyataan if harus <= atau> =, saya akan mengoreksi ini dalam jawaban ...
— Aardvark

12

Anda dapat mencoba sesuatu seperti ini untuk melihat berapa banyak persentase yang terlihat:

-(void)scrollViewDidScroll:(UIScrollView *)sender
{
    [self checkWhichVideoToEnable];
}

-(void)checkWhichVideoToEnable
{
    for(UITableViewCell *cell in [tblMessages visibleCells])
    {
        if([cell isKindOfClass:[VideoMessageCell class]])
        {
            NSIndexPath *indexPath = [tblMessages indexPathForCell:cell];
            CGRect cellRect = [tblMessages rectForRowAtIndexPath:indexPath];
            UIView *superview = tblMessages.superview;

            CGRect convertedRect=[tblMessages convertRect:cellRect toView:superview];
            CGRect intersect = CGRectIntersection(tblMessages.frame, convertedRect);
            float visibleHeight = CGRectGetHeight(intersect);

            if(visibleHeight>VIDEO_CELL_SIZE*0.6) // only if 60% of the cell is visible
            {
                // unmute the video if we can see at least half of the cell
                [((VideoMessageCell*)cell) muteVideo:!btnMuteVideos.selected];
            }
            else
            {
                // mute the other video cells that are not visible
                [((VideoMessageCell*)cell) muteVideo:YES];
            }
        }
    }
}

Jika tampilan tabel dapat menampilkan 2 sel dengan video (di iPad misalnya), kedua video akan diputar dengan kode di atas?
— Jerome

@Catalin Saya sudah mencoba solusi Anda tetapi tampilan tabel saya melambat. Adakah cara untuk meningkatkan kinerja pengguliran?
— Rahul Vyas

@RahulVyas maaf tapi tidak :( periksa elemen dalam Anda mungkin tata letaknya dapat dioptimalkan sedikit sehubungan dengan lapisan / sublapisan
— Catalin

5

Dari dokumen:

visibleCells Mengembalikan sel tabel yang terlihat di penerima.

- (NSArray *)visibleCells

Return Value Array yang berisi objek UITableViewCell, masing-masing mewakili sel yang terlihat dalam tampilan tabel penerima.

Ketersediaan Tersedia di iOS 2.0 dan yang lebih baru.

Lihat Juga - indexPathsForVisibleRows


4
Maaf mungkin saya kurang jelas. Saya hanya tertarik pada sel yang benar-benar terlihat. - (NSArray *) visibleCells dan indexPathsForVisibleRows mengembalikan sel yang sepenuhnya terlihat dan sebagian terlihat. Seperti yang Anda lihat di kode saya, saya sudah menggunakan - (NSArray *) visibleCells untuk menemukan yang benar-benar terlihat. Terima kasih.
— RohinNZ

4

Jika Anda juga ingin mempertimbangkan contentInset, dan tidak ingin mengandalkan superview (bingkai tampilan tabel di superview bisa jadi lebih dari 0,0), inilah solusi saya:

extension UITableView {

    public var boundsWithoutInset: CGRect {
        var boundsWithoutInset = bounds
        boundsWithoutInset.origin.y += contentInset.top
        boundsWithoutInset.size.height -= contentInset.top + contentInset.bottom
        return boundsWithoutInset
    }

    public func isRowCompletelyVisible(at indexPath: IndexPath) -> Bool {
        let rect = rectForRow(at: indexPath)
        return boundsWithoutInset.contains(rect)
    }
}

1
- (BOOL)checkVisibilityOfCell{
    if (tableView.contentSize.height <= tableView.frame.size.height) {
        return YES;
    } else{
        return NO;
    }
}

0
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
CGRect frame = cell.frame;
if (CGRectContainsRect(CGRectOffset(self.collectionView.frame, self.collectionView.contentOffset.x, self.collectionView.contentOffset.y), frame))
{
    // is on screen
}

0

Bahkan jika Anda mengatakan ingin memeriksanya setiap kali Anda menggulir, Anda juga dapat menggunakan

-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
       CGRect cellRect = [tableView convertRect:cell.frame toView:tableView.superview];
       if (CGRectContainsRect(tableView.frame, cellRect)){
            //Do things in case cell is fully displayed
        }

}

0

Mungkin untuk masalah ini lebih baik digunakan fungsi selanjutnya dari UITableViewDelegate

func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath)

Ini tidak akan berhasil. Dari doktertells the delegate that the specified cell was removed from the table.
— anatoliy_v

0

Kode di bawah ini akan memungkinkan Anda memeriksa apakah sel tampilan koleksi benar-benar terlihat melalui atribut tata letak tampilan koleksi.

guard let cellRect = collectionView.layoutAttributesForItem(at: indexPath)?.frame else { return } let isCellCompletelyVisible = collectionView.bounds.contains(cellRect)

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.