Scale image to fit - iOS - ios

I have a UICollectionView that displays 12 images. The images are not fitting in the cells, they are being cropped, but I want them to fit in the cells without being cropped.
The code that should make the image scale to fit is:
cell.imageView.contentMode = UIViewContentMode.ScaleAspectFit
cell.imageView.image = UIImage(named: name)
Here is my code for the whole viewController class and screenshot:
import UIKit
class DressingRoomViewController:
UIViewController,
UICollectionViewDelegateFlowLayout,
UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
let identifier = "cellIdentifier"
let dataSource = DataSource()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
}
override func viewDidAppear(animated: Bool) {
// Notes on the equation to get the cell size:
// cells per row = 6
// cell spacing = 10
// collectionView.layout.inset = 20 (10 left, 10 right)
let cellSize = (collectionView.collectionViewLayout
.collectionViewContentSize().width - 20) / 6 - 10
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets( top: 20,
left: 10,
bottom: 10,
right: 10)
layout.itemSize = CGSize(width: cellSize, height: cellSize)
collectionView.collectionViewLayout = layout
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepareForSegue( segue: UIStoryboardSegue,
sender: AnyObject?) {
if (segue.identifier == "dressingRoom2MyOutfits") {
let myOutfitsViewController = segue.destinationViewController
as! MyOutfitsViewController
}
}
}
// MARK:- UICollectionViewDataSource Delegate
extension DressingRoomViewController : UICollectionViewDataSource {
func numberOfSectionsInCollectionView(
collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return 12
}
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(
identifier,forIndexPath:indexPath) as! FruitCell
let fruits: [Fruit] = dataSource.fruits
let fruit = fruits[indexPath.row]
let name = fruit.name!
cell.imageView.contentMode = UIViewContentMode.ScaleAspectFit
cell.imageView.image = UIImage(named: name)
return cell
}
}
EDIT: Here is the FruitCell class, just in case you were wondering.
class FruitCell: UICollectionViewCell {
#IBOutlet weak var imageView: UIImageView!
}

A UIImageView that has been placed in a UICollectionViewCell from the interface builder, will not have any knowledge of a UICollectionViewFlowLayout that has been initialised programmatically. So the layout.sectionInset that I had set, was making the UICollectionViewCells smaller, and even when the UIImageView created in interface builder was constrained to the margins of the UICollectionViewCell, the UIImageView was not resizing to go smaller.
The solution was to initialise the UIImageView programmatically and set the size to be the size of the cell taking into account the cell spacing.
Here is the code:
import UIKit
class DressingRoomViewController:
UIViewController,
UICollectionViewDelegateFlowLayout,
UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
let identifier = "cellIdentifier"
let dataSource = DataSource()
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
let cellSpacing: CGFloat = 2
let cellsPerRow: CGFloat = 6
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
}
override func viewDidAppear(animated: Bool) {
let cellSize = (collectionView.collectionViewLayout
.collectionViewContentSize().width / cellsPerRow) - (cellSpacing)
layout.itemSize = CGSize(width: cellSize, height: cellSize)
layout.minimumInteritemSpacing = cellSpacing
layout.minimumLineSpacing = cellSpacing
collectionView.collectionViewLayout = layout
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepareForSegue( segue: UIStoryboardSegue,
sender: AnyObject?) {
if (segue.identifier == "dressingRoom2MyOutfits") {
let myOutfitsViewController = segue.destinationViewController
as! MyOutfitsViewController
}
}
}
// MARK:- UICollectionViewDataSource Delegate
extension DressingRoomViewController : UICollectionViewDataSource {
func numberOfSectionsInCollectionView(
collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return 12
}
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(
identifier,forIndexPath:indexPath) as! FruitCell
let fruits: [Fruit] = dataSource.fruits
let fruit = fruits[indexPath.row]
let name = fruit.name!
var imageView :UIImageView
imageView = UIImageView(frame:CGRectMake( 0,
0,
(collectionView.collectionViewLayout
.collectionViewContentSize().width / cellsPerRow)
- (cellSpacing),
(collectionView.collectionViewLayout
.collectionViewContentSize().width / cellsPerRow)
- (cellSpacing)))
imageView.contentMode = UIViewContentMode.ScaleAspectFit
imageView.image = UIImage(named: name)
imageView.backgroundColor = UIColor.redColor()
cell.addSubview(imageView)
return cell
}
}
EDIT: I was able to crop off the empty space at the bottom of the UICollectionView by creating an outlet for the height constraint ( control + drag height constraint in interface builder onto viewController swift file) calling it heightConstraint, and then at the bottom of viewDidAppear added these two lines of code:
self.heightConstraint.constant = collectionView.contentSize.height
self.view.layoutIfNeeded()
So to show you the result with a white background on the UICollectionView and a red background on the UIImageViews, here is the result (also works on all other device sizes):

