I have a custom xib file which contains a label and a UICollectionView. I have a second xib file for the collection view's cell with a custom subclass of UICollectionViewCell.
The parent xib file's owner looks like below-
import UIKit
class PackageSizePickerVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var collection: UICollectionView!
let sizes: [PackageSize] = {
let small = PackageSize(title: "Small", imageName: "S")
let medium = PackageSize(title: "Medium", imageName: "M")
let large = PackageSize(title: "Large", imageName: "L")
let extralarge = PackageSize(title: "Extra Large", imageName: "XL")
return [small, medium, large, extralarge]
}()
override func viewDidLoad() {
super.viewDidLoad()
collection.delegate = self
collection.dataSource = self
collection.register(UINib(nibName: "SizesCell", bundle: nil), forCellWithReuseIdentifier: "Sizecell") //register with nib
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return sizes.count
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collection.dequeueReusableCell(withReuseIdentifier: "Sizecell", for: indexPath) as? SizesCell {
let size = sizes[indexPath.row]
cell.image = size.imageName
cell.title = size.title
return cell
}else{
return UICollectionViewCell()
}
}
}
The UICollectionViewCell xib file is named SizesCell.xib and the class file is SizesCell.swift, the code in SizesCell class looks like this-
import UIKit
class SizesCell: UICollectionViewCell {
#IBOutlet weak var sizeImage: UIImageView!
#IBOutlet weak var sizeLabel: UILabel!
var image: String!
var title: String!
override init(frame: CGRect) {
super.init(frame: frame)
sizeImage.image = UIImage(named: image)
sizeLabel.text = title
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Where PackageSize is a Struct as
struct PackageSize {
let title: String
let imageName: String
}
Now the problem I am facing is that the cells simply won't load into the collection view in the parent xib file and I simply can't figure out why the init in the UICollectionViewCell class is not being called at all. I have tried it with awakeFromNib() as well, but that didn't work either. The file owners, custom classes etc. are all set correctly in the IB. What am I missing here?
Related
I am currently trying to control two different collection view. I have also embedded buttons in each of the collection views I want to make each of them contain a set of values from an array. This is my code so far, I don't know what I should actually do to achieve that:
import UIKit
class MyButtonCell: UICollectionViewCell{
#IBOutlet weak var buttonOne: UIButton!
#IBOutlet weak var targetButton: UIButton!
var callback: (() -> ())?
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
contentView.layer.borderWidth = 1
contentView.layer.borderColor = UIColor.black.cgColor
}
#IBAction func buttonTapped(_ sender: UIButton) {
callback?()
}
}
class StevenViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
let buttonTitles: [String] = [
"4", "6", "7", "8"
]
var targetButtonTitles: [String] = [
"", "", "", ""
]
var current:String = ""
#IBOutlet var collectionView: UICollectionView!
#IBOutlet var targetCollection: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
targetCollection.delegate = self
targetCollection.dataSource = self
collectionView.delegate = self
collectionView.dataSource = self
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return buttonTitles.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCellID", for: indexPath) as! MyButtonCell
let targetCell = targetCollection.dequeueReusableCell(withReuseIdentifier: "myCellID", for: indexPath) as! MyButtonCell
// set the button title (and any other properties)
cell.buttonOne.setTitle(buttonTitles[indexPath.item], for: [])
targetCell.targetButton.setTitle(self.targetButtonTitles[indexPath.item], for: [])
// set the cell's callback closure
cell.callback = {
print("Button was tapped at \(indexPath)")
self.targetButtonTitles.append(self.buttonTitles[indexPath.item])
print(self.targetButtonTitles)
// do what you want when the button is tapped
}
return targetCell
}
}
buttonTitles and targetButtonTitles are the two arrays I want each of my collectionviews to contain.
This is what it looks like right now - both are showing the same array. I understand that I probably should have different identifier ID for the cells, but once I do that, it gave me an error saying "could not dequeue a view of kind: UICollectionElementKindCell with identifier target"
In UICollectionViewDelegate & UICollectionViewDataSource
You can use this code snippet to identify the current UICollectionView.
if collectionView == [YOUR_COLLECTIONVIEW_QUTLET] {
}
I am new to Swift. Unable to find solution for below problem.
Below is a ViewController with CollectionView and When you click on Cell in CollectionView, data from cell(even this who isn't in label and image view, but are in Book array row) must be send to TabBarCollection, than from TabBarCollection I need to send this data to all of child's, like in this image.
Later in childs of TabBar I will set value of Labels in View Controllers from data from choosed Cell.
Book.swift
import UIKit
struct Book {
let title: String
let image: UIImage?
//Here soon will be more "let", and this data will also have to be send to TabBar but it don't will be show I CollectionViewCell
}
extension Book {
static let books: [Book] = [
Book(title: "Antygona", image: UIImage(named: "imgantygona")!),
//etc
]
}
CollectionViewCell.swift
import UIKit
class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var bookImageView: UIImageView!
#IBOutlet weak var bookTitle: UILabel!
func setup(with book: Book) {
bookTitle.text = book.title
bookImageView.image = book.image
}
}
ViewController
import UIKit
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
let books = Book.books
override func viewDidLoad() {
super.viewDidLoad()
let fontAttributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16.0)]
UITabBarItem.appearance().setTitleTextAttributes(fontAttributes, for: .normal)
collectionView.dataSource = self
collectionView.delegate = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return books.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "bookCell", for: indexPath) as! CollectionViewCell
let book = books[indexPath.item]
cell.setup(with: book)
return cell
}
}
I saw many solutions but I can't perfectly adapt it to my problem. :(
Thanks for help !
BookInsideViewController.swift
import UIKit
class BookInsideViewController: UIViewController {
#IBOutlet weak var testImageView: UIImageView!
#IBOutlet weak var testLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
}
You can use collection view DidSelectItemAt function
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tabBarController = storyboard.instantiateViewController(withIdentifier: "YourtabbarIdentifier") as! UITabBarController
// You should access the `imageController` through your `tabBarController`,
// not instantiate it from storyboard.
if let viewControllers = tabBarController.viewControllers,
let imageController = viewControllers.first as? ImageController {
BookInsideViewController.recivedData1 = Books[indexPath.row]
}
navigationController?.pushViewController(tabBarController, animated: true)
}
Case:
I'm using a collection view inside a UIView, I've connected it as an outlet called 'partsCollectionView'. I've created a cell with identifier 'cell' and a custom class 'SelectionCollectionViewCell' for that cell. Inside the cell, I've got a label called 'cellTitle'.
Error: Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
I'm facing the error while setting the value of that label inside 'cellForItemAt' Method.
Here are the concerned views:
Cell description,
and, collection View Description
The Cell Class is:
import UIKit
class SelectionCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var cellTitle: UILabel!
}
The class where I'll use the collection view is:
import UIKit
private let reuseIdentifier = "cell"
let array = ["small","Big","Medium","Very big","Toooo big String","small"]
class SelectionCollectionViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var partsCollectionView: UICollectionView!
#IBOutlet weak var instructionsView: UITextView!
#IBOutlet weak var image: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.partsCollectionView.register(SelectionCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return array.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = partsCollectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! SelectionCollectionViewCell
cell.layer.cornerRadius = 10
cell.clipsToBounds = true
cell.cellTitle.text = array[indexPath.row]
cell.layer.borderColor = #colorLiteral(red: 0.07843137255, green: 0.6862745098, blue: 0.9529411765, alpha: 0.6819349315)
cell.layer.borderWidth = 2
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let name = array[indexPath.row]
let attributedName = NSAttributedString(string: name)
let width = attributedName.widthWithConstrainedHeight(height: 20.0)
return CGSize(width: width + 40, height: 30.0)
}
}
extension NSAttributedString {
func widthWithConstrainedHeight(height: CGFloat) -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)
return boundingBox.width
}
}
Update: This is how it looks if I skip setting the title text. The title will come inside those rounded boxes.
Remove this line from viewDidLoad.
self.partsCollectionView.register(SelectionCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
When you register a cell programmatically it creates a new collection view cell object. It doesn't get the cell from storyboard. So cellTitle will be nil
OR
Programmatically initialize the label in custom collection view cell
class SelectionCollectionViewCell:UICollectionViewCell {
let cellTitle = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(cellTitle)
cellTitle.frame = CGRect(x: 10, y: 10, width: 100, height: 30)
}
}
Just minor mistake is there I think, In cellForItemAt indexPath Please update following line,
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SelectionCollectionViewCell
I just paste your code and got following output, nothing else,
If you still unable to produce desired output, just delete existing UILabel from SelectionCollectionViewCell and add it again.
FYI. No need to register cell in viewDidLoad() method
This is the error:
Base.lproj/Main.storyboard: error: IB Designables: Failed to update auto layout status: The agent raised a "NSInternalInconsistencyException" exception: could not dequeue a view of kind: UICollectionElementKindCell with identifier WalletCurrencyCollectionCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard
I have wallet control in storyboard
Wallet control has 4 cells in UICOllectionView , but it gives this error in storyboard (but when I launch code, everything is OK, it just gives error in storyboard). But if I set number of cells = 0 , it works fine, so the problem is in the collection view cell
Here is the structure of the cell
Here is the structure of wallet control
Here is the code of wallet control:
import UIKit
class WalletCurrencyCollectionCell: UICollectionViewCell {
#IBOutlet var labelAccount: UILabel!
}
#IBDesignable class WalletControl: UIView, UIPickerViewDelegate, UIPickerViewDataSource, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
#IBOutlet var labelInYourAccount: UILabel!
#IBOutlet var labelOtherAccounts: UILabel!
#IBOutlet var collectionViewMain: UICollectionView!
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
func setup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [.FlexibleHeight ,.FlexibleWidth]
addSubview(view)
// localizations
labelInYourAccount.text = "wallet_label_inYourAccount".localized
}
override func awakeFromNib() {
// register nib
let collectionCellNib = UINib(nibName: "WalletCurrencyCollectionCell", bundle: nil)
collectionViewMain.registerNib(collectionCellNib, forCellWithReuseIdentifier: "WalletCurrencyCollectionCell")
}
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "WalletControl", bundle: bundle)
view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
override func intrinsicContentSize() -> CGSize {
// return CGSizeMake(200, labelSuper.maxY + 10)
let sizeOfView = viewContainerToMesherSize.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
return sizeOfView
}
// MARK: - Collection view
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// test
return 4
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("WalletCurrencyCollectionCell", forIndexPath: indexPath) as! WalletCurrencyCollectionCell
// fill with information
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
collectionView.deselectItemAtIndexPath(indexPath, animated: true)
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let width = collectionView.widthMy
return CGSizeMake(width, 100)
}
}
So I don't understand why I have this error in storyboard about nib, because I register nib. And after app launch everything is ok. What can I do with this storyboard error (the problem is in UICollectionView, because if I set number of cell = 0 everything is OK)?
I solved that by calculating count of cells using server responce (I return 0 if there were no server responce about wallet accounts), so in this case storyboard always thinks that I have 0 cell and I don't have any warnings, and when I launch application everything is still OK, because I just properly display cells in UICollectionView
func help_getArrayOfUserCurrencyAccounts() -> [SCurrencyAccount] {
if let currentUser = ApiManager.sharedInstance.userService_currentUser {
return currentUser.arrayCurrencyAccounts
}
else {
return [SCurrencyAccount]()
}
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
let realAccountsCount = help_getArrayOfUserCurrencyAccounts().count
if realAccountsCount > 0 {
if realAccountsCount == 1 {
return realAccountsCount
}
else {
return realAccountsCount + 1 // so we can scroll through accounts
}
}
else {
return 0
}
}
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