Trying to access height property in Custom UiView, running into "height is a get-only property" error - ios

I created a custom UIView (DesignView) within a custom TableViewCell (TableViewCell) and I am trying to access the height property within the UIView so that I can grow it/shrink it when the TableViewCellexpands/shrinks.
I am running into the error of
cannot assign to property: 'height' is a get-only property
However after researching similar questions to mines none of the solutions/explanations seems to work.
The DesignView is within the TableViewCell and I am trying to accomplish my goal of making the DesignView "grow" when the cell expands in my main ViewController.
Here is my code for the custom UIView:
#IBDesignable class DesignView : UIView {
var newValue : CGFloat = 0
#IBInspectable var cornerRadius : CGFloat = 0
#IBInspectable var shadowColor : UIColor? = UIColor.black
#IBInspectable var shadowOffSetWidth : Int = 0
#IBInspectable var shadowOffSetHeight : Int = 1
#IBInspectable var shadowOpacity : Float = 0.2
override func layoutSubviews() {
layer.cornerRadius = cornerRadius/2
layer.shadowColor = shadowColor?.cgColor
layer.shadowOffset = CGSize(width: shadowOffSetWidth, height: shadowOffSetHeight)
let shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
layer.shadowPath = shadowPath.cgPath
layer.shadowOpacity = shadowOpacity
}
}
Here is the code for my custom TableViewCell:
class TableViewCell: UITableViewCell {
#IBOutlet var nameLabel: UILabel!
#IBOutlet var phoneNumberLabel: UILabel!
#IBOutlet var addressLabel: 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
}
}
Here is the code for my ViewController that contains the expand/shrink function I mentioned earlier:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var count : Int?
if tableView == self.tableView {
count = 3
} else if tableView == self.pantryTableView {
count = 7
} else if tableView == self.foodTableView {
count = 3
}
return count!
}
var selectedIndexPath : IndexPath? = nil
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("didSelectRowAtIndexPath was called")
var cell = tableView.cellForRow(at: indexPath) as! TableViewCell
switch selectedIndexPath {
case nil:
selectedIndexPath = indexPath
default:
if selectedIndexPath! == indexPath {
selectedIndexPath == nil
} else {
selectedIndexPath = indexPath
}
}
tableView.reloadRows(at: [indexPath], with: UITableViewRowAnimation.automatic)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath, _ sender: TableViewCell) -> CGFloat {
let smallHeight : CGFloat = 70
let expandedHeight : CGFloat = 180
let ip = indexPath
if selectedIndexPath != nil {
if ip == selectedIndexPath! {
return expandedHeight
} else {
return smallHeight
}
} else {
return smallHeight
}
}
Here is where the error is occurring in my TableViewCell:
class TableViewCell: UITableViewCell {
#IBOutlet var designView: DesignView!
var height : CGFloat {
get {
designView.bounds.height
}
set {
return designView.bounds.height = newValue
}
}
I'll also attach a screenshot so you can see what I'm trying to do/where the error is occurring:

let view = UIView(frame: .zero)
// You can only **access** the height as it is a get-only property
print(view.frame.height)
// You can **access** the height and **modify** it
view.frame.size.height = 50.0
You shouldn't need to increase the height if they are properly pinned to the edges of the Table view cell's content view.

In your code, designView.bounds returns a CGRect which is a struct. It doesn't have any property as height. Look the definition of CGRect below:
public struct CGRect {
public var origin: CGPoint
public var size: CGSize
public init()
public init(origin: CGPoint, size: CGSize)
}
Obviously, CGRect does not have any accessible property as height. This is the reason for which you are getting error.. You should get height from size property. There is also a return missing in get method. Replace lines of as below:
var height : CGFloat {
get {
return designView.bounds.size.height
}
set {
return designView.bounds.size.height = newValue
}
}
Hope, it will work!

Related

UICollection View "Reload Data" After Scrolling Crash

I have made a collection view with cells arranged in rows in columns using a great tutorial. I have added a button to a toolbar in the main view controller that calls collectionView.reloadData() as I want a user to be able to edit values which will in turn update the datasource and then reload the collection view to show the updates.
Running this on a simulator it works, but if any scrolling takes place it causes this crash *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array'. If no scrolling has taken place then calling collectionView.reloadData() works. I can't find where this empty array is which is causing the crash. I have tried printing all the arrays that are used in the code in the console but none appear to be empty. Have tried commenting out various lines of code to try and narrow down where the problem is, it seems to be something in the override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? block. I have also tried reseting the collection view frame co-ordinates to 0 before reload data is called but that also didn't work. Have been stuck going round in circles for a few days which no luck. Any suggestions as to where I am going wrong would be hugely appreciated! My code so far is below (please excuse the long winded explanation and code);
View Controller
import UIKit
class ViewController: UIViewController {
// variable to contain cell indexpaths sent from collectionViewFlowLayout.
var cellIndexPaths = [IndexPath] ()
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var collectionViewFlowLayout: UICollectionViewFlowLayout! {
didSet {
collectionViewFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
}
}
#IBAction func editButtonPressed(_ sender: UIButton) {
collectionView.reloadData()
}
#IBAction func doneButtonPressed(_ sender: UIButton) {
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
}
}
extension ViewController:UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! collectionViewCell
cell.backgroundColor = .green
cell.textLabel.text = "\(indexPath) - AAAABBBBCCCC"
return cell
}
}
extension ViewController:UICollectionViewDelegate, IndexPathDelegate {
func getIndexPaths(indexPathArray: Array<IndexPath>) {
cellIndexPaths = indexPathArray.uniqueValues
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
var cellArray = [UICollectionViewCell] ()
print(cellIndexPaths)
for indexPathItem in cellIndexPaths {
if let cell = collectionView.cellForItem(at: indexPathItem) {
if indexPathItem.section == indexPath.section {
cellArray.append(cell)
}
}
for cells in cellArray {
cells.backgroundColor = .red
Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false) { (timer) in
cells.backgroundColor = .green
}
}
}
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cell = collectionView.cellForItem(at: indexPath)
if let safeCell = cell {
let cellSize = CGSize(width: safeCell.frame.width, height: safeCell.frame.height)
return cellSize
} else {
return CGSize (width: 300, height: 100)
}
}
}
extension Array where Element: Hashable {
var uniqueValues: [Element] {
var allowed = Set(self)
return compactMap { allowed.remove($0) }
}
}
Flow Layout
import UIKit
protocol IndexPathDelegate {
func getIndexPaths(indexPathArray: Array<IndexPath>)
}
class collectionViewFlowLayout: UICollectionViewFlowLayout {
override var collectionViewContentSize: CGSize {
return CGSize(width: 10000000, height: 100000)
}
override func prepare() {
setupAttributes()
indexItemDelegate()
}
// MARK: - ATTRIBUTES FOR ALL CELLS
private var allCellAttributes: [[UICollectionViewLayoutAttributes]] = []
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttributes = [UICollectionViewLayoutAttributes]()
for rowAttrs in allCellAttributes {
for itemAttrs in rowAttrs where rect.intersects(itemAttrs.frame) {
layoutAttributes.append(itemAttrs)
}
}
return layoutAttributes
}
// MARK: - SETUP ATTRIBUTES
var cellIndexPaths = [IndexPath] ()
private func setupAttributes() {
allCellAttributes = []
var xOffset: CGFloat = 0
var yOffset: CGFloat = 0
for row in 0..<rowsCount {
var rowAttrs: [UICollectionViewLayoutAttributes] = []
xOffset = 0
for col in 0..<columnsCount(in: row) {
let itemSize = size(forRow: row, column: col)
let indexPath = IndexPath(row: row, column: col)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = CGRect(x: xOffset, y: yOffset, width: itemSize.width, height: itemSize.height).integral
rowAttrs.append(attributes)
xOffset += itemSize.width
cellIndexPaths.append(indexPath)
}
yOffset += rowAttrs.last?.frame.height ?? 0.0
allCellAttributes.append(rowAttrs)
}
}
// MARK: - CONVERT SECTIONS TO ROWS, ITEMS TO COLUMNS
private var rowsCount: Int {
return collectionView!.numberOfSections
}
private func columnsCount(in row: Int) -> Int {
return collectionView!.numberOfItems(inSection: row)
}
// MARK: - GET CELL SIZE
private func size(forRow row: Int, column: Int) -> CGSize {
guard let delegate = collectionView?.delegate as? UICollectionViewDelegateFlowLayout,
let size = delegate.collectionView?(collectionView!, layout: self, sizeForItemAt: IndexPath(row: row, column: column)) else {
assertionFailure("Implement collectionView(_,layout:,sizeForItemAt: in UICollectionViewDelegateFlowLayout")
return .zero
}
return size
}
private func indexItemDelegate () {
let delegate = collectionView?.delegate as? IndexPathDelegate
delegate?.getIndexPaths(indexPathArray: cellIndexPaths)
}
}
// MARK: - INDEX PATH EXTENSION
//creates index path with rows and columns instead of sections and items
private extension IndexPath {
init(row: Int, column: Int) {
self = IndexPath(item: column, section: row)
}
}
Collection Cell
import UIKit
class collectionViewCell: UICollectionViewCell {
#IBOutlet weak var textLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
contentView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
contentView.leftAnchor.constraint(equalTo: leftAnchor),
contentView.rightAnchor.constraint(equalTo: rightAnchor),
contentView.topAnchor.constraint(equalTo: topAnchor),
contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}
}
I have managed to get around the issue by using the UICollectionView.reloadSections(sections: IndexSet) method. This doesn't cause any crashes. I loop through all sections and add each section to an IndexSet variable then use that in the reload sections method like this;
var indexSet = IndexSet()
let rowCount = collectionView.numberOfSections
for row in 0..<rowCount {
indexSet = [row]
collectionView.reloadSections(indexSet)
}

