Подтвердить что ты не робот

Как создать центрированный UICollectionView, например, в Spotify Player

У меня много трудностей, пытаясь создать UICollectionView, как в Spotify Player, который действует следующим образом:

a busy cat

Проблема для меня в два раза.

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

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

2) С pagingEnabled = YES, просмотр коллекции правильно перебирает с одной страницы на другую. Однако, если ячейки не центрированы, он просто перемещает представление коллекции по странице, которая является шириной экрана. Итак, вопрос в том, как вы перемещаете страницы, чтобы получить эффект выше.

3), как вы анимируете размер ячеек при перемещении.

  • Я не хочу слишком беспокоиться об этом. Если я смогу добиться этого, это будет здорово, но более трудные проблемы - 1 и 2.

Код, который я имею в настоящее время, представляет собой простой UICollectionView с нормальной настройкой делегата и пользовательскими ячейками UICollectionview, которые являются квадратами. Может быть, я подошел к подклассу UICollectionViewFlowLayout? Или, может быть, мне нужно включить pagingEnabled в НЕТ, а затем использовать пользовательские события салфетки? Любить любую помощь!

4b9b3361

Ответ 1

Как вы сказали в комментарии, вы хотите, чтобы в коде Objective-c существовала очень известная библиотека iCarousel, которая может помочь в выполнении вашего требования. Ссылка: https://github.com/nicklockwood/iCarousel

Вы можете использовать "Поворотный" или "Линейный" или какой-либо другой стиль с небольшими изменениями или без изменений для реализации пользовательского представления.

Для его реализации у вас есть только некоторые методы делегата, и он работает для ex:

//specify the type you want to use in viewDidLoad
_carousel.type = iCarouselTypeRotary;

//Set the following delegate methods
- (NSInteger)numberOfItemsInCarousel:(iCarousel *)carousel
{
    //return the total number of items in the carousel
    return [_items count];
}

- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSInteger)index reusingView:(UIView *)view
{
    UILabel *label = nil;

    //create new view if no view is available for recycling
    if (view == nil)
    {
        //don't do anything specific to the index within
        //this 'if (view == nil) {...}' statement because the view will be
        //recycled and used with other index values later
        view = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200.0f, 200.0f)];
        ((UIImageView *)view).image = [UIImage imageNamed:@"page.png"];
        view.contentMode = UIViewContentModeCenter;

        label = [[UILabel alloc] initWithFrame:view.bounds];
        label.backgroundColor = [UIColor clearColor];
        label.textAlignment = NSTextAlignmentCenter;
        label.font = [label.font fontWithSize:50];
        label.tag = 1;
        [view addSubview:label];
    }
    else
    {
        //get a reference to the label in the recycled view
        label = (UILabel *)[view viewWithTag:1];
    }

    //set item label
    label.text = [_items[index] stringValue];

    return view;
}

- (CGFloat)carousel:(iCarousel *)carousel valueForOption:(iCarouselOption)option withDefault:(CGFloat)value
{
    if (option == iCarouselOptionSpacing)
    {
        return value * 1.1;
    }
    return value;
}

Вы можете проверить полную рабочую демонстрацию в разделе " Примеры/Базовый пример iOS ", который включен в ссылку на репозиторий Github.

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

Ответ 2

Чтобы создать горизонтальный карусельный макет, вам нужно UICollectionViewFlowLayout подкласс UICollectionViewFlowLayout затем переопределить targetContentOffset(forProposedContentOffset:withScrollingVelocity:), layoutAttributesForElements(in:) и shouldInvalidateLayout(forBoundsChange:).

Следующий полный код Swift 5/iOS 12.2 показывает, как их реализовать.


CollectionViewController.swift

import UIKit

class CollectionViewController: UICollectionViewController {

    let collectionDataSource = CollectionDataSource()
    let flowLayout = ZoomAndSnapFlowLayout()

    override func viewDidLoad() {
        super.viewDidLoad()

        title = "Zoomed & snapped cells"

        guard let collectionView = collectionView else { fatalError() }
        //collectionView.decelerationRate = .fast // uncomment if necessary
        collectionView.dataSource = collectionDataSource
        collectionView.collectionViewLayout = flowLayout
        collectionView.contentInsetAdjustmentBehavior = .always
        collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
    }

}

ZoomAndSnapFlowLayout.swift

import UIKit

class ZoomAndSnapFlowLayout: UICollectionViewFlowLayout {

    let activeDistance: CGFloat = 200
    let zoomFactor: CGFloat = 0.3

