Лучший способ проверить, полностью ли виден UITableViewCell

100

У меня есть UITableView с ячейками разной высоты, и мне нужно знать, когда они полностью видны или нет.

На данный момент я просматриваю каждую ячейку в списке видимых ячеек, чтобы проверить, полностью ли она видна при каждой прокрутке представления. Это лучший подход?

Вот мой код:

- (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];
        }
    }
}

Редактировать:

Обратите внимание, что * - (NSArray ) visibleCells возвращает видимые ячейки, которые являются полностью видимыми и частично видимыми.

Изменить 2:

Это исправленный код после объединения решений как от lnafziger, так и от Вадима Елагина :

- (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
источник
3
В качестве примечания, вы должны перейти ко всем своим предыдущим вопросам и принять ответы тех, кто вам помог.
Baub
4
Спасибо, что сказал мне! Я уже дал им +1, но забыл о функции набора принятых ответов.
RohinNZ
Мне ваш код кажется правильным, и хотя он сложен, он работает. Не чините то, что не сломалось, а?
CodaFi

Ответы:

126

Вы можете получить прямоугольник ячейки с помощью rectForRowAtIndexPath:метода и сравнить его с прямоугольником границ таблицы с помощью CGRectContainsRectфункции.

Обратите внимание, что это не создаст экземпляр ячейки, если она не видна, и, следовательно, будет довольно быстро.

Swift

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

Obj-C

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

Конечно, при этом не будет рассматриваться представление таблицы, обрезанное супервизором или скрытое другим представлением.

Вадим Елагин
источник
Спасибо! Я объединил этот код с решением lnafziger.
RohinNZ
11
По второй мысли это может быть простоCGRectContainsRect(tableView.bounds, [tableView rectForRowAtIndexPath:indexPath])
Вадим Елагин
1
почему мой cellRect origin.x = 0, origin.y = 0, width = 0, height = 0? хотя в пользовательском интерфейсе они не все нули, я использую автоматический макет, есть идеи?
RainCast
7
Swift 3:let completelyVisible = tableView.bounds.contains(tableView.rectForRow(at: indexPath))
cbartel
Как мы можем обрабатывать аналогичные функции для UICollectionView?
Satyam
64

Я бы изменил это так:

- (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];
}
Инафцигер
источник
Знаки <и> в выражении if должны быть <= или> =, я исправлю это в ответе ...
Aardvark
12

Вы можете попробовать что-то вроде этого, чтобы увидеть, сколько процентов видно:

-(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];
            }
        }
    }
}
Каталин
источник
Если в виде таблицы могут отображаться 2 ячейки с видео (например, на iPad), оба видео будут воспроизводиться с приведенным выше кодом?
Джером
@Catalin Я пробовал ваше решение, но моя таблица замедляется. Есть ли способ улучшить скорость прокрутки?
Рахул Вьяс
@RahulVyas, извините, но нет :( проверьте свои внутренние элементы, может быть, макет можно немного оптимизировать, что касается слоев / подслоев
Catalin
5

Из документов:

visibleCells Возвращает ячейки таблицы, видимые в приемнике.

- (NSArray *)visibleCells

Возвращаемое значение Массив, содержащий объекты UITableViewCell, каждый из которых представляет видимую ячейку в представлении принимающей таблицы.

Доступность Доступно в iOS 2.0 и новее.

См. Также - indexPathsForVisibleRows

CodaFi
источник
4
Извините, возможно, я был недостаточно ясен. Меня интересуют только полностью видимые ячейки. - (NSArray *) visibleCells и indexPathsForVisibleRows возвращают ячейки, которые полностью и частично видны. Как вы можете видеть в моем коде, я уже использую - (NSArray *) visibleCells, чтобы найти те, которые полностью видны. Все равно спасибо.
RohinNZ
4

Если вы также хотите принять во внимание contentInset и не хотите полагаться на супервизор (рамка представления таблицы в супервизоре может быть чем-то другим, кроме 0,0), вот мое решение:

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

Даже если вы сказали, что хотите проверять его каждый раз при прокрутке, вы также можете использовать

-(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
источник
0

Возможно, для этой проблемы лучше использовать следующую функцию из UITableViewDelegate

func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath)
Артём
источник
Это не будет работать. Из документаtells the delegate that the specified cell was removed from the table.
анатолий_в
0

Приведенный ниже код позволит вам проверить, полностью ли видна ячейка представления коллекции через атрибуты макета представления коллекции.

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

фахлаут
источник