This is the continuation of the previous part. It is mandatory that you went through it. We will use the same project that we created previously. Build and run the starter project, you will see the vertical collection View and the example cover here is inspired by Raywenderlich
Our first objective is, the cell at the center become more prominent than others. Since the layout in this case also the grid based vertical layout we still need
UICollectionViewFlowLayout but we need to add some extra decorative logic to make the center cell more prominent for that we need to subclass
UICollectionViewFlowLayout as we discussed in the previous blog. We subclassed
UICollectionViewFlowLayout when we need grid line based behaviour but with custom behaviour
As shown in Figure 2 , we did a few things
- Created a
- This class responsibilities is to layout object and as you can see we removed
UICollectionViewDelegateFlowLayoutmethod as well, though you can still use these method to tell the size and other properties , but to put this logic in our custom class for two reason 1. Reusability (if you want this type of layout in other view you can assign only this layout and it will works as expected) 2. Performance (since our item size is hardcoded , we assign only one time instead of every time cell is render)
- Override this method to calculate the position and size of every cell, as well as the total dimensions for entire layout
- Subclasses should always call super if they override.
- After this method return ,
UICollectionViewdecided it’s scrolling content size , if you don’t implement
sizeForItemAthave overwrite the
- You will see
prepare()method usage when we actually subclass
- This method only calls once or whenever the layout is invalidated and as you can see in Figure 3, this method only calls one time and in our device we scrolled to the bottom. The
isSetupflag we added to protect invalidated case
- We will see this in details when we will be working with
UICollectionViewLayoutdirectly, though in our case we used to put layout logic in one class
- A layout object that manages the layout-related attributes for a given item in a collection view.
- Layout objects create instances of this class when asked to do so by the collection view. In turn, the collection view uses the layout information to position cells and supplementary views inside its bounds.
let’s understand this in deeper with figure 4 and 5, Build and run the application , few things to note
layoutAttributesForElementsis called once from each visible frame and asks us to change attributes or return the attributes that is only created by UICollectionViewFlowLayout. You don’t need to overwrite this method if you are not changing any attributes
- As shown we can see it asks us to give cells attributes from index 0 to 3 since only four cells are visible
- Let’s consider information it holds ,
attributes!holds all the information to place cell at index
0 . frame = (87.5, 0.0, 200.0, 200.0), size(200,200), alpha = 1you can validate with the Figure as well.
- You can subclass
UICollectionViewLayoutAttributesand define whatever properties you want to store the additional layout data.
As shown in Figure 6 we safely update the attributes, Safely means we didn’t remove
layoutAttributesForElements that is created by Layout object . Instead we first copy all attributes and update then return a new copy much like functional programming .
You are thinking 🤔 we can do this when displaying the cell but when displaying the cell we don’t have any information related to which cell currently is in center . The information we only get in this method
Some people thinking, 🤔 we can do this by implementing
UIScrollView delegate , and the answer is yes we can do there but if we give the responsibility to one class they should do this.
Some people thinking, 🤔 , we are changing
alpha value here , and the responsibility of that class to hold layout information not to do any UI stuff so we are breaking SRP, yes But there are other people as well who says If I change alpa value here and If I need this logic in other collection View only I just give them this layout not to duplicate code of alpha value in other collection view . In next section I will cover how to do UI stuff in
There is also an alternative approach as well , where we will do this when displaying the cell , for that we need to add some new properties on layout object that keeps track which cell is currently in center
As shown in Figure 7 we did few things
- Created subclass of
- Added additional property
isCenterto track which cell is is center
- Declared your custom attribute subclass class name by
override class var layoutAttributesClass: AnyClassin your
UICollectionViewLayoutclass. The system calls this class method to see if there is a custom class to be supplied when you use the factory method for instantiating/dequeuing layout attribute objects. If you don’t provide this class application will crash
- Implement custom property, be sure to implement an override for -
UICollectionViewwill lose any custom values you have applied to your custom collection view object. Further you need to override
func isEqual(_ object: Any?) -> Boolas well
layoutAttributesForElementsmethod we assign value on the basis of layout information we got at that point . We access this property by cast it as MyAttributeClass.
Note : ignore force unwrap things , you are noticing everywhere in this blog series
As shown in Figure 8 we finally changing alpha value on our custom cell. Few things
- Classes that want to support custom layout attributes specific to a given
UICollectionViewLayoutsubclass can apply them here.
applyLayoutAttributes:is then called after the view is added to the collection view and just before the view is returned from the reuse queue.
- Note that
-applyLayoutAttributes: is only called when attributes change, as defined by -isEqual:.
- This function returns an array of layout attributes for all the cells and views within the collection view visible rectangle .
- You step through these attributes of the cell and can modify depending on your layout information you get
- You are familiar this methods as we used this method perviously
- You only need to override this method when your intend is to change some attributes
Focus On Objective 1
UICollectionViewLayoutAttributes custom class . we need to do some math that we reduce alpha and scale on the basis of cell distance from the center of CollectionView and the alpha is reduces minimum to 0.5. If the cell is is center it’s alpha will be 1.0
Step 1: Find the Distance between Center of Cell and The CollectionView Center
As shown in Figure 9 we find the distance
If you want to see How this method will find the distance between center let’s look Figure 10 and consider Cell3, Here are the points
collectionCenterwe get the collectionView center , By dividing its visible frame height / 2, Let’ see collection View height = 400 , so the center is always be the
- Since each cell height is
200, previous 400 space is filled by the cell 1 and 2 and since user scrolls to the cell 3 it’s content size position is from 400 to 600, plz ignore line spacing and status bar inset so its center.y value will be at
center_of_cell_3 = 500
- Since user scrolls Cell 1 and cell 2 completely and they are not in the frame so the content offset will be
content_offset = 400,(Cell1 height + Cell 2 height )
- So normalized center of cell 3 with respect to current visible area will be 100 (
center_of_cell_3 — content_offset)
- If cell 3 center is at 200 you can say that cell 3 is completely center on the frame , but the distance of cell 3 center with the frame center is 100 (
normalized center — collectionCenter) . Note we take absolute value because
normalized centercan be greater than
Step 2: If the distance is greater or equal to the cell height , then take cell height
let distance = min(abs(collectionCenter — normalizedCenter), maxDistance)
Step 3: Find ratio
Distance = 0 , ratio = 1
Distance = 200, ratio = 0
let ratio = (maxDistance — distance) / maxDistance
Step 4: Find alpha and scale Value
ratio = 0 , alpha = 0.5, scale 0.5
ratio =0.5 , alpha = 0.75, scale 0.75
ratio =1 , alpha = 1, scale 1
let standardItemAlpha: CGFloat = 0.5let standardItemScale: CGFloat = 0.5let alpha = ratio * (1 — standardItemAlpha) + standardItemAlphalet scale = ratio * (1 — standardItemScale) + standardItemScale
Objective 1 : Cell Closest to Center will have alpha and scale value 1
As shown in Figure 11 since cell 2 is closest to center it has hight alpha and scale greater than 0.5
But we have one problem when you scroll layout will not updated, because as we scroll , the collection view will need to update it’s layout . To get the layout updated we need to override
shouldInvalidateLayout method. return YES to cause the collection view to requery the layout for geometry information. One thing to note after we implemented
shouldInvalidateLayout , when you scroll it invalidated the layout and prepare will call , now at this point out flag will work
Objective 2 we want at a time only one cell will be prominent , like in this case since cell 3 is closest to the Frame center it should be in center. So we need to snap the cell to center which is closest
targetContentOffsetForProposedContentOffsetreturn a point at which to stop scrolling
- It has
proposedContentOffsetparameter which is point at which scrolling naturally stops
- By overriding this method we can set scrolling to snap to specific boundary
- We did a calculation after user naturally stops, and from that location who is the closest cell , and what additional offset we need further to move it to the center
- Let’s say
collection view center is 333.5
- User scroll and
proposedContentOffset is 11.5
proposed center = 345
proposed center — collection view center
As shown in Figure 15 we are creating tag cell , we have collection view in blue having
400 height, and cell in red and you can see we don’t specify cell width and height at all and collection view used default
height which is
50 as shown in Figure 16,
Problem : we want cell to use it’s intrinsic content size or in short expand on the basis of it’s content (Dynamic Size)
By default UICollectionViewFlowLayout uses itemSize property for defining the sizes of the cells. Another way is to use UICollectionViewDelegateFlowLayout and supplying item sizes by implementing collectionView(_:layout:sizeForItemAt:). Third way is to let auto layout for defining the size of the cell
Dynamic cells sizing is an opt-in feature of
UICollectionViewFlowLayout, which could be enabled by setting
estimatedItemSize property to a non-zero value. Once estimated size has been set, the flow layout computes a first approximation of cells arrangement. The layout is re-calculated when the updated attributes are received. Hence it boosts performance if estimated size is close to the actual one.
Set this constant as the value for the estimatedItemSize property to enable self-sizing cells for your collection view. This is a non-zero, placeholder value that tells the collection view to query each cell for its actual size using the cell’s preferredLayoutAttributesFitting(_:) method.
Providing an estimated cell size can improve the performance of the collection view when the cells adjust their size dynamically. The estimated value lets the collection view defer some calculations to determine the actual size of its content. Cells that aren’t onscreen are assumed to be the estimated height.The default value of this property is
CGSizeZero. Setting it to any other value, like
automaticSize, causes the collection view to query each cell for its actual size using the cell’s
preferredLayoutAttributesFitting(_:) method.If all of your cells are the same size, use the
itemSize property, instead of this property, to specify the cell size instead.
As shown in Figure 18, Four Item text exceeding from device boundary
preferredMaxLayoutWidth (The preferred maximum width, in points, for a multiline label.)
This property affects the size of the label when the system applies layout constraints to it. During layout, if the text extends beyond the width specified by this property, the additional text flows to one or more new lines, increasing the height of the label.
Now we run into another we want collection view to take heigh as per content , now you can see we don’t want scrolling , In short we want dynamic height of collection view on the basis of it’s content
As clear from view debugger collection view height = 100 and collection view
content size = 154
Content Size represent the width and height of all the content, not just the content that is currently visible. The collection view uses this information to configure its own content size for scrolling purposes. if content size > size = perform scrolling
154 > 100 == it will perform scrolling
Intrinsic content size
Most views have an intrinsic content size, which refers to the amount of space the view needs for its content to appear in an ideal state. For example, the intrinsic content size of a
UILabel will be the size of the text it contains using whatever font you have configured it to use.
Intrinsic content sizes are important because they allow views to have a natural width and height without us forcing one. For Auto Layout to work it must know where each view is positioned precisely: its X, Y, width, and height values. With intrinsic content size we can say “place this button 20 points from the top and center it horizontally” and that’s enough to form a complete layout — Auto Layout can calculate the rest based on the button’s intrinsic size.
we need the
UITableViewCell to adjust its height dynamically according to its content. Also, the
UICollectionView must be such that all the cells are displayed in one go, i.e. no scrolling allowed. Our strategy would be we will enable autolayout in cell to calculate cell height and cell will use the intrinsic size of collection view , for collection view intrinsic size we will inject the actual content size in collection view intrinsic size
Step 1 Add Table View
Step 2 UITableViewCell With Collection View
Step 3 Collection View Data Source
Collection view cell is Red color
Run the application and you can see cell height not taking collections view content size, In collection view you need to scroll to see the content
Dynamically calculating the
collectionView’s height as per its
contentSize is simply a 3 step process.
Dynamically calculating the
collectionView’s height as per its
contentSize is simply a 3 step process.
UICollectionViewand override its
intrinsicContentSize, The above code will invalidate the
intrinsicContentSizeand will use the actual
collectionView. The above code takes into consideration the
custom layoutas well.
- . Now, set
collectionView’sclass and isScrollEnabled = false
- One last thing, for the changes to take effect: you need to call
collectionView, after reloading
As shown in Figure 24 it is working fine but tableview view cell is taking more extra space
In the next part we will subclass
UICollectionViewLayout and build a
proposedContentOffset The proposed point (in the collection view's content view) at which to stop scrolling. This is…
Custom Collection View Layouts
Introduced in iOS 6, UICollectionView is the new star among view classes in UIKit. It shares its API design with…
Layout objects create instances of this class when asked to do so by the collection view. In turn, the collection view…
Learn all about how to customize the layout of collection views in iOS. You'll start with the basics, such as…