IOS —— CollectionView 所遇到的坑


哈嘍,還是俺,記錄一下今天復習的成果(collectionView還要學習,這老臉丟不起)

重點是日歷方法以及collectionView的一些細節補充!

那么閑話少說上代碼上分析


1. xib創建日歷

我們知道創建collectionView時,必須要用到initWithFrame: collectionFlowLayout 方法並且還要創建一個collectionFlowLayout對象,設置屬性雲雲

代碼上是這樣創建的,但xib呢? 在開發中有些時候頁面控制沒太多約束時,嘗試xib開發也不是一種壞事。so?go

既然要使用collectionView創建日歷,那么准備需要什么?

1.cell的h.m.xib 文件  ,並在文件里創建大小與cell相同的label

2.headerView xib 文件 ,並在文件里創建7個均等分割屏幕大小的label,修改text為星期日、星期一等 (為什么是星期日先后面會提及)

對應視圖控制器與控件之間的關聯,以及代理對象的設置。 此處容易疏漏導致報錯

准備完畢后開始激活CollectionView了!  

[_collectView registerNib:[UINib nibWithNibName:@"DateCell" bundle:nil] forCellWithReuseIdentifier:@"DateCell"];

collectionView和tableView不一樣,他的屬性大多數需要注冊才可使用。這里為注冊DateCell

[_collectView registerNib:[UINib nibWithNibName:@"DateHeadView" bundle:nil] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"DateHeadView"];

這里為注冊headerView,同樣的注冊collectionView的footerView一樣是在這里,只需要將supplementaryViewofKind的值的末尾改成Footer即可。

collectionView 倆把火槍 cell、以及flowLayout,缺一不可。那么接下來就是flowLayout

UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
//cell 大小
flowLayout.itemSize  = CGSizeMake(ScreenWidth / 7,  ScreenWidth / 7);
//cell 行距
flowLayout.minimumLineSpacing = 0;
//cell 距離
flowLayout.minimumInteritemSpacing = 0;
//headerView Size
flowLayout.headerReferenceSize = CGSizeMake(ScreenWidth, 50);
[_collectView setCollectionViewLayout:flowLayout];

然后的就是代理對象的實現

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
    if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
       UICollectionReusableView *headerView =  [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"DateHeadView" forIndexPath:indexPath];
        
        return headerView;
    }
    return nil;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    DateCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"DateCell" forIndexPath:indexPath];
    
    cell.textLabel.text = [_dateArr[indexPath.row] description];
    
    return cell;
}

由於是在…沒什么好講的,就僅僅是保證注冊對象命名一致,重用、數據源綁定即可

那么會有人問了,日歷部分呢?嗯

這里要引出一個 NSCalendar的類。以前制作日歷總是非常復雜,獲取時間方式也是。需要瑣碎代碼控制

往后蘋果就統一出了這個類方便后來的人自定義日歷

先貼代碼

+ (NSInteger)totalDaysInMonthFromDate:(NSDate *)date
{
        NSRange dayRange = [[NSCalendar currentCalendar] rangeOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitMonth forDate:date];
    return  dayRange.length;
}

//@"2016-09-29" YYYY-MM-dd
/*
 字符串格式、轉時間 時間差
 */
+ (NSInteger)totalDaysInMonthFromDateStr:(NSString *)dateStr
{
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    //調整時區
    [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
    [formatter setDateFormat:@"YYYY-MM-dd"];
    
    NSDate *date = [formatter dateFromString:dateStr];

    return [self totalDaysInMonthFromDate:date];
}

+ (NSInteger)weekDayMonthOfFirstDayFromDate:(NSDate *)date
{
    //美國時間是將星期天視作一周的第一天。
    NSInteger firstDayOfMonthInt = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitWeekOfMonth forDate:date];
    
    return firstDayOfMonthInt ;
}

+ (NSInteger)weekDayMonthOfFirstDayFromDateStr:(NSString *)dateStr
{
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    
    [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
    [formatter setDateFormat:@"YYYY-MM-dd"];
    
    NSDate *date = [formatter dateFromString:dateStr];
    
    return [self weekDayMonthOfFirstDayFromDate:date];
}

第一個方法里就用到了

[[NSCalendar currentCalendar] rangeOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitMonth forDate:date]

有些人就會問了,這個是啥啊?還能是啥日歷方法唄

在日歷類里,從輸入的數據源的月份中查詢有當月有多少天。 

輸入當前的12月,那么range的范圍長度就是31。這就能直接返回給collectionView的numberOfSection方法,得知cell得創建幾個

Unit屬性NSCalendarUnit里有還有許多枚舉,這里就不一一列舉。有需要點進去根據需求輸入就好

接下來的問題是,如何確保每個月的第一天添加到適合的位置? 總不能每個月的第一天都是星期天吧。

那么這里再用了一個方法,查找到當月第一天距離星期天有幾天

+ (NSInteger)weekDayMonthOfFirstDayFromDate:(NSDate *)date
{
    //美國時間是將星期天視作一周的第一天。
    NSInteger firstDayOfMonthInt = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitWeekOfMonth forDate:date];
    
    return firstDayOfMonthInt ;
}

