J'ai beaucoup de difficulté à créer une UICollectionView comme dans Spotify Player qui se comporte comme ceci:
Le problème pour moi est double.
1) Comment puis-je centrer les cellules afin que vous puissiez voir la cellule du milieu ainsi que celle de gauche et de droite.
2) Avec pagingEnabled = YES, la collectionview passe correctement d'une page à l'autre. Cependant, sans que les cellules soient centrées, il déplace simplement la vue de la collection sur une page qui est la largeur de l'écran. La question est donc de savoir comment faire bouger les pages pour obtenir l'effet ci-dessus.
3) Comment animez-vous la taille des cellules lorsqu'elles se déplacent
Le code que j'ai actuellement est un simple UICollectionView avec une configuration de délégué normale et des cellules UICollectionview personnalisées qui sont des carrés. Peut-être ai-je besoin de sous-classer UICollectionViewFlowLayout? Ou peut-être que je dois désactiver pagingEnabled sur NO, puis utiliser des événements de balayage personnalisés? J'adorerais toute aide!
Comme vous l'avez dit dans le commentaire que vous voulez que dans le code Objective-c, il existe une bibliothèque très célèbre appelée iCarousel qui peut être utile pour remplir votre exigence.Link: https://github.com/nicklockwood/ iCarousel
Vous pouvez utiliser "Rotary" ou "Linear" ou un autre style avec peu ou pas de modification pour implémenter la vue personnalisée
Pour l'implémenter, vous n'avez implémenté que quelques méthodes déléguées et cela fonctionne par exemple:
//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;
}
Vous pouvez vérifier la démonstration complète de travail à partir de ' Exemples/Exemple iOS de base ' qui est inclus avec le lien du référentiel Github
Comme il est ancien et populaire, vous pouvez trouver des tutoriels connexes et il sera également beaucoup plus stable que l'implémentation de code personnalisé
Afin de créer une disposition de carrousel horizontale, vous devrez sous-classer UICollectionViewFlowLayout
puis remplacer targetContentOffset(forProposedContentOffset:withScrollingVelocity:)
, layoutAttributesForElements(in:)
et shouldInvalidateLayout(forBoundsChange:)
.
Le code complet Swift 5/iOS 12.2 suivant montre comment les implémenter.
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")
}
}
Résultat attendu:
La source:
Eh bien, j'ai fait bouger UICollectionview comme ça, hier.
Je peux partager mon code avec vous :)
Voici mon storyboard
assurez-vous de décocher "Paging activé"
Voici mon code.
@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's height is exactly same with collection view's 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's 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;
}
Le code clé de la cellule centrée est
scrollViewWillEndDragging
insetForSectionAtIndex
Le code clé de l'animation de la taille est
Je souhaite que cela vous aide
P.S. Si vous souhaitez changer l'alpha comme l'image que vous avez téléchargée, ajoutez [cell setalpha] dans scrollViewDidScroll
Je voulais un comportement similaire il y a quelque temps, et avec l'aide de @Mike_M, j'ai pu le comprendre. Bien qu'il existe de nombreuses façons de procéder, cette implémentation particulière consiste à créer un UICollectionViewLayout personnalisé.
Code ci-dessous (Gist peut être trouvé ici: https://Gist.github.com/mmick66/981222 )
Maintenant, il est important de définir ce qui suit: *yourCollectionView*.decelerationRate = UIScrollViewDecelerationRateFast
, Cela empêche les cellules d'être ignorées par un balayage rapide.
Cela devrait couvrir les parties 1 et 2. Maintenant, pour la partie 3, vous pouvez l'incorporer dans la collectionView personnalisée en invalidant et en mettant à jour constamment, mais c'est un peu compliqué si vous me le demandez. Une autre approche consisterait donc à définir une CGAffineTransformMakeScale( , )
dans le UIScrollViewDidScroll
où vous mettez à jour dynamiquement la taille de la cellule en fonction de sa distance par rapport au centre de l'écran.
Vous pouvez obtenir les indexPaths des cellules visibles de collectionView à l'aide de [*youCollectionView indexPathsForVisibleItems]
, Puis obtenir les cellules de ces indexPaths. Pour chaque cellule, calculez la distance de son centre au centre de yourCollectionView
Le centre de la collectionView peut être trouvé en utilisant cette méthode astucieuse: CGPoint point = [self.view convertPoint:*yourCollectionView*.center toView:*yourCollectionView];
Maintenant, établissez une règle, que si le centre de la cellule est plus éloigné que x, la taille de la cellule est par exemple la "taille normale", appelez-la 1. et plus elle se rapproche du centre, plus elle se rapproche de deux fois la taille normale 2.
alors vous pouvez utiliser l'idée if/else suivante:
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);
}
Ce qui se passe, c'est que la taille de la cellule suivra exactement votre contact. Faites-moi savoir si vous avez d'autres questions, car j'écris la plupart du temps du haut de ma tête).
- (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);
}
pagingEnabled
ne doit pas être activé car il a besoin que chaque cellule soit la largeur de votre vue, ce qui ne fonctionnera pas pour vous car vous devez voir les bords des autres cellules. Pour vos points 1 et 2. Je pense que vous trouverez ce dont vous avez besoin ici d'une de mes réponses tardives à une autre question.
L'animation des tailles de cellule peut être réalisée en sous-classant UIcollectionviewFlowLayout et en remplaçant layoutAttributesForItemAtIndexPath:
À l'intérieur, modifiez les attributs de disposition fournis en appelant d'abord super, puis modifiez la taille des attributs de disposition en fonction de la position par rapport au centre de la fenêtre.
J'espère que cela aide.