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];
}
RohinNZ
sumber
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.

Vadim Yelagin
sumber
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];
}
lnafziger
sumber
<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];
            }
        }
    }
}
Catalin
sumber
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

CodaFi
sumber
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)
    }
}
Kukosk
sumber
1
- (BOOL)checkVisibilityOfCell{
    if (tableView.contentSize.height <= tableView.frame.size.height) {
        return YES;
    } else{
        return NO;
    }
}
Naresh Jain
sumber
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
}
Andy Poes
sumber
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
        }

}
iRestMyCaseYourHonor
sumber
0

Mungkin untuk masalah ini lebih baik digunakan fungsi selanjutnya dari UITableViewDelegate

func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath)
Artyom
sumber
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)

fahlout
sumber