    override init() {
        super.init()

        scrollDirection = .horizontal
        minimumLineSpacing = 40
        itemSize = CGSize(width: 150, height: 150)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func prepare() {
        guard let collectionView = collectionView else { fatalError() }
        let verticalInsets = (collectionView.frame.height - collectionView.adjustedContentInset.top - collectionView.adjustedContentInset.bottom - itemSize.height) / 2
        let horizontalInsets = (collectionView.frame.width - collectionView.adjustedContentInset.right - collectionView.adjustedContentInset.left - itemSize.width) / 2
        sectionInset = UIEdgeInsets(top: verticalInsets, left: horizontalInsets, bottom: verticalInsets, right: horizontalInsets)

        super.prepare()
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        guard let collectionView = collectionView else { return nil }
        let rectAttributes = super.layoutAttributesForElements(in: rect)!.map { $0.copy() as! UICollectionViewLayoutAttributes }
        let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.frame.size)

        // Make the cells be zoomed when they reach the center of the screen
        for attributes in rectAttributes where attributes.frame.intersects(visibleRect) {
            let distance = visibleRect.midX - attributes.center.x
            let normalizedDistance = distance / activeDistance

            if distance.magnitude < activeDistance {
                let zoom = 1 + zoomFactor * (1 - normalizedDistance.magnitude)
                attributes.transform3D = CATransform3DMakeScale(zoom, zoom, 1)
                attributes.zIndex = Int(zoom.rounded())
            }
        }

        return rectAttributes
    }

    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
        guard let collectionView = collectionView else { return .zero }

        // Add some snapping behaviour so that the zoomed cell is always centered
        let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.frame.width, height: collectionView.frame.height)
        guard let rectAttributes = super.layoutAttributesForElements(in: targetRect) else { return .zero }

        var offsetAdjustment = CGFloat.greatestFiniteMagnitude
        let horizontalCenter = proposedContentOffset.x + collectionView.frame.width / 2

        for layoutAttributes in rectAttributes {
            let itemHorizontalCenter = layoutAttributes.center.x
            if (itemHorizontalCenter - horizontalCenter).magnitude < offsetAdjustment.magnitude {
                offsetAdjustment = itemHorizontalCenter - horizontalCenter
            }
        }

        return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y)
    }

    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        // Invalidate layout so that every cell get a chance to be zoomed when it reaches the center of the screen
        return true
    }

    override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
        let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext
        context.invalidateFlowLayoutDelegateMetrics = newBounds.size != collectionView?.bounds.size
        return context
    }

}

CollectionDataSource.swift

import UIKit

class CollectionDataSource: NSObject, UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 9
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell
        return cell
    }

}

CollectionViewCell.swift

import UIKit

class CollectionViewCell: UICollectionViewCell {

    override init(frame: CGRect) {
        super.init(frame: frame)

        contentView.backgroundColor = .green
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

Ожидаемый результат:

enter image description here


Источник:

Ответ 3

Хорошо, вчера я сделал UICollectionview таким же образом.

Я могу поделиться с вами своим кодом:)

Вот моя раскадровка

снимите флажок "Пейджинг включен"

Вот мой код.

@interface FavoriteViewController () <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
{
    NSMutableArray * mList;

    CGSize cellSize;
}

@property (weak, nonatomic) IBOutlet UICollectionView *cv;
@end

@implementation FavoriteViewController

- (void) viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    // to get a size.
    [self.view setNeedsLayout];
    [self.view layoutIfNeeded];

    CGRect screenFrame = [[UIScreen mainScreen] bounds];
    CGFloat width = screenFrame.size.width*self.cv.frame.size.height/screenFrame.size.height;
    cellSize = CGSizeMake(width, self.cv.frame.size.height);
    // if cell height is exactly same with collection view height, you get an warning message.
    cellSize.height -= 1;

    [self.cv reloadData];

    // setAlpha is for hiding looking-weird at first load
    [self.cv setAlpha:0];
}

- (void) viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    [self scrollViewDidScroll:self.cv];
    [self.cv setAlpha:1];
}

#pragma mark - scrollview delegate
- (void) scrollViewDidScroll:(UIScrollView *)scrollView
{
    if(mList.count > 0)
    {
        const CGFloat centerX = self.cv.center.x;
        for(UICollectionViewCell * cell in [self.cv visibleCells])
        {
            CGPoint pos = [cell convertPoint:CGPointZero toView:self.view];
            pos.x += cellSize.width/2.0f;
            CGFloat distance = fabs(centerX - pos.x);

// If you want to make side-cell scale bigger or smaller,
// change the value of '0.1f'
            CGFloat scale = 1.0f - (distance/centerX)*0.1f;
            [cell setTransform:CGAffineTransformMakeScale(scale, scale)];
        }
    }
}

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{ // for custom paging
    CGFloat movingX = velocity.x * scrollView.frame.size.width;
    CGFloat newOffsetX = scrollView.contentOffset.x + movingX;

    if(newOffsetX < 0)
    {
        newOffsetX = 0;
    }
    else if(newOffsetX > cellSize.width * (mList.count-1))
    {
        newOffsetX = cellSize.width * (mList.count-1);
    }
    else
    {
        NSUInteger newPage = newOffsetX/cellSize.width + ((int)newOffsetX%(int)cellSize.width > cellSize.width/2.0f ? 1 : 0);
        newOffsetX = newPage*cellSize.width;
    }

    targetContentOffset->x = newOffsetX;
}