tableview last section is scrambled when setting row height to 0 swift

I added an arrow to fold the section when clicked, when I fold the last section (e.g. setting the row height to 0 in cellForRow, all the rows in the last section get mixed up, as can be seen in the image below:
Can anyone suggest any reason why this should happen if I'm setting the height of the row to 0?
Here's the relevant code:
viewForHeaderInSection:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section == 0 {
let profileCompletion = clientFields?.profileCompletion ?? 0
headerView.profileComplete.text = "Profile".localized() + " \(profileCompletion)% " + "Complete".localized()
headerView.profileProgress.progress = Float(profileCompletion) * 0.01
headerView.profileProgress.progressTintColor = Constants.Client.getProfileCompletenessColor(profileCompletion)
headerView.delegate = self
headerView.section = section
headerView.isOpen = self.sectionsStatuses[section].shouldDisplaySection
return headerView
}
else {
let height = (section == 0) ? FIRST_HEADER_HEIGHT : HEADER_HEIGHT
let nib = UINib(nibName: "ClientDetailsSectionHeaderCell", bundle: nil)
guard let returnedView = nib.instantiate(withOwner: self, options: nil)[0] as? ClientDetailsSectionHeaderCell else {
return UIView()
}
returnedView.delegate = self
returnedView.section = section
returnedView.isOpen = self.sectionsStatuses[section].shouldDisplaySection
returnedView.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: height)
returnedView.heightConstraint.constant = height
returnedView.mainView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
returnedView.mainView.backgroundColor = Constants.ParentForm.headerBgColor
// Draw separators
if DRAW_SECTION_SEPARATORS || DRAW_SECTION_TOP_SEPARATOR {
returnedView.createTopSeparator(color: Constants.ParentForm.separatorColor)
}
if DRAW_SECTION_SEPARATORS || DRAW_SECTION_BOTTOM_SEPARATOR {
returnedView.createBottomSeparator(color: Constants.ParentForm.separatorColor)
}
if isSectionTitleHidden == false {
let xOffset = HEADER_X_OFFSET
let yOffset = section == 0 ? FIRST_HEADER_Y_OFFSET : HEADER_Y_OFFSET
returnedView.title.frame = CGRect(x: xOffset, y: yOffset, width: view.frame.size.width - xOffset, height: height - yOffset)
returnedView.title.text = self.sectionsArray[section].uppercased().localized()
returnedView.title.font = Constants.ParentForm.headerFont
returnedView.title.textColor = Constants.ParentForm.headerTextColor
returnedView.title.sizeToFit()
}
return returnedView
}
}
heightForRow
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if sectionsStatuses[indexPath.section].shouldDisplaySection {
return super.tableView(tableView, heightForRowAt: indexPath)
}
return CGFloat(0)
}
cellForRow
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if self.sectionsArray[indexPath.section] != "ADDRESSES" {
if let clientFields = self.clientFields {
self.dataMap = Utils.convertObjectToMap(item: clientFields)
let birthdayDate = clientFields.birthdayDateString
self.dataMap["birthdayDate"] = birthdayDate
let createdDate = clientFields.createdDateString
self.dataMap["createdDate"] = createdDate
}
}
else {
if let clientAddresses = self.clientAddresses, clientAddresses.count > 0 {
self.dataMap = Utils.convertObjectToMap(item: clientAddresses[Int(floor(Double(indexPath.row) / 8.0))])
}
}
return super.tableView(tableView, cellForRowAt: indexPath)
}
protocol function
func openCloseSection(openSection: Bool, section: Int) {
self.sectionsStatuses[section].shouldDisplaySection = openSection
self.tableView.reloadData()
}
ClientDetailsSectionHeaderCell
import UIKit
protocol SectionViewerProtocol {
func openCloseSection(openSection: Bool, section: Int)
}
class ClientDetailsSectionHeaderCell: UITableViewCell {
#IBOutlet weak var mainView: UIView!
#IBOutlet weak var heightConstraint: NSLayoutConstraint!
#IBOutlet weak var accessoryButton: UIButton!
#IBOutlet weak var title: UILabel!
var section = 0
var delegate: SectionViewerProtocol?
var isOpen: Bool? {
didSet {
if let isOpen = self.isOpen {
accessoryButton.setImage(UIImage(named: isOpen ? "fill588": "fill589"), for: .normal)
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
}
#IBAction func openCloseSection(_ sender: UIButton) {
if let isOpen = self.isOpen {
self.isOpen = !isOpen
delegate?.openCloseSection(openSection: !isOpen, section: section)
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
Try setting clipsToBounds of your cell or any enclosing UIView to true.

Changing UICollectionview cell XY position - swift 3

I'm using a UICollectionview with XX cells. How can i change the XY position on every single cell programmatically? I can change the size of every cell with:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
Is there another function like above that changes the XY positioning?
I hope this example will help you. Let me explain this code blocks. You can think standard UICollectionView and Cell design. There is no detailView. This code implementation will create parallax effect HeaderView.
FIRST AND FOREMOST
import UIKit
class DIYLayoutAttributes: UICollectionViewLayoutAttributes {
var deltaY: CGFloat = 0
override func copy(with zone: NSZone?) -> Any {
let copy = super.copy(with: zone) as! DIYLayoutAttributes
copy.deltaY = deltaY
return copy
}
override func isEqual(_ object: Any?) -> Bool {
if let attributes = object as? DIYLayoutAttributes {
if attributes.deltaY == deltaY {
return super.isEqual(object)
}
}
return false
}
}
class DIYLayout: UICollectionViewFlowLayout {
var maximumStretchHeight: CGFloat = 0
override class var layoutAttributesClass : AnyClass {
return DIYLayoutAttributes.self
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// Create layout attributes
let layoutAttributes = super.layoutAttributesForElements(in: rect) as! [DIYLayoutAttributes]
// How far user scrollview.
let insets = collectionView!.contentInset
let offset = collectionView!.contentOffset
let minY = -insets.top
// we need the calculate delta.
if (offset.y < minY) {
let deltaY = fabs(offset.y - minY)
// we can loop through all attributes
for attributes in layoutAttributes {
//Manipulate attributes
if let elementKind = attributes.representedElementKind {
if elementKind == UICollectionElementKindSectionHeader {
// create frame.
var frame = attributes.frame
frame.size.height = max(minY, headerReferenceSize.height + deltaY)
//We need the change origin for scrolling.
frame.origin.y = frame.minY - deltaY
attributes.frame = frame
attributes.deltaY = deltaY
}
}
}
}
return layoutAttributes
}
// Layout will scroll.
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
SECOND
You need custom cell design.
import UIKit
class ScheduleHeaderView: UICollectionReusableView {
#IBOutlet fileprivate weak var backgroundImageView: UIView!
#IBOutlet fileprivate weak var backgroundImageViewHeightLayoutConstraint: NSLayoutConstraint!
#IBOutlet fileprivate weak var foregroundImageView: UIView!
#IBOutlet fileprivate weak var foregroundImageViewHeightLayoutConstraint: NSLayoutConstraint!
fileprivate var backgroundImageViewHeight: CGFloat = 0
fileprivate var foregroundImageViewHeight: CGFloat = 0
fileprivate var previousHeight: CGFloat = 0
override func awakeFromNib() {
super.awakeFromNib()
backgroundImageViewHeight = backgroundImageView.bounds.height
foregroundImageViewHeight = foregroundImageView.bounds.height
}
override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
super.apply(layoutAttributes)
let attributes = layoutAttributes as! DIYLayoutAttributes
let height = attributes.frame.height
if previousHeight != height {
backgroundImageViewHeightLayoutConstraint.constant = backgroundImageViewHeight - attributes.deltaY
foregroundImageViewHeightLayoutConstraint.constant = foregroundImageViewHeight + attributes.deltaY
previousHeight = height
}
}
}
THIRD
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "ScheduleHeader", for: indexPath) as! ScheduleHeaderView
return header
}
Of course we need to from viewDidLoad.
override func viewDidLoad() {
super.viewDidLoad()
//Add layout.
let width = collectionView!.bounds.width
let layout = collectionViewLayout as! DIYLayout
layout.headerReferenceSize = CGSize(width: width, height: 180)
layout.itemSize = CGSize(width: width, height: 62)
layout.maximumStretchHeight = width
}

XCode7: "Failed to render instance of" affects all UICollectionViewCells

I currently always get an error when using UICollectionViewCells in a Storyboard. No other Controls show this behavior. How can I get rid of them?
This is how one of the affected CollectionViewCells looks like:
This is how I defined it:
Here is the code of the CategoryCollectionCell
import UIKit
import Foundation
#IBDesignable class CategoryCollectionCell : UICollectionViewCell {
#IBOutlet private weak var imageView: UIImageView!
#IBOutlet private weak var label: UILabel!
internal var id : Int?
override var highlighted : Bool {
didSet {
label.textColor = highlighted ? UIColor.greenColor() : UIColor.whiteColor()
}
}
#IBInspectable var text : String? {
get { return label.text }
set(value) { label.text = value }
}
#IBInspectable var image : UIImage? {
get { return imageView.image }
set(value) { imageView.image = value }
}
}
And this is the code of the CollectionViewController:
extension CategoryViewController : UICollectionViewController {
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = self.collectionView?.dequeueReusableCellWithReuseIdentifier(kReuseCellIdentifier, forIndexPath: indexPath)
var categoryCollectionCell = cell as? CategoryCollectionCell
if categoryCollectionCell == nil {
categoryCollectionCell = CategoryCollectionCell()
}
let data = getDataForIndexPath(indexPath)
if data != nil {
categoryCollectionCell?.id = data!.id
categoryCollectionCell!.text = data!.category
categoryCollectionCell!.image = data!.image
}
return categoryCollectionCell!
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 8
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
}
extension CategoryViewController : UICollectionViewDelegateFlowLayout {
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
guard let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout else {
return CGSize()
}
let width = CGRectGetWidth(collectionView.bounds)
let padding = flowLayout.sectionInset.left + flowLayout.sectionInset.right
let itemSpacing = flowLayout.minimumInteritemSpacing
let size = (width - padding - itemSpacing) / 2
return CGSize(width: size, height: size)
}
}
Ok. I found that the errors displayed by XCode had nothing to do with the actual problem.
The directory /Users/{Username}/Library/Logs/DiagnosticReports should contain files with names like this: IBDesignablesAgentCocoaTouch[...].crash
Inside them I found stacktraces which led me to the real problem:
The problem lied inside the code of a custom UITableViewCell instead of a UICollectionViewCell
class FooTableCell : UITableViewCell {
#IBOutlet private weak var checkmarkImageView: UIImageView!
override internal var selected : Bool {
didSet {
checkmarkImageView.hidden = !selected
}
}
}
The checkmarkImageView was nil when using the designer. Because of this the Cocoa Storyboard Agent crashed.
I fixed it by adding a guard statement:
class FooTableCell : UITableViewCell {
#IBOutlet private weak var checkmarkImageView: UIImageView!
override internal var selected : Bool {
didSet {
guard let imageView = checkmarkImageView else {
return
}
imageView.hidden = !selected
}
}
}

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