UICollectionViewLayoutのカスタマむズ。 芞術の名においお

みなさん、こんにちは



むントロ



私は、ロシアに最も近い倖囜の地方の囜の地方郜垂でios開発者ずしお働いおいたす。 箄1幎半前、囜は私に圌女に䜕かを負わせるこずを決めたした。特に、私は自分の人生に1幎、技術の䜎い仕事に1幎、垰囜の倢、家族ず仕事に1幎のおかげでした...-芁するに、私は軍隊に城兵されたした。 そしお、このケヌスの背埌で、私はどういうわけか、長い間埅ち望たれおいたUICollectionViewを含むすべおの機胜を備えたiOS 6のリリヌスを逃したした。

衣装、トレヌニング堎、チャヌタヌなどの魅力的なものを扱った埌、私は家に戻り、再び働き始めたした。もちろん、顧客がデザむナヌが「pinterest board」、぀たりUICollectionView自䜓ず呌んでいる圢匏でデヌタを衚瀺する必芁があるプロゞェクトあなたを埅たせたせんでした。



プロゞェクト



このプロゞェクトは、骚one品鑑定䌚瀟のiPadオヌクションカタログのようなものです。 ハブでの圌らの蚀及に察しお、圌らがどのように反応するかわからないので、リンク、デザむンレむアりト、たたは実際のスクリヌンショットは提䟛したせん。

アプリケヌションは、原則ずしお耇雑ではありたせん。デザむンの喜びから、私を少しだけ守ったのは、メむン画面の倖芳です。 これは、氎平スクロヌルを䌎う3぀の氎平列に配眮されたロットの画像のコレクションであるず想定されおいたした。 芁玠の高さず幅が固定された䞭倮の行画像の比率に応じお。 1行目ず3行目の芁玠の高さは、小さい方向にわずかに異なり、砎れた゚ッゞのようになりたす。 私のドラむな説明を文字で十分に理解しおいない堎合は、蚘事の最埌にあるスクリヌンショット最終結果のサンプルを芋おください。 たたはここ。



UICollectionViewFlowLayout



これは最小限の劎力を費やした最初の詊みでした。 これらの芁玠に遭遇したこずは䞀床もないので、Appleのクラスに察する私の垌望は非垞に匷かった。 たず、私は通垞、氎平方向の「むンタレストボヌド」の出力に単玔に焊点を圓おお、砎れた゚ッゞでスコアリングしたした。 残念ながら、ボヌルを滑らせるこずはできたせんでした。



ああ写真
図0-開発者は想定し、UICollectionViewFlowLayoutは


UICollectionViewFlowLayout



スクリヌンショットで2぀のこずがわかりたす。





RFQuiltLayout



私のグヌグルスキルが非垞に匱いか、 RFQuiltLayoutが私の堎合は唯䞀のタヌンキヌ゜リュヌションです。

このクラスは、デフォルトのセルサむズCGSizeを栌玍する倉数blockPixelsを䜿甚したす。 デリゲヌトメ゜ッド



- (CGSize) blockSizeForItemAtIndexPath:(NSIndexPath *)indexPath;
      
      





各セルのblockPixelsの乗数を返したす。 ぀たり、blockPixels = {100、100}、blockSizeForItemAtIndexPath = {2.2、0.8}の堎合、セルのサむズは{220、80}になりたす。

私の意芋では、それは少し奇劙なシステムです。blockPixelsを{1、1}に蚭定し、デリゲヌトメ゜ッドで芁玠に必芁なサむズを返したすが、この堎合の配眮アルゎリズムは15芁玠でも非垞に時間がかかり、配眮するのに100芁玠が必芁です蚈算胜力はiPadよりも急激です。 忍耐のアルゎリズムを分解するのに十分な時間がなかったので、遞択方法によっおblockPixelsの倀{20、20}を遞択したした。これにより、15個のセルで通垞のパフォヌマンスず良奜な配眮粟床が埗られたした。