我知道有些人會問,為什么要距離星期天幾天雲雲

注釋里也有寫到,因為美國時間是將星期日視為一周的第一天,所以我們通過方法獲取當前月份的第一周中第一天距離星期天有幾天。將獲得的數據與當前月份的數據一起添加到一個NSArray中。便完成了日歷的數據源了!

- (void)dataHandle:(NSString *)datestrr
{
    NSInteger firstWeekDay = [DateModel weekDayMonthOfFirstDayFromDate:[NSDate date]];
    
    
    NSInteger dayCount = [DateModel totalDaysInMonthFromDate:[NSDate date]];
    //補前面空白
    for (int i = 0; i< firstWeekDay; i++) {
        [_dateArr addObject:@""];
    }
    //數據源為0-30 所以需要做+1操作
    for (int i =0; i< dayCount; i++) {
        [_dateArr addObject:@(i+1)];
    }
    //前邊補了空白末尾也不能漏呀
    int leftDay = 0;
    if (!(_dateArr.count%7) ){
        leftDay = 7 - _dateArr.count%7;
    }
    for (int i = 0; i < leftDay; i++) {
        [_dateArr addObject:@""];
    }

    [_collectView reloadData];
}

剩下的,就是collectionView的一些小操作了。就不一一提及

 


2.瀑布流

說起瀑布流,那一定是開發們繞不開的坑。學都學過。

瀑布流需要通過設置布局屬性來實現,說道布局屬性……中美合拍,文體倆開花?

不不不 說的是 UICollectionFlowLayout。我們只要重寫一下布局屬性就可以簡單的實現了

但是這里還是會遇到一些坑。

collectionView Xib的設置這里就略過,和上文同樣的設置方式。

Layout的源碼先pro出來

- (void)prepareLayout
{
    //准備數據 對每一個cell布局進行初始化
    [layoutAry removeAllObjects];
    [_originYArr removeAllObjects];
    // row
    for (int i = 0; i<_collectViewRowCount; i++) {
        [_originYArr addObject:@(0)];
    }
    
    NSInteger cellCount = [self.collectionView numberOfItemsInSection:0];
    for (int i = 0; i< cellCount; i++) {
        // 處理每一個cell的布局屬性
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
        UICollectionViewLayoutAttributes *attribute = [self layoutAttributesForItemAtIndexPath:indexPath];
        [layoutAry addObject:attribute];
    }
    
}

//初始化布局方法
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    
    
    
    float cellSizeWidth = [UIScreen mainScreen].bounds.size.width/ _collectViewRowCount;
    //如果這個cell是一個圖片,那么就返回圖片高度
    float cellSizeHeight = 50 + arc4random_uniform(100); //通過indexPath 算出相應高度
    float cellX = cellSizeWidth * (indexPath.row %3);
    //第一個cell的Y
    //動態計算布局
    float cellY = [_originYArr[indexPath.row%3] floatValue];
    _originYArr[indexPath.row %3] = @(cellY +cellSizeHeight);
    
    attributes.frame = CGRectMake(cellX, cellY, cellSizeWidth, cellSizeHeight);
    
    
    
    return attributes;
}

//tableView 所有高度計算完,算出contentSize
//滑動不了的原因
- (CGSize)collectionViewContentSize
{
    float maxHeight =[_originYArr[0] floatValue];
    for (int i = 1; i<_collectViewRowCount; i++) {
        if (maxHeight < [_originYArr[i] floatValue]) {
            maxHeight = [_originYArr[i] floatValue];
        }
    }
    CGSize size = CGSizeMake([UIScreen mainScreen].bounds.size.width, maxHeight);

    return size;
}

我們假定瀑布流為3列

首先我們需要用到這個方法,字面意思 准備布局 相當於View will appear 。在准備布局的代碼里准備數據。

- (void)prepareLayout

 如注釋提及,首先得清空數據。並且因為瀑布流中每一列的初始y值都為0,后續的才參差不齊。

那么我們需要准備一個存儲高度的數組,專門用於計算高度。接下來便是遍歷當前collectionView numberOfSection中cell的count數。

然后分別進行設置布局。

當設置布局時,就用到接下來的代理方法。

這里的float設置,字面上說的不如自己畫個圖,畫個圖就懂了…實在不懂利用代碼打個斷點了解一下

大致說說就是計算出當前Cell的x、y、width,然后通過不斷往初始高度height中添加同縱列的新cell的height。

然后設置attributes.frame值。返回即可。

這時候我們運行代碼會發現,跑是跑的通,但是就是不能滑動

因為collectionView與tableView特性類似。我們單獨設置了每個cell的大小。所以此時系統並沒有幫忙獲取當前collectionView的contentSIze。主要內容的尺寸

這里我們通過方法,判斷布局Array數組中數值最大的值,獲取並且賦值給contentSize即可


結語:雖然與tableView類似,但是很多情況下collectionView的坑比TableView還多。滿頭疼的還是。

這里就暫時這么多,因為比較簡單就不多次闡述。

在下文筆不太好,大多寫給自己看留個底供自己查閱(自黑一下),水平欠佳也請看官們見諒

再接再厲吧!

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM