I have a UICollectionView. On certain devices, the cells hug the edges of the device, while having a big gap in the center. I have tried changing insets and minimum spacing, both of which did not work. I created this in the storyboard so I have no code related to formatting. How would I make the space between the cells equal to the space between the outer cells and the margins so there is not a huge gap in the middle? Thanks.
Edges of image are the exact edge of the device
Assuming that you're using UICollectionViewFlowLayout and your cell has fixed size:
Custom cell code: (I added some shadow and rounded corners, ignore it)
import UIKit
class CollectionViewCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
layer.shadowColor = UIColor.lightGray.cgColor
layer.shadowOffset = CGSize(width: 0, height: 2.0)
layer.shadowRadius = 5.0
layer.shadowOpacity = 1.0
layer.masksToBounds = false
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: contentView.layer.cornerRadius).cgPath
layer.backgroundColor = UIColor.clear.cgColor
contentView.layer.masksToBounds = true
layer.cornerRadius = 10
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
View controller code:
import UIKit
private let reuseIdentifier = "Cell"
class CollectionViewController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
/// Class registration
collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
/// Expected cell size
let cellSize = CGSize(width: 120, height: 110)
/// Number of columns you need
let numColumns: CGFloat = 2
/// Interitem space calculation
let interitemSpace = ( UIScreen.main.bounds.width - numColumns * cellSize.width ) / (numColumns + 1)
/// Setting content insets for side margins
collectionView.contentInset = UIEdgeInsets(top: 10, left: interitemSpace, bottom: 10, right: interitemSpace)
/// Telling the layout which cell size it has
(self.collectionView.collectionViewLayout as! UICollectionViewFlowLayout).itemSize = cellSize
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
cell.backgroundColor = .white
return cell
}
}
Here is the result:
Related
I have a collection view which scrolls horizontally much like a carousel.
The height and width of all the cells should be equal to the cell which has the longest text.
It doesn't do this however and instead expands it's width depending on the length of the text which results into different widths of cells though the height remains constant.
max number of lines for the text is only 4.
I tried other answers in SO but none of them worked for me.
Here is my code, I am using SnapKit for constraints.
View Controller
import UIKit
import SnapKit
class ViewController: UIViewController {
let viewModel: [SeatViewModel] = [.init(text: "single text"), .init(text: "slightly longer text."), .init(text: "very very very very long long text")]
var collectionViewSize: CGFloat = 10
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.estimatedItemSize = CGSize(width: 200, height: 100)
let cv = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
cv.register(SeatCardCell.self, forCellWithReuseIdentifier: "SeatCardCell")
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
make.top.equalToSuperview()
make.leading.equalToSuperview()
make.trailing.equalToSuperview()
make.height.equalTo(100)
}
collectionView.dataSource = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.frame = view.bounds
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let collectioCell = collectionView.dequeueReusableCell(withReuseIdentifier: "SeatCardCell", for: indexPath) as? SeatCardCell else { fatalError() }
collectioCell.backgroundColor = [UIColor.green, .red, .black, .brown, .yellow].randomElement()
collectioCell.viewModel = viewModel[indexPath.row]
return collectioCell
}
}
Cell and View Model
struct SeatViewModel {
let text: String
}
class SeatCardCell: UICollectionViewCell {
var viewModel: SeatViewModel? {
didSet {
configure()
}
}
// MARK: - Properties
let seatImageView = UIImageView(image: nil)
let seatLabel: CustomLabel = {
let label = CustomLabel()
return label
}()
// MARK: - Lifecycle
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func configure() {
guard let viewModel = viewModel else { return }
seatLabel.text = viewModel.text
seatLabel.backgroundColor = .cyan
seatLabel.sizeToFit()
seatImageView.image = .strokedCheckmark
let stackView = UIStackView(arrangedSubviews: [seatImageView, seatLabel])
stackView.spacing = 0
stackView.alignment = .center
addSubview(stackView)
seatImageView.snp.makeConstraints { make in
make.size.equalTo(40)
}
stackView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
// MARK: - CUSTOM TITLE LABEL
class CustomLabel: UILabel {
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
self.textColor = .darkGray
self.textAlignment = .left
self.numberOfLines = 4
}
}
Assuming you want all of the cells to be the same size...
One possible solution would be to use NSString.boundingRect. In a place where you only need to calculate this once, for example in viewDidLoad, determine the bounding rect of the largest text:
var maxTextWidth: CGFloat = 0
override func viewDidLoad() {
// All the other code you currently have, plus:
let boundingWidth = 9999 // Something arbitrarily large
let boundingHeight = 34 // Make this the height of however many lines you want
let targetSize = CGSize(width: boundingWidth, height: boundingHeight)
let attributes = [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 17)] // Put any other necessary attributes to accurately calculate the size of the text
for vm in viewModel {
let width = NSString(string: vm.text).boundingRect(with: estimatedSize, options: .usesLineFragmentOrigin, attributes: attributes, context: nil).width
if width > maxTextWidth {
maxTextWidthWidth = width
}
}
}
And then you also need to provide this size to the collection view using:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = maxTextWidth + // Any padding you want
let height = // Height of text plus any padding
return CGSize(width: width, height: height)
}
More documentation on boundingRect can be found here
I have a working list with UICollectionViewController (but the issue might be relevant for bare UICollectionView, I have not tested). It is working like Image below, but the issue is, the width varies contents.
I want the width to fill the width and let the height calculate itself based on contents. So far I have not been able to put a coherent solution. I have tried to override FlowLayout but then I have to pass the height of the cell which I don't know at that moment.
What is the solution that iOS devs use to solve such a problem. It looks to me common sense thing but apple seems to have complicated this one for no reason.
Swift 5 example of full width cells when standard UICollectionViewFlowLayout is used.
Idea behind:
We are informing UICollectionViewFlowLayout that we want to have self-sizing cells.
Then we passing current collection view width to each cell.
Cell overrides preferredLayoutAttributesFitting(...) method. Then it performs desired size calculation using passed width. Desired size calculated using systemLayoutSizeFitting(width: ...).
class MyVievControler: UICollectionViewController {
enum CellId: String {
case red, green, blue
}
let items = repeatElement([CellId.red, .green, .blue], count: 40).reduce(into: []) { $0 += $1 }
init() {
let layout = UICollectionViewFlowLayout()
// We want self-sizing cells
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
super.init(collectionViewLayout: layout)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cellId = items[indexPath.row]
switch cellId {
case .red:
let cell = collectionView.dequeueReusableCell(MyCellRed.self, indexPath: indexPath)
cell.contentWidth = collectionView.bounds.width
cell.text = indexPath.item % 2 == 0 ? StubObject().text.x128 : StubObject().text.x32
return cell
case .green:
let cell = collectionView.dequeueReusableCell(MyCellGreen.self, indexPath: indexPath)
cell.contentWidth = collectionView.bounds.width
return cell
case .blue:
let cell = collectionView.dequeueReusableCell(MyCellBlue.self, indexPath: indexPath)
cell.contentWidth = collectionView.bounds.width
return cell
}
}
}
private class MyCellRed: UICollectionViewCell {
var contentWidth: CGFloat = 100
private lazy var label = UILabel()
var text: String? {
didSet {
label.text = text
}
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
var newFrame = layoutAttributes.frame
// For better performance this size need to be cached as long as Cell content/model is not changed.
let desiredSize = systemLayoutSizeFitting(width: contentWidth, verticalFitting: .fittingSizeLevel)
newFrame.size = CGSize(width: contentWidth, height: desiredSize.height)
layoutAttributes.frame = newFrame
log.debug("Red: Calculated frame: \(newFrame). Desired size: \(desiredSize)")
return layoutAttributes
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundView = UIView(backgroundColor: .red)
layer.setBorder(width: 0.5)
contentView.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
anchor.pin.toBounds(insets: 8, label).activate()
label.layer.borderWidth = 1
label.numberOfLines = 0
label.textColor = .white
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
private class MyCellGreen: UICollectionViewCell {
var contentWidth: CGFloat = 100
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
var newFrame = layoutAttributes.frame
newFrame.size = CGSize(width: contentWidth, height: 40)
layoutAttributes.frame = newFrame
log.debug("Green: Calculated frame: \(newFrame)")
return layoutAttributes
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundView = UIView(backgroundColor: .green)
layer.setBorder(width: 0.5)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
private class MyCellBlue: UICollectionViewCell {
var contentWidth: CGFloat = 100
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
var newFrame = layoutAttributes.frame
newFrame.size = CGSize(width: contentWidth, height: 100)
layoutAttributes.frame = newFrame
log.debug("Blue: Calculated frame: \(newFrame)")
return layoutAttributes
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundView = UIView(backgroundColor: .blue)
layer.setBorder(width: 0.5)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Result:
After reading this Article (Thanks to #DonMag), I was able to achieve what I wanted. So basically you extend the Cell class, and do some changes to CollectionView and its flowLayout. Here are the classes
FullWidthCollectionViewCell: All your cells in need of full width should extend it instead of UICollectionViewCell
class FullWidthCollectionViewCell: UICollectionViewCell
{
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
var modifiedTargetSize = targetSize
modifiedTargetSize.height = CGFloat.greatestFiniteMagnitude
let size = super.systemLayoutSizeFitting(
modifiedTargetSize,
withHorizontalFittingPriority: .required,
verticalFittingPriority: .fittingSizeLevel
)
return size
}
}
Add extension to UICollectionView class. I have single file for all my view extensions but you can put it in the same file as CollectionView code
extension UICollectionView {
var widestCellWidth: CGFloat {
let insets = contentInset.left + contentInset.right
return bounds.width - insets
}
}
Finally in your CollectionView code. In my case I was using UICollectionViewController so All is set for me. If you are using UICollectionView as view in some custom UIViewController then you must adjust the code accordingly
class MyCollectionViewController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
let layout = collectionView.collectionViewLayout
if let flowLayout = layout as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = CGSize(
width: collectionView.widestCellWidth,
// Make the height a reasonable estimate to
// ensure the scroll bar remains smooth
height: 200
)
}
}
//Delegate and other methods here
}
That is all to enjoy. Again thanks to #DonMag for the link!
I am trying to make custom Image Slider with collections view. I want to it to be reusable. So I made separate custom class where all collectionView stuff. and then call that class from UIViewController as shown in code below. And my UICollectonViewCell only contains imageView.
Problem is that in second cell. imageView is not being added and on third cell, it also flickers. I tried to debug these issues but could not.
ImageSlider class and UICollectionViewCell class at end end, with collection view stuff:
class ImageSlider: UIView {
var imgArr = [UIImage(named: "one.jpg"), UIImage(named: "3.jpg"), UIImage(named: "two.jpg"), UIImage(named: "4.jpg")]
var sliderCollection : UICollectionView = {
let widthRatio : Float = 16.0
let heightRatio : Float = 9.0
let collecionWidth = UIScreen.main.bounds.width - 30
let layout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: CGRect(x: 15, y: 100, width: collecionWidth, height: CGFloat((Float(collecionWidth)*heightRatio)/widthRatio)), collectionViewLayout: layout)
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
collectionView.backgroundColor = .systemOrange
collectionView.isPagingEnabled = true
// collectionView.isScrollEnabled = true
collectionView.register(ImageSliderCell.self, forCellWithReuseIdentifier: "ImageSliderCell")
return collectionView
}()
}
extension ImageSlider: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imgArr.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageSliderCell", for: indexPath) as! ImageSliderCell
cell.imgView.image = imgArr[indexPath.item]
// cell.imgView.contentMode = .scaleAspectFit
print("cell frame : ", "(\(cell.frame.width), \(cell.frame.height)")
print("imgView frame : ", "(\(cell.imgView.frame.width), \(cell.imgView.frame.height)")
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
}
class ImageSliderCell: UICollectionViewCell {
var imgView = UIImageView()
// override func awakeFromNib() {
// self.addSubview(imgView)
// }
override init(frame: CGRect) {
super.init(frame: frame)
imgView.frame = frame
self.addSubview(imgView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is ViewController, where I am calling ImageSlider() class.
class ImageSliderVC: UIViewController, UICollectionViewDelegate {
let imageSlider = ImageSlider()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(imageSlider.sliderCollection)
imageSlider.sliderCollection.delegate = imageSlider
imageSlider.sliderCollection.dataSource = imageSlider
imageSlider.sliderCollection.reloadData()
}
}
It looks like it does not work without constrains because UICollectionViewCell could be created with zero frame and it translated to imageView inside the cell. You need to add constrains to imageView to make it visible.
extension UIView {
func centerX(inView view: UIView, constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: constant).isActive = true
}
func centerY(inView view: UIView, constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: constant).isActive = true
}
func setDimensions(height: CGFloat, width: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
widthAnchor.constraint(equalToConstant: width).isActive = true
}
func setHeight(_ height: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
class ImageSliderCell: UICollectionViewCell {
var imgView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(imgView)
// not sure about the right size of image ...
imgView.setDimensions(height: 100.0, width: 100.0)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
See the following boarder of the each items (Border Spacing)
With collection view Header I am able to achieve the following out put but stuck at how to put separator inside the uicollection view. also the number of cell inside the row is dynamic.
and last row should not the the bottom separator
any help is really appreciate..
For Achieving the following layout i only use the collection view with section header
I have already done the following output
All section are collapsed
clicked on particular secttion
Just the separator part is remaining for each expanded section
I cant figure out how can i achieve the same using decoration view.
You could do it like this,
Create two different types of decoration view, one for vertical line which is at the center of the collection view and other being horizontal which appear below two cell
Vertical decoration view is created only once for the whole view, while horizontal decoration view is created for a pair of two which appears below each row. For the last row, dont create the decoration view.
subclass UICollectionViewFlowLayout, and override override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? and return appropriate decoration view.
Here is how the layout looks for me,
And here is the code used for that,
CollectionViewController,
class ViewController: UICollectionViewController {
let images = ["Apple", "Banana", "Grapes", "Mango", "Orange", "Strawberry"]
init() {
let collectionViewLayout = DecoratedFlowLayout()
collectionViewLayout.register(HorizontalLineDecorationView.self,
forDecorationViewOfKind: HorizontalLineDecorationView.decorationViewKind)
collectionViewLayout.register(VerticalLineDecorationView.self,
forDecorationViewOfKind: VerticalLineDecorationView.decorationViewKind)
super.init(collectionViewLayout: collectionViewLayout)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = UIColor.white
collectionView?.register(CollectionViewCell.self,
forCellWithReuseIdentifier: CollectionViewCell.CellIdentifier)
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectionViewCell.CellIdentifier,
for: indexPath) as! CollectionViewCell
let name = images[indexPath.item]
cell.imageView.image = UIImage(named: "\(name).png")
cell.label.text = name
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return .zero
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10 + DecoratedFlowLayout.horizontalLineWidth
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = (collectionView.bounds.size.width - DecoratedFlowLayout.verticalLineWidth) * 0.5
return CGSize(width: width,
height: width + 20)
}
}
CollectionViewCell,
class CollectionViewCell: UICollectionViewCell {
static let CellIdentifier = "CollectionViewCellIdentifier"
var imageView: UIImageView!
var label: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
createViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func createViews() {
imageView = UIImageView(frame: .zero)
imageView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(imageView)
label = UILabel(frame: .zero)
label.font = UIFont.systemFont(ofSize: 20)
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(label)
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
imageView.leftAnchor.constraint(equalTo: contentView.leftAnchor),
imageView.rightAnchor.constraint(equalTo: contentView.rightAnchor),
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
label.leftAnchor.constraint(equalTo: contentView.leftAnchor),
label.rightAnchor.constraint(equalTo: contentView.rightAnchor),
label.topAnchor.constraint(equalTo: imageView.bottomAnchor),
label.heightAnchor.constraint(equalToConstant: 20)
])
}
}
DecoratedFlowLayout,
class DecoratedFlowLayout: UICollectionViewFlowLayout {
static let verticalLineWidth: CGFloat = 20
static let horizontalLineWidth: CGFloat = 20
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
minimumLineSpacing = 40 // should be equal to or greater than horizontalLineWidth
}
override init() {
super.init()
minimumLineSpacing = 40
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let attributes = super.layoutAttributesForElements(in: rect) else {
return nil
}
var attributesCopy: [UICollectionViewLayoutAttributes] = []
for attribute in attributes {
attributesCopy += [attribute]
let indexPath = attribute.indexPath
if collectionView!.numberOfItems(inSection: indexPath.section) == 0 {
continue
}
let firstCell = IndexPath(item: 0,
section: indexPath.section)
let lastCell = IndexPath(item: collectionView!.numberOfItems(inSection: indexPath.section) - 1,
section: indexPath.section)
if let attributeForFirstItem = layoutAttributesForItem(at: firstCell),
let attributeForLastItem = layoutAttributesForItem(at: lastCell) {
let verticalLineDecorationView = UICollectionViewLayoutAttributes(forDecorationViewOfKind: VerticalLineDecorationView.decorationViewKind,
with: IndexPath(item: 0, section: indexPath.section))
let firstFrame = attributeForFirstItem.frame
let lastFrame = attributeForLastItem.frame
let frame = CGRect(x: collectionView!.bounds.midX - DecoratedFlowLayout.verticalLineWidth * 0.5,
y: firstFrame.minY,
width: DecoratedFlowLayout.verticalLineWidth,
height: lastFrame.maxY - firstFrame.minY)
verticalLineDecorationView.frame = frame
attributesCopy += [verticalLineDecorationView]
}
let contains = attributesCopy.contains { layoutAttribute in
layoutAttribute.indexPath == indexPath
&& layoutAttribute.representedElementKind == HorizontalLineDecorationView.decorationViewKind
}
let numberOfItemsInSection = collectionView!.numberOfItems(inSection: indexPath.section)
if indexPath.item % 2 == 0 && !contains && indexPath.item < numberOfItemsInSection - 2 {
let horizontalAttribute = UICollectionViewLayoutAttributes(forDecorationViewOfKind: HorizontalLineDecorationView.decorationViewKind,
with: indexPath)
let frame = CGRect(x: attribute.frame.minX,
y: attribute.frame.maxY + (minimumLineSpacing - DecoratedFlowLayout.horizontalLineWidth) * 0.5,
width: collectionView!.bounds.width,
height: DecoratedFlowLayout.horizontalLineWidth)
horizontalAttribute.frame = frame
attributesCopy += [horizontalAttribute]
}
}
return attributesCopy
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}
And decoration views
class VerticalLineDecorationView: UICollectionReusableView {
static let decorationViewKind = "VerticalLineDecorationView"
let verticalInset: CGFloat = 40
let lineWidth: CGFloat = 4.0
let lineView = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
lineView.backgroundColor = .black
addSubview(lineView)
lineView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
lineView.widthAnchor.constraint(equalToConstant: lineWidth),
lineView.topAnchor.constraint(equalTo: topAnchor, constant: verticalInset),
lineView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -verticalInset),
lineView.centerXAnchor.constraint(equalTo: centerXAnchor),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class HorizontalLineDecorationView: UICollectionReusableView {
let horizontalInset: CGFloat = 20
let lineWidth: CGFloat = 4.0
static let decorationViewKind = "HorizontalLineDecorationView"
let lineView = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
lineView.backgroundColor = .black
addSubview(lineView)
lineView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
lineView.heightAnchor.constraint(equalToConstant: lineWidth),
lineView.leftAnchor.constraint(equalTo: leftAnchor, constant: horizontalInset),
lineView.rightAnchor.constraint(equalTo: rightAnchor, constant: -horizontalInset),
lineView.centerYAnchor.constraint(equalTo: centerYAnchor),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I hope you can make use of it and tweak values to suit your own need. Some of the calculations might make sense to change to some level. But, I hope you get the idea about how this could be achieved.
If the number of columns is always 2, why don't you add three views(separators) to a cell, two on the sides and one on the bottom. For the cells on the right side, hide the right most separator and vice versa for cells on the left.
Hide the separator on the bottom for cells on the last row.
It is a bit of a hack but much simpler to implement.
customize UICollectionviewcell (I think you might already have done that),
Now on custom UICollectionviewcell put two UIView -
one UIView at the right of the cell with 1 or 2 point width(as per
required) and height equals to cell height, give a black background
color to UIView.
Another UIView at the bottom of the cell with 1 or 2 point height(as per required) and this time width equals to cell width, give a black background color to UIView.
and adjust the spaces.
I thing this trick will work for you.
I've got a custom subclass of UICollectionViewFlowLayout, and I'm trying to update the cell size to always be the width of the screen. The cells aren't updating properly when I rotate to landscape. Here's my subclass:
// MARK: - Constants
private let EdgeInset: CGFloat = 10
private let CellSpacing: CGFloat = 10
private let LineSpacing: CGFloat = 10
private let CellHeight: CGFloat = 50
// MARK: - FullWidthLayout
class FullWidthLayout: UICollectionViewFlowLayout {
override init() {
super.init()
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
minimumLineSpacing = LineSpacing
minimumInteritemSpacing = CellSpacing
sectionInset = UIEdgeInsets(top: EdgeInset, left: EdgeInset, bottom: EdgeInset, right: EdgeInset)
}
}
// MARK: Layout Methods
extension FullWidthLayout {
override func prepare() {
super.prepare()
if let view = collectionView {
let availableWidth = view.bounds.width - EdgeInset * 2
itemSize = CGSize(width: availableWidth, height: CellHeight)
print("Item size: \(itemSize)")
}
}
}
Here's the output in the console:
Item size: (300.0, 50.0)
Item size: (548.0, 50.0)
And here's what's happening in the simulator:
The item size seems to be set correctly in the prepare() method, but the cell doesn't update in the collection view. Exceptions are thrown if you start in landscape and move to portrait.
Is there something I'm missing? Is the base UICollectionViewFlowLayout ignoring the itemSize in some cases?
Here's a link to the project on GitHub: CollectionViewRotate
Add this in your ViewController
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.size.width/ 2 - 10, height: 100) // chnage as per your need ...10 is the space between cell
}
override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
myCollectionView!.reloadData()
}