匕き裂かれた゚ッゞを䜜成するには、少しトリックを䜿甚する必芁がありたした-実際、セルのサむズ自䜓には觊れたせんでした。配眮の段階でセルがどの行にあるかを芋぀けるこずができたせんでしたが、写真をむンストヌルするずきに行をチェックし、最初ず最埌の行の行の高さを枛らしたした写真。 画像は䞊䞋からわずかにトリミングされたした。 写真がトリミングの䞋にあるロットがポヌトレヌトであった堎合、人々は胞の冠ず底を倱い、ロットが䞭囜の眮物であった堎合、ドラゎンは王章なしで衚瀺され、ペルシャ絚毯で私の汚いハックは山を切りたした。 しかし、クラむアントは満足したした。぀たり、技術仕様がわずかに修正されるたで、私も喜んでいたした。 メむン画面の哀れな芁玠の代わりに、すべおのロットが衚瀺されおいるはずです。 すべお1500。



より倚くの芞術



絵画、花瓶、ヒスむの眮物、その他すべおの1500の画像が、劣らず矎しいです。 厳しい真実は、サヌドパヌティの゜リュヌションが私を救ったずいうこずではありたせんでした。 それでは、プレヌスメントマネヌゞャヌを䜜成したしょう。

予想されるように、ロシア語で情報を芋぀けるこずはできたせんでしたあたり期埅しおいたせんでしたが、䞍必芁で少なくずも少し圹に立぀英語の教垫は、䜕ずかしお埗点したせんでした怜玢結果の最初の10ペヌゞ。 結果ずしお、私のマニュアルは、実際には、 Appleのドキュメントず䞊蚘のRFQuiltLayoutコヌドでしたずころで、著者のBryce Reddに感謝の意を衚したい。



SKRaggyCollectionViewLayout



すぐにクラスの愚かな名前をおaびしたす。

最初に、デリゲヌトのプロトコルを定矩したした。



 @protocol SKRaggyCollectionViewLayoutDelegate <UICollectionViewDelegate> - (float)collectionLayout:(SKRaggyCollectionViewLayout*)layout preferredWidthForItemAtIndexPath:(NSIndexPath *)indexPath; @optional - (UIEdgeInsets)collectionLayout:(SKRaggyCollectionViewLayout*)layout edgeInsetsForItemAtIndexPath:(NSIndexPath *)indexPath; @end
      
      





デリゲヌトはセルの高さに圱響を䞎えるこずができないため、特定のセルで芋たい幅だけをマネヌゞャヌに枡したす。 さお、UIEdgeInsetsセルの内郚パディングを返すメ゜ッドは、それなしで。

そしおもちろん、行数を栌玍するプロパティは、3行だけでなく、散歩のように歩くこずであり、クラスをナニバヌサルにするこずです



 @property (nonatomic, assign) NSUInteger numberOfRows;
      
      





今実装。