Related

How to get all UICollectionViewCells to share the same height?

I have a UICollectionView where the Cell's do not fill their vertical space. What adjustment should I make to ensure each cell fills up the entire cell area? Or at least all share the same height for the row they are on?
Here is the Storyboard
UICollectionViewFlowLayout
class AddServiceFlowLayout: UICollectionViewFlowLayout {
let cellsPerRow: Int
init(cellsPerRow: Int, minimumInteritemSpacing: CGFloat = 0, minimumLineSpacing: CGFloat = 0, sectionInset: UIEdgeInsets = .zero) {
self.cellsPerRow = cellsPerRow
super.init()
self.minimumInteritemSpacing = minimumInteritemSpacing
self.minimumLineSpacing = minimumLineSpacing
self.sectionInset = sectionInset
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepare() {
super.prepare()
guard let collectionView = collectionView else { return }
let marginsAndInsets = sectionInset.left + sectionInset.right + collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right + minimumInteritemSpacing * CGFloat(cellsPerRow - 1)
let itemWidth = ((collectionView.bounds.size.width - marginsAndInsets) / CGFloat(cellsPerRow)).rounded(.down)
itemSize = CGSize(width: itemWidth, height: itemWidth)
}
override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext
context.invalidateFlowLayoutDelegateMetrics = newBounds.size != collectionView?.bounds.size
return context
}
}
UICollectionViewCell
class AddServiceViewCell: UICollectionViewCell {
#IBOutlet weak var labelStackView: UIStackView!
#IBOutlet weak var tvServiceName: UILabel!
#IBOutlet weak var tvQuantityNeeded: UILabel!
public var onCellTapped: (() -> ())?
override func awakeFromNib() {
super.awakeFromNib()
self.layer.cornerRadius = 10.0
}
override func prepareForReuse() {
super.prepareForReuse()
self.tvServiceName.text = nil
self.tvQuantityNeeded.show()
}
public static func nib() -> UINib {
return UINib.init(nibName: identifier, bundle: Bundle(for: ServiceLineItemCell.self))
}
public func configure(with service: TowService){
self.tvServiceName.text = service.description
if(service.calculated){
self.tvQuantityNeeded.hide()
}
}
public func eventTriggered()
{
onCellTapped?()
}
}
UIViewController
class AddServiceViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
private let columnLayout = AddServiceFlowLayout(
cellsPerRow: 3,
minimumInteritemSpacing: 10,
minimumLineSpacing: 10,
sectionInset: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
)
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Add Service"
// Register cell classes
self.collectionView!.delegate = self
self.collectionView!.dataSource = self
self.collectionView!.collectionViewLayout = columnLayout
self.collectionView!.contentInsetAdjustmentBehavior = .always
self.collectionView!.register(AddServiceViewCell.nib().self, forCellWithReuseIdentifier: AddServiceViewCell.identifier)
}
// removed for brevity ....
// MARK: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout
extension AddServiceViewController : UICollectionViewDelegateFlowLayout, UICollectionViewDelegate, UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return allServices.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AddServiceViewCell.identifier, for: indexPath) as! AddServiceViewCell
cell.configure(with: allServices[indexPath.row])
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.onConfirmAdd(allServices[indexPath.row])
}
}
Here is a road map
1- You need to implement sizeForItemAt
2- Consider you have 3 columns per row , create a function that that accepts 3 Items ( introduced with the current indexPath.item ) from your model and manually calculate maximum height for each string in that model
3- Return the maximum height and by this you will set that height for all cells of same row

Collectionview dynamic width not worked Swift

