I have three UICollectionView inside one UIViewController. Then I set IBOutlet for each UICollectionView
#IBOutlet weak var firstCollectionView: UICollectionView!
#IBOutlet weak var secondCollectionView: UICollectionView!
#IBOutlet weak var thirdCollectionView: UICollectionView!
and I set delegate and datasource for them. But when I want to set numberOfItemsInSection like below
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == firstCollectionView {
return 1
} else if collectionView == secondCollectionView {
return 2
} else if collectionView == thirdCollectionView {
return 3
}
}
It keeps giving me this error Missing return in a function expected to return 'Int'
You need to return 0 because in numberOfItemsInSection you must have to return Int. you ended with else if but numberOfItemsInSection still need return Int value.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == firstCollectionView {
return 1
} else if collectionView == secondCollectionView {
return 2
} else if collectionView == thirdCollectionView {
return 3
} else {
return 0
}
}
For better readability & a little less code, I suggest you use a switch statement:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
switch collectionView {
case firstCollectionView:
return 1
case secondCollectionView:
return 2
case thirdCollectionView:
return 3
default:
return 0
}
}
Personally, I'd make a separate dataSource class for each collectionView, & assign them in viewDidLoad -
firstCollectionView.dataSource = FirstDataSource()
secondCollectionView.dataSource = SecondDataSource()
thirdCollectionView.dataSource = ThirdDataSource()
& either give the required data to each dataSource when it's created or make the dataSource have responsibility for getting the data as well. Putting everything into the viewController in a situation like this leads to madness...
Simple example -
class MyDataSource: UICollectionViewDataSource {
var data: [MyData] = [] // set this when instantiating, or provide the means of creating the data which you would have put in the viewController
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell: MyCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellIdentifier", for: indexPath) as? MyCollectionViewCell else {
return UICollectionViewCell()
}
// do stuff to cell
}
}
In viewController -
self.firstCollectionView.dataSource = MyDataSource()
// etc...
Very straight forward, all it's doing is separating the code into areas of responsibility, makes it a whole lot easier to manage
Related
I am attempting a horizontal UICollectionView printing out CoreData entries, I get no errors when I enter in data and try to view the data, but the data does not print out in the UICollectionView. It is not an issue with entering the data in CoreData. below is my code to fetch CoreData and print out in UICollectionView, where am I going wrong?
class HistoryVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
var QBHistory = [QB]()
override func viewDidLoad() {
super.viewDidLoad()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return QBHistory.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "QBCollectionViewCell", for: indexPath) as! QBCollectionViewCell
do{
QBHistory = try context.fetch(QB.fetchRequest())
for each in QBHistory {
cell.QBName.text = each.qbName
cell.QBPotential.text = each.qbPotential
cell.QBValue.text = each.qbValue
cell.QBBust.text = String(each.qbBust)
cell.QBRound.text = each.qbRound
}
} catch {
print("oh no it broke")
}
return cell
}
}
The problem is this line in numberOfItemsInSection:
return QBHistory.count
That would be zero, so you have zero cells.
You need to move
QBHistory = try context.fetch(QB.fetchRequest())
Into viewDidLoad, so that it happens before your collection view data source query methods are called.
I have 12 cells in my CollectionView. I want to show 3 cells on one screen like this:
and scroll right to show another cells.
But now I create CollectionView and CollectionViewCell and edit cell in storyboard and I get this result:
How to fix it?
My code in CollectionView:
import UIKit
private let reuseIdentifier = "Cell"
class MasterViewController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 12
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! MasterViewCell
cell.label.text = String(indexPath.row)
cell.backgroundColor = UIColor.green
return cell
}
}
My code in CollectionViewCell:
import UIKit
class MasterViewCell: UICollectionViewCell {
#IBOutlet weak var label: UILabel!
}
To achieve that spacing you need to do like so
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
so after each 3 cell in new section it will take that Section Inset and will give that space.
I have added sample screen shot for your reference please check it
and still you have confusion so please let me know.
I've been researching on how to embed my own custom decimal keyboard into a UIView. i've found below image, which i would like to recreate. However i'm not sure on how and what is the best way to get started with such? is it just to create multiple UIButton and then handle it in a method or is there a smart way to recreate such ?
A collectionView is a great way to do this. Create a new storyboard, put a UICollectionViewController in there. Then create a UICollectionViewCell with an UILabel (for digits and a dot) and an UIImage (for the delete button).
Here's my UICollectionViewController code for this:
import UIKit
protocol KeypadDelegate: class {
func did(tapOnKeypad key: KeypadKey)
}
enum KeypadKey {
case number (value: Int)
case backspace
}
class KeypadViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
// MARK: - Properties.
weak var delegate: KeypadDelegate?
// MARK: - Lifecycle.
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.register(cellReuseID: KeypadCVCell.reuseID)
}
// MARK: - UICollectionView DataSource.
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 12
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "KeypadCVCell", for: indexPath) as! KeypadCVCell
switch indexPath.row {
case 0...8:
cell.configure(number: String(indexPath.row + 1))
case 9:
cell.setBlank()
case 10:
cell.configure(number: "0")
case 11:
let image = UIImage(named: "btn_keypad_backspace")
cell.configure(image: image)
default:
break
}
return cell
}
// MARK: - UICollectionView Delegate.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
switch indexPath.row {
case 0...8:
let key: KeypadKey = .number(value: indexPath.row + 1)
delegate?.did(tapOnKeypad: key)
case 9:
break
case 10:
let key: KeypadKey = .number(value: 0)
delegate?.did(tapOnKeypad: key)
case 11:
delegate?.did(tapOnKeypad: .backspace)
default:
break
}
}
// MARK: - UICollectionView Delegate FlowLayout.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.bounds.width / 3
let height = collectionView.bounds.height / 4
return CGSize(width: width, height: height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
class KeypadCVCell: UICollectionViewCell {
// MARK: - Outlets.
#IBOutlet weak var numberLabel: UILabel!
#IBOutlet weak var backspaceImageView: UIImageView!
// MARK: - Configuration.
func configure(number: String) {
self.numberLabel.text = number
self.backspaceImageView.isHidden = true
}
func configure(image: UIImage?) {
self.numberLabel.isHidden = true
self.backspaceImageView.image = image
}
func setBlank() {
self.numberLabel.isHidden = true
self.backspaceImageView.isHidden = true
}
}
Create a single UIButton action outlet, connect all the buttons to it, check the senders title value, convert it to Int, if not possible it's the comma or if title text is empty it's the backspace.Or create a separate function for the backspace. That would be a relatively easy, clutter free way to do it and wouldn't take much more than a dozen lines of code.
I have a UICollectionView with several headers.
They are implemented as section headers.
Each header belongs to its own controller and those controllers conform to MyHeaderProtocol.
Basically I have an array of controllers and request header cell from each of them when necessary.
So:
collectionView(:viewForSupplementaryElementOfKind:at) is called and returns actual view. View is properly set, has good frame size etc. Just in case I color view there to purple - but I can't see it on screen.
collectionView(_:layout:referenceSizeForHeaderInSection:) is called and return good CGSize values.
Collection view is displayed but instead of all those headers I see just empty white space.
So the issue is - both necessary methods do display a header section are called but section header isn't displayed (yet space is reserved for it).
Here is simplified version of my code:
protocol MyHeaderProtocol: class {
var nib: UINib { get }
var cellReuseIdentifier: String { get }
// Will dequeue UICollectionViewCell using dequeueReusableSupplementaryView and set data for this cell
func setupHeader(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: IndexPath) -> UICollectionViewCell?
func getCellHeight()
}
class MyCollectionView: UIViewController {
#IBOutlet private weak var collectionView: UICollectionView
private var headers: [MyHeaderProtocol] = []
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
// it will instantiate controllers conformed to MyHeaderProtocol and append them to header array
setUpHeaders()
registerHeaders()
}
private func setUpHeaders() {
let headerOne = SimpleHeader1(withModel: viewModel)
headers.append(headerOne)
let headerTwo = SimpleHeader2(withModel: viewModel)
headers.append(headerTwo)
}
private func registerHeaders() {
for header in headers {
collectionView.register(header.nib, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: header.cellReuseIdentifier)
}
}
}
extension MyCollectionView: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return headers.count + 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == headers.count {
// If section isn't a header section - we return actual cells
return viewModel.people.count
} else {
// if it is a header - we return only section, no intems
return 0
}
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
var view : UICollectionReusableView?
switch kind {
case UICollectionElementKindSectionHeader:
// if it is a header
if indexPath.section < headers.count {
let header = headers[indexPath.section]
view = header.setupHeader(collectionView: collectionView, cellForItemAtIndexPath: indexPath)
// to test that header is visible
view?.backgroundColor = UIColor.purple
}
case UICollectionElementKindSectionFooter:
break
default:
assert(false, "Unexpected element kind")
break
}
return view ?? UICollectionReusableView()
}
}
extension MyCollectionView : UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
var size = CGSize.zero
// If that's header - return non-Zero size
if section < headers.count {
let header = headers[section]
size = CGSize.init(width: collectionView.bounds.width, height: header.getCellHeight())
}
return size
}
}
I think the issue may be with your custom MyHeaderProtocol protocol. This function in particular:
func setupHeader(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: IndexPath) -> UICollectionViewCell?
You have to return a header here, otherwise this function collectionView(:viewForSupplementaryElementOfKind:at will not produce expected behaviour:
var view : UICollectionReusableView?
view = header.setupHeader(collectionView: collectionView, cellForItemAtIndexPath: indexPath)
So try this instead:
func setupHeader(collectionView: UICollectionView, headerForItemAtIndexPath indexPath: IndexPath) -> UICollectionReusableView
I hope this helps.
I want one View Controller to have seven horizontal button scrolls. I've set up the first Collection View, and I repeated the exact process for a second Collection View directly underneath, but I keep getting Thread 1: SIGABRT error. My code for the functional Collection View is here:
class xyViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
var tableImages: [String] = ["1.png", "2.png", "3.png", "4.png", "5.png", "6.png"]
override func viewDidLoad() {
// Initialize the collection views, set the desired frames
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return tableImages.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell:xyCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! xyCollectionViewCell
cell.xyImgCell.image = UIImage(named: tableImages[indexPath.row])
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
println("cell \(indexPath.row) selected")
}
}
It works when I run this.
So when I add a second Collection View underneath, create a new View Controller file to IBOutlet the button's image and a new Collection View Cell file to add the following code, it crashes.
class bwViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
var bwTableImages: [String] = ["a", "PlasmaBlast.png", "b.png", "c.png", "d.png", "e.png", "f.png", "g.png", "h.png", "i.png", "j.png"]
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return bwTableImages.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell:bwCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("cell1", forIndexPath: indexPath) as! bwCollectionViewCell
cell.bwImgCell.image = UIImage(named: bwTableImages[indexPath.row])
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
println("cell \(indexPath.row) selected")
}
}
Here is a two-section version of the code you wrote. I'm not sure if it addresses the error you are seeing, but it's how you'd do multiple collection views on one page.
class xyViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
var tableImagesOne: [String] = ["1.png", "2.png", "3.png", "4.png", "5.png", "6.png", "7.png", "8.png"]
var tableImagesTwo: [String] = ["1.png", "2.png", "3.png", "4.png", "5.png"]
override func viewDidLoad() {
// Initialize the collection views, set the desired frames
}
func numberOfSectionsInCollectionView(_ collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == 0 {
return tableImagesOne.count
}
else {
return tableImagesTwo.count
}
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let row = indexPath.row
let section = indexPath.section
let cell:xyCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! xyCollectionViewCell
if section == 0 {
cell.xyImgCell.image = UIImage(named: tableImagesOne[row])
}
else if section == 1 {
cell.xyImgCell.image = UIImage(named: tableImagesTwo[row])
}
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
println("cell \(indexPath.section) : \(indexPath.row) selected")
}
}