Appleは、補足ビュヌや装食ビュヌなど、UICollectionViewの远加芁玠に煩わされたくない堎合は、少なくずも次のメ゜ッドをオヌバヌラむドする必芁があるず蚀っおいたす。



 - (CGSize)collectionViewContentSize; - (NSArray*)layoutAttributesForElementsInRect:(CGRect)bounds; - (UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath; - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;
      
      





埌者では、すべおが明確です-newBoundsがコレクションの珟圚の境界に䞀臎しない堎合はYESを返したす。

最初のメ゜ッドは埌で実装しやすいように残し、layoutAttributesForItemAtIndexPathメ゜ッドに進みたしょう。 その名前から明らかなように、各芁玠のUICollectionViewLayoutAttributesを蚈算する必芁がありたす。 UICollectionViewLayoutAttributesクラスのオブゞェクトには、セルのあらゆる皮類のかわいらしさを䜜成できるtransform3Dを含む、オブゞェクトの堎所に関する倚くの情報が含たれおいたすが、この堎合はたった1぀のフレヌムを実行できたす。



 - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { UIEdgeInsets insets = UIEdgeInsetsZero; if ([self.delegate respondsToSelector:@selector(collectionLayout:edgeInsetsForItemAtIndexPath:)]) { insets = [self.delegate collectionLayout:self edgeInsetsForItemAtIndexPath:indexPath]; } // Get saved frame and edge insets for given path and create attributes object with them CGRect frame = [self frameForIndexPath:indexPath]; UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; attributes.frame = UIEdgeInsetsInsetRect(frame, insets); return attributes; }
      
      





実際、興味深いこずはありたせん-デリゲヌトからUIEdgeInsetsを取埗したす。デリゲヌトが提䟛した堎合、frameForIndexPathメ゜ッドを䜿甚しおフレヌムを取埗し、受信したUIEdgeInsetsおよびCGRectで属性を䜜成しお返したす。 しかし、frameForIndexPathメ゜ッドでは、シャヌマニズムの䞻芁郚分は私から隠されおいたす。



 - (CGRect)frameForIndexPath:(NSIndexPath*)path { // if there is saved frame for given path, return it NSValue *v = [self.framesByIndexPath objectForKey:path]; if (v) return [v CGRectValue]; // Find X-coordinate and a row which are the closest to the collection left corner. A cell for this path should be placed here. int currentRow = 0; float currentX = MAXFLOAT; for (int i = 0; i < self.edgeXPositions.count; i++) { float x = [[self.edgeXPositions objectAtIndex:i] floatValue]; if (x < currentX) { currentRow = i; currentX = x; } } // Calculate cell frame values based on collection height, current row, currentX, the number of rows and delegate's preferredWidthForItemAtIndexPath: value // If variableFrontierHeight is YES this value will be adjusted for the first and last rows float maxH = self.collectionView.frame.size.height; float rowMaxH = maxH / self.numberOfRows; float x = currentX; float y = rowMaxH * currentRow; float w = [self.delegate collectionLayout:self preferredWidthForItemAtIndexPath:path]; float h = self.collectionView.frame.size.height / self.numberOfRows; float newH = h; // Adjust height of the frame if we need raggy style if (self.variableFrontierHeight) { if (currentRow == 0) { float space = arc4random() % self.randomFirstRowVar; if (self.prevWasTallFirst) { space += self.fixedFirstRowVar; } self.prevWasTallFirst = !self.prevWasTallFirst; y += space; newH -= space; } else if (currentRow == self.numberOfRows - 1) { float space = arc4random() % self.randomLastRowVar; if (self.prevWasTallLast) { space += self.fixedLastRowVar; } self.prevWasTallLast = !self.prevWasTallLast; newH -= space; } } // Assure that we have preferred height more than 1 h = h <= 1 ? 1.f : h; // Adjust frame width with new value of height to save cell's right proportions w = w * newH / h; // Save new calculated data ad return [self.edgeXPositions replaceObjectAtIndex:currentRow withObject:[NSNumber numberWithFloat:x + w]]; CGRect currentRect = CGRectMake(x, y, w, newH); NSValue *value = [NSValue valueWithCGRect:currentRect]; [self.indexPathsByFrame setObject:path forKey:value]; [self.framesByIndexPath setObject:value forKey:path]; return currentRect; }
      
      





私のパスタコヌドず半英語のコメントがあたり明確でない堎合は、疑䌌コヌドをより明確に混乱させようずしたす。

ここにいる
[以前に蚈算したフレヌムをindexPathキヌを䜿甚しお保存したNSMutableDictionaryを参照したす。勝利であるこずがわかった堎合、それ以䞊のこずはありたせん]



[蚈算が避けられない堎合、最埌のフレヌムの右境界がテヌブルの先頭に最も近い行を芋぀けたす-そこで、珟圚の芁玠を配眮する必芁がありたす各行のx座暙はNSMutableArray edgeXPositionsに栌玍されたす]



[ここで、X軞の行ず䜍眮、および芁玠のデリゲヌトが必芁ずする幅を知っおいるので、巊䞊隅の䜍眮を蚈算できたす。 コレクションの高さず行数がわかっおいるので、同時に芁玠の高さを蚈算したす]



[悪名高い「匕き裂かれた゚ッゞ」が必芁で、行が最初たたは最埌の堎合、蚈算された高さをわずかに枛らしたす]



[高さが1未満の堎合は安党に再生し、高さの枛少に比䟋しお幅を瞮小したす]



[将来の迅速なアクセスずメ゜ッドからの戻りのために、取埗した倀をindexPathsByFrameおよびframesByIndexPathディクショナリに保存したす]


ずころで、invalidateLayoutメ゜ッドでこれらのindexPathsByFrame、framesByIndexPath、what-else-is-cachedをすべおクリアするこずを忘れおはなりたせん。 圓然、[super invalidateLayout]が欠萜しおいたせん。



contentSizeに戻りたす。 明らかに、氎平スクロヌルの堎合、次のようになりたす。



 - (CGSize)collectionViewContentSize { return CGSizeMake(self.edgeX, self.collectionView.frame.size.height); }
      
      





ここで、edgeXは最も遠くにあるセルのX座暙です。 結局のずころ、すべおのセルがどのように配眮されおいるかはすでにわかっおいたす。 たたはわからない。 たたは、ただわかっおいたす...確かに、その䞭で[super prepareLayout]を呌び出すこずを忘れずにprepareLayoutメ゜ッドを再定矩し、各セルのフレヌムを蚈算する必芁がありたす



 - (void)prepareLayout { [super prepareLayout]; // calculate and save frames for all indexPaths. Unfortunately, we must do it for all cells to know content size of the collection for (int i = 0; i < [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:0]; i++) { NSIndexPath *path = [NSIndexPath indexPathForItem:i inSection:0]; [self frameForIndexPath:path]; } }
      
      





はい、数十䞇個のセルがある堎合、コレクションを急いでロヌドするこずはありたせんが、他に些现な方法はありたせん。

最埌に、最埌のメ゜ッドであるlayoutAttributesForElementsInRectをオヌバヌラむドしたす。 その䞭で、この領域に該圓するすべおの芁玠の属性を返す必芁がありたす。 コレクションがそのフレヌムのサむズにスクロヌルされるたびに呌び出されたす。 その埌、これはすべおキャッシュされおいるように芋えるため、メ゜ッドはcontentSize.width / frame.size.width回だけ呌び出されたす。

「額」ず呌ばれるものの私の実装各芁玠のフレヌムを調べ、それらがこの領域ず亀差する堎合、返された配列に远加したす。



 - (NSArray*)layoutAttributesForElementsInRect:(CGRect)bounds { if (CGRectEqualToRect(bounds, self.previousLayoutRect)) { return self.previousLayoutAttributes; } [self.previousLayoutAttributes removeAllObjects]; self.previousLayoutRect = bounds; NSArray *allFrames = self.framesByIndexPath.allValues; for (NSValue *frameValue in allFrames) { CGRect rect = [frameValue CGRectValue]; if (CGRectIntersectsRect(rect, bounds)) { [self.previousLayoutAttributes addObject:[self layoutAttributesForItemAtIndexPath:[self.indexPathsByFrame objectForKey:[NSValue valueWithCGRect:rect]]]]; } } return self.previousLayoutAttributes; }
      
      





絵があるはずでしたが、そうではありたせん
線集された投皿の倉曎を保存した埌、habr゚ンゞンが驚くほど毎回それを削陀するためです





すべおが正垞に機胜するずいう事実に察する幞犏感が過ぎ去った盎埌に、私の内郚の仕事䞭毒者は評決を出したした最適化するために しかし、ワヌカホリックよりも匷いこずが刀明したその内なる自己は、蚘憶の腞から「時期尚早な最適化」ずいうフレヌズを埗お、1䞇個の芁玠があっおもiPad 2でのテストでは速床䜎䞋が芋られなかったずいう事実の背埌に隠れお、しばらくの間最適化を延期するこずにしたした。



あずがき



誰かが興味を持ち、誰かが有甚であり、残りはこの投皿を読むだけの忍耐を持っおいたず思いたす。 ご枅聎ありがずうございたした。

最埌に、いく぀かのリンク





PS圌らはすでに最埌のネタバレには絵がないず私に曞いた。 同じ操䜜を3回行いたした。ミッシングリンクを挿入しお線集し、保存し、倉曎が発生したこずを確認し、ペヌゞを閉じ、再床開いお呪いをかけたした-画像が再び消えたした。 さらに、同じ状況に぀ながるはずのリンクOr right hereは 、その機胜的矩務を果たすのをやめたした。 トラブル。

念のため、この䞍正なリンクを単にテキストでここに残したす

habr.habrastorage.org/post_images/0e1/0c7/be4/0e10c7be44690901268d5dfa9e532d0e.png




All Articles