I have one collectionView inside Table view, it works perfectly but the issue is that collection view cell width not worked perfectly on initial stage but worked once scrolls it.
You can check here that in the first section, it shows full name but in other section, it truncates tail and that works after scrolls.
Here is the code that matters
class SubjectsViewFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributesForElementsInRect = super.layoutAttributesForElements(in: rect)
var newAttributesForElementsInRect = [UICollectionViewLayoutAttributes]()
// use a value to keep track of left margin
var leftMargin: CGFloat = 0.0
for attributes in attributesForElementsInRect! {
let refAttributes = attributes
// assign value if next row
if (refAttributes.frame.origin.x == self.sectionInset.left) {
leftMargin = self.sectionInset.left
} else {
// set x position of attributes to current margin
var newLeftAlignedFrame = refAttributes.frame
newLeftAlignedFrame.origin.x = leftMargin
if newLeftAlignedFrame.origin.x + newLeftAlignedFrame.size.width > (self.collectionView?.bounds.size.width)! {
leftMargin = 0.0
newLeftAlignedFrame.origin.x = 0.0
if (newAttributesForElementsInRect.last?.frame.origin.y == newLeftAlignedFrame.origin.y){
newLeftAlignedFrame.origin.y = newLeftAlignedFrame.origin.y + newLeftAlignedFrame.height + minimumLineSpacing
}
}
refAttributes.frame = newLeftAlignedFrame
}
// calculate new value for current margin
leftMargin += refAttributes.frame.size.width + 10
newAttributesForElementsInRect.append(refAttributes)
}
return newAttributesForElementsInRect
}
}
class DynamicCollectionView: UICollectionView {
override func layoutSubviews() {
super.layoutSubviews()
if !__CGSizeEqualToSize(bounds.size, self.intrinsicContentSize) {
self.invalidateIntrinsicContentSize()
if self.superview?.superview?.superview is UITableView {
(self.superview?.superview?.superview as! UITableView).beginUpdates()
(self.superview?.superview?.superview as! UITableView).endUpdates()
}
}
}
override var intrinsicContentSize: CGSize {
return collectionViewLayout.collectionViewContentSize
}
}
class TagTableCell: UITableViewCell {
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var flowLayout: SubjectsViewFlowLayout!
override func awakeFromNib() {
super.awakeFromNib()
collectionView.register(UINib(nibName: "TagCollectionCell", bundle: nil), forCellWithReuseIdentifier: "TagCollectionCell")
}
func setupCell() {
let flowLayout = collectionView.collectionViewLayout as? SubjectsViewFlowLayout
flowLayout?.estimatedItemSize = .init(width: 100, height: 45)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.collectionView.reloadData()
}
}
func setCollectionViewDataSourceDelegate<D: UICollectionViewDataSource & UICollectionViewDelegate>(_ dataSourceDelegate: D, forRow row: Int) {
setupCell()
collectionView.delegate = dataSourceDelegate
collectionView.dataSource = dataSourceDelegate
collectionView.tag = row + 1
collectionView.layoutIfNeeded()
}
}
You just need to call datasource and datadelegate in async method while adding the UICollectionView in tableview cell
DispatchQueue.main.async {
self.collectionView.delegate = dataSourceDelegate
self.collectionView.dataSource = dataSourceDelegate
}
You can modify your cell size just by adding this function, change your desired width and height for each cell.
Here is an example to make the cell's width half of the collection view's.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cellWidth = collectionView.layer.bounds.width / 2
let cellHeight : CGFloat = 150
return CGSize(width: cellWidth, height: cellHeight)
}
You should implement this Protocol to your class:
UICollectionViewDelegateFlowLayout

nil label for custom UICollectionCell