#pragma mark - collectionview delegate
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return mList.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"list" forIndexPath:indexPath];

    NSDictionary * dic = mList[indexPath.row];

    UIImageView * iv = (UIImageView *)[cell.contentView viewWithTag:1];
    UIImage * img = [UIImage imageWithData:[dic objectForKey:kKeyImg]];
    [iv setImage:img];

    return cell;
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    return cellSize;
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
    CGFloat gap = (self.cv.frame.size.width - cellSize.width)/2.0f;
    return UIEdgeInsetsMake(0, gap, 0, gap);
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
{
    return 0;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
{
    return 0;
}

Key code создания ячейки с ячейкой

  • scrollViewWillEndDragging

  • insetForSectionAtIndex

Key code анимированного размера

  • scrollviewDidScroll

Я хочу, чтобы это помогло вам

P.S. Если вы хотите изменить альфу так же, как и загруженное изображение, добавьте [cell setalpha] в scrollViewDidScroll

Ответ 4

Некоторое время назад я хотел подобное поведение, и с помощью @Mike_M я смог понять это. Хотя есть много, много способов сделать это, эта конкретная реализация заключается в создании пользовательского UICollectionViewLayout.

Код ниже (суть можно найти здесь: https://gist.github.com/mmick66/9812223)

Теперь важно установить следующее: *yourCollectionView*.decelerationRate = UIScrollViewDecelerationRateFast, это предотвращает пропуск ячеек быстрым пролистыванием.

Это должно охватывать части 1 и 2. Теперь, для части 3 вы можете включить это в пользовательский collectionView, постоянно аннулируя и обновляя, но это немного хлопотно, если вы спросите меня. Поэтому другим подходом было бы установить CGAffineTransformMakeScale(, ) в UIScrollViewDidScroll где вы динамически обновляете размер ячейки на основе ее расстояния от центра экрана.

Вы можете получить indexPath видимых ячеек collectionView, используя [*youCollectionView indexPathsForVisibleItems] а затем получить ячейки для этих indexPaths. Для каждой ячейки вычислите расстояние от ее центра до центра yourCollectionView

Центр collectionView можно найти с помощью этого CGPoint point = [self.view convertPoint:*yourCollectionView*.center toView:*yourCollectionView]; метода: CGPoint point = [self.view convertPoint:*yourCollectionView*.center toView:*yourCollectionView];

Теперь установите правило, что если центр ячейки находится дальше, чем x, то размер ячейки равен, например, "нормальному размеру", назовите его 1. И чем ближе он подходит к центру, тем ближе он становится вдвое. нормальный размер 2.

тогда вы можете использовать следующую идею if/else:

 if (distance > x) {
        cell.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
 } else if (distance <= x) {

        float scale = MIN(distance/x) * 2.0f;
        cell.transform = CGAffineTransformMakeScale(scale, scale);
 }

Что происходит, так это то, что размер ячейки будет точно соответствовать вашим прикосновениям. Дайте мне знать, если у вас есть еще вопросы, поскольку я пишу большую часть этого из головы).

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)offset 
                             withScrollingVelocity:(CGPoint)velocity {

CGRect cvBounds = self.collectionView.bounds;
CGFloat halfWidth = cvBounds.size.width * 0.5f;
CGFloat proposedContentOffsetCenterX = offset.x + halfWidth;

NSArray* attributesArray = [self layoutAttributesForElementsInRect:cvBounds];

UICollectionViewLayoutAttributes* candidateAttributes;
for (UICollectionViewLayoutAttributes* attributes in attributesArray) {

    // == Skip comparison with non-cell items (headers and footers) == //
    if (attributes.representedElementCategory != 
        UICollectionElementCategoryCell) {
        continue;
    }

    // == First time in the loop == //
    if(!candidateAttributes) {
        candidateAttributes = attributes;
        continue;
    }

    if (fabsf(attributes.center.x - proposedContentOffsetCenterX) < 
        fabsf(candidateAttributes.center.x - proposedContentOffsetCenterX)) {
        candidateAttributes = attributes;
    }
}

return CGPointMake(candidateAttributes.center.x - halfWidth, offset.y);

}

Ответ 5

pagingEnabled не должен быть включен, так как каждая ячейка должна быть шириной вашего представления, которая не будет работать для вас, так как вам нужно увидеть края других ячеек. Для ваших пунктов 1 и 2. Я думаю, вы найдете то, что вам нужно здесь, из одного из моих поздних ответов на другой вопрос.

Анимация размеров ячеек может быть достигнута путем подкласса UIcollectionviewFlowLayout и переопределения layoutAttributesForItemAtIndexPath:. Внутри этого изменения изменяются атрибуты макета, предоставляемые при первом вызове super, а затем изменяются размер атрибутов макета на основе позиции, относящейся к центру окна.

Надеюсь, это поможет.