EDIT: seems like same question as this
I made collection view with custom layout and cell but my cell's positions were a little off so I decided to number them so that I could debug it easier. I followed this guy's answer to number my cell using labels. Somehow myLabel was nil and caused my app to crash.
I believe I have connected the outlets correctly but maybe someone could provide a list for me to check if I am doing anything wrong.
ViewController
import UIKit
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet var gridView: UICollectionView!
private let reuseIdentifier = "DesignCell"
private let numberOfSections = 1
private let numberOfCircles = 48
override func viewDidLoad() {
super.viewDidLoad()
self.gridView.registerClass(MyCell.self, forCellWithReuseIdentifier: reuseIdentifier)
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return numberOfSections
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return numberOfCircles
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! MyCell
cell.myLabel.text = String(indexPath.item) // myLabel is nil and causes a crash
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
GridLayout (my custom layout)
import UIKit
import Darwin
class GridLayout: UICollectionViewLayout {
private let numberOfColumns = 12
private let numberOfRows = 4
private var cache = [UICollectionViewLayoutAttributes]()
private var contentHeight: CGFloat = 0
private var contentWidth: CGFloat {
let insets = collectionView!.contentInset
return CGRectGetWidth(collectionView!.bounds) - insets.left - insets.right
}
override func prepareLayout() {
// some calculation i did for my cells
}
override func collectionViewContentSize() -> CGSize {
return CGSize(width: contentWidth, height: contentHeight)
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttributes = [UICollectionViewLayoutAttributes]()
for attr in cache {
if CGRectIntersectsRect(attr.frame, rect) {
layoutAttributes.append(attr)
}
}
return layoutAttributes
}
}
MyCell
import UIKit
class MyCell : UICollectionViewCell {
#IBOutlet weak var myLabel: UILabel!
}
Try to remove this line within viewDidLoad() in your ViewController class:
self.gridView.registerClass(MyCell.self, forCellWithReuseIdentifier: reuseIdentifier)
It's not needed since you have created your UICollectionView in Storyboard, connected to your dataSource and delegate, and you have added all the required methods:
numberOfItemsInSection
cellForItemAtIndexPath

UICollectionView with Custom Cell Breaking UICollectionViewDataSource Protocol

I am trying to use a UICollectionView with a custom cell. My ViewController inherits from UICollectionViewDataSource as below:
import UIKit
import Parse
class DressingRoomViewController: UIViewController,
UICollectionViewDelegateFlowLayout,
UICollectionViewDataSource {
That inheritance is causing me to break a protocol of UICollectionViewDataSource with my UICollectionView function that creates and returns the custom cell. This is the function:
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath)
-> CustomCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(
identifier,forIndexPath:indexPath) as! CustomCell
let dressingRoomIcons: [DressingRoomIcon] =
dataSource.dressingRoomIcons
let dressingRoomIcon = dressingRoomIcons[indexPath.row]
var imageView: MMImageView =
createIconImageView(dressingRoomIcon.name!)
cell.setImageV(imageView)
return cell
}
So before compilation the error is shown in the IDE. How do I get around this error? Here are the two errors I am experiencing:
Type 'DressingRoomViewController' does not conform to protocol
'UICollectionViewDataSource'
Cannot assign a value of type 'DressingRoomViewController' to a value
of type 'UICollectionViewDataSource?'
Here is the whole ViewController:
import UIKit
import Parse
class DressingRoomViewController: UIViewController,
UICollectionViewDelegateFlowLayout,
UICollectionViewDataSource {
#IBOutlet weak var MirrorImageView: UIImageView!
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var heightConstraint: NSLayoutConstraint!
let identifier = "cellIdentifier"
let dataSource = DataSource()
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
let cellSpacing: CGFloat = 5
let cellsPerRow: CGFloat = 6
let numberOfItems = 12
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
}
override func viewDidAppear(animated: Bool) {
let cellSize = (collectionView.collectionViewLayout
.collectionViewContentSize().width
/ cellsPerRow)
- (cellSpacing)
layout.itemSize = CGSize(width: cellSize, height: cellSize)
layout.minimumInteritemSpacing = cellSpacing
layout.minimumLineSpacing = cellSpacing
layout.scrollDirection = UICollectionViewScrollDirection.Horizontal
collectionView.collectionViewLayout = layout
self.heightConstraint.constant = cellSize
self.view.layoutIfNeeded()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepareForSegue( segue: UIStoryboardSegue,
sender: AnyObject?) {
if (segue.identifier == "dressingRoom2MyOutfits") {
let myOutfitsViewController = segue.destinationViewController
as! MyOutfitsViewController
} else if (segue.identifier == "dressingRoom2StickerPicker") {
let myStickerPickerController = segue.destinationViewController
as! StickerPickerViewController
}
}
func imageTapped(sender: UITapGestureRecognizer) {
var imageView = sender.view as! MMImageView
println(imageView.fname)
performSegueWithIdentifier( "dressingRoom2StickerPicker",
sender: imageView)
}
}
// MARK:- UICollectionViewDataSource Delegate
extension DressingRoomViewController : UICollectionViewDataSource {
func numberOfSectionsInCollectionView(
collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return 12
}
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath)
-> CustomCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(
identifier,forIndexPath:indexPath) as! CustomCell
let dressingRoomIcons: [DressingRoomIcon] =
dataSource.dressingRoomIcons
let dressingRoomIcon = dressingRoomIcons[indexPath.row]
var imageView: MMImageView =
createIconImageView(dressingRoomIcon.name!)
cell.setImageV(imageView)
return cell
}
func createIconImageView(name: String) -> MMImageView{
var imageView :MMImageView =
MMImageView(frame:CGRectMake( 0,
0,
(collectionView.collectionViewLayout
.collectionViewContentSize().width / cellsPerRow)
- (cellSpacing),
(collectionView.collectionViewLayout
.collectionViewContentSize().width / cellsPerRow)
- (cellSpacing)))
imageView.contentMode = UIViewContentMode.ScaleAspectFit
imageView.image = UIImage(named: name)
imageView.setName(name)
imageView.backgroundColor = UIColor.clearColor()
imageView.userInteractionEnabled = true
var tapGestureRecognizer =
UITapGestureRecognizer(target: self, action: "imageTapped:")
tapGestureRecognizer.numberOfTapsRequired = 1
imageView.addGestureRecognizer(tapGestureRecognizer)
return imageView
}
}
EDIT: Here is my CustomCell:
import Foundation
import UIKit
class CustomCell: UICollectionViewCell {
var imageView = MMImageView()
func setImageV(IV: MMImageView) {
self.imageView = IV
}
}
Replace this Code
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath)
-> UICollectionViewCell {
Instead of below this :
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath)
-> CustomCell {
You can return custom cell but you can't change return type of any DataSource or Delegate method.

Stuck understanding how to create a table with multiple columns in iOS Swift

I've spent the better half of the day so far researching and trying to understand how to make a table with multiple columns. Embarrassingly, I am still quite new to Swift and programming in general so a lot of the stuff I've read and found aren't helping me too much.
I have basically found exactly what I want to create with this gentleman's blo:
http://www.brightec.co.uk/blog/uicollectionview-using-horizontal-and-vertical-scrolling-sticky-rows-and-columns
However, even with his Github I'm still confused. It seems as if he did not use Storyboard at all (and for my project I've been using storyboard a lot). Am I correct in assuming this?
What I have so far is a UICollectionView embedded in a navigation controller. From here, I have created a new cocoa touch class file subclassed in the CollectionView. But from here is where I'm not entirely sure where to go.
If I can have some direction as to where to go from here or how to properly set it up that would be GREATLY appreciated.
Thanks so much in advance!
IOS 10, XCode 8, Swift 3.0
I found an awesome tutorial on this. thanks to Kyle Andrews
I created a vertical table which can be scrollable on both directions by subclassing UICollectionViewLayout. Below is the code.
class CustomLayout: UICollectionViewLayout {
let CELL_HEIGHT: CGFloat = 50
let CELL_WIDTH: CGFloat = 180
var cellAttributesDictionary = Dictionary<IndexPath, UICollectionViewLayoutAttributes>()
var contentSize = CGSize.zero
override var collectionViewContentSize: CGSize {
get {
return contentSize
}
}
var dataSourceDidUpdate = true
override func prepare() {
let STATUS_BAR_HEIGHT = UIApplication.shared.statusBarFrame.height
let NAV_BAR_HEIGHT = UINavigationController().navigationBar.frame.size.height
collectionView?.bounces = false
if !dataSourceDidUpdate {
let yOffSet = collectionView!.contentOffset.y
for section in 0 ..< collectionView!.numberOfSections {
if section == 0 {
for item in 0 ..< collectionView!.numberOfItems(inSection: section) {
let cellIndexPath = IndexPath(item: item, section: section)
if let attrs = cellAttributesDictionary[cellIndexPath] {
var frame = attrs.frame
frame.origin.y = yOffSet + STATUS_BAR_HEIGHT + NAV_BAR_HEIGHT
attrs.frame = frame
}
}
}
}
return
}
dataSourceDidUpdate = false
for section in 0 ..< collectionView!.numberOfSections {
for item in 0 ..< collectionView!.numberOfItems(inSection: section) {
let cellIndexPath = IndexPath(item: item, section: section)
let xPos = CGFloat(item) * CELL_WIDTH
let yPos = CGFloat(section) * CELL_HEIGHT
let cellAttributes = UICollectionViewLayoutAttributes(forCellWith: cellIndexPath)
cellAttributes.frame = CGRect(x: xPos, y: yPos, width: CELL_WIDTH, height: CELL_HEIGHT)
// Determine zIndex based on cell type.
if section == 0 && item == 0 {
cellAttributes.zIndex = 4
} else if section == 0 {
cellAttributes.zIndex = 3
} else if item == 0 {
cellAttributes.zIndex = 2
} else {
cellAttributes.zIndex = 1
}
cellAttributesDictionary[cellIndexPath] = cellAttributes
}
}
let contentWidth = CGFloat(collectionView!.numberOfItems(inSection: 0)) * CELL_WIDTH
let contentHeight = CGFloat(collectionView!.numberOfSections) * CELL_HEIGHT
contentSize = CGSize(width: contentWidth, height: contentHeight)
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var attributesInRect = [UICollectionViewLayoutAttributes]()
for cellAttrs in cellAttributesDictionary.values {
if rect.intersects(cellAttrs.frame) {
attributesInRect.append(cellAttrs)
}
}
return attributesInRect
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return cellAttributesDictionary[indexPath]
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}
Below is my CollectionViewController Code.
import UIKit
private let reuseIdentifier = "Cell"
class VerticalCVC: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.isScrollEnabled = true
}
// MARK: UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 20
}
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) as! CustomCell
if indexPath.section == 0 {
cell.backgroundColor = UIColor.darkGray
cell.titleLabel.textColor = UIColor.white
} else {
cell.backgroundColor = UIColor.white
cell.titleLabel.textColor = UIColor.black
}
cell.titleLabel.text = "section: \(indexPath.section) && row: \(indexPath.row)"
return cell
}
}
To force CollectionView to use Custom Layout instead of UICollectionViwFlowLayout check below image.
Result:
Portrait mode
landscape mode
One approach is to use a custom cell in a tableviewcontroller. Your story board consists of a table in which the cell is a custom cell with UILabels for columns laid out next to each other (with properly defined constraints).
Example code for the controllers looks like:
import UIKit
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath) as TableViewCell
cell.column1.text = "1" // fill in your value for column 1 (e.g. from an array)
cell.column2.text = "2" // fill in your value for column 2
return cell
}
}
and:
import UIKit
class TableViewCell: UITableViewCell {
#IBOutlet weak var column1: UILabel!
#IBOutlet weak var column2: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
In IB I set up a tableview and added a stackview in the content view (can be done programmatically). The labels are setup programmatically since it allows me to set the width of each column as a fraction of the cell width. Also, I acknowledge that some of the calculations inside the table view cellForRow method should be moved out.
import UIKit
class tableViewController: UITableViewController {
var firstTime = true
var width = CGFloat(0.0)
var height = CGFloat(0.0)
var cellRect = CGRectMake(0.0,0.0,0.0,0.0)
let colors:[UIColor] = [
UIColor.greenColor(),
UIColor.yellowColor(),
UIColor.lightGrayColor(),
UIColor.blueColor(),
UIColor.cyanColor()
]
override func viewDidLoad() {
super.viewDidLoad()
// workaround to get the cell width
cellRect = CGRectMake(0, 0, self.tableView.frame.size.width ,44);
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 3
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
var cellWidth = CGFloat(0.0)
var cellHeight = CGFloat(0.0)
let widths = [0.2,0.3,0.3,0.2]
let labels = ["0","1","2","3"]
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
let v = cell.contentView.subviews[0] // points to stack view
// Note: using w = v.frame.width picks up the width assigned by xCode.
cellWidth = cellRect.width-20.0 // work around to get a right width
cellHeight = cellRect.height
var x:CGFloat = 0.0
for i in 0 ..< labels.count {
let wl = cellWidth * CGFloat(widths[i])
let lFrame = CGRect(origin:CGPoint(x: x,y: 0),size: CGSize(width:wl,height: cellHeight))
let label = UILabel(frame: lFrame)
label.textAlignment = .Center
label.text = labels[i]
v.addSubview(label)
x = x + wl
print("i = ",i,v.subviews[i])
v.subviews[i].backgroundColor = colors[i]
}
return cell
}
}

Resources