I'm new to swift and I can't quite figure out how to address this error.
I'm creating a collection view and this is my code:
import UIKit
class FlashViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// Move on ...
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
layout.itemSize = CGSize(width: 90, height: 90)
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
self.collectionView.dataSource = self
self.collectionView.delegate = self
collectionView.registerClass(CollectionViewCell.self, forCellWithReuseIdentifier: "CollectionViewCell")
collectionView.backgroundColor = UIColor.whiteColor()
self.view.addSubview(collectionView!)
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 20
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionViewCell", forIndexPath: indexPath) as CollectionViewCell
cell.backgroundColor = UIColor.blackColor()
cell.textLabel?.text = "\(indexPath.section):\(indexPath.row)"
cell.imageView?.image = UIImage(named: "circle")
return cell
}
}
every time I run it the self.collectionView.dataSource = self line gets highlighted and I get the above mentioned error.
As you use weak reference your collection view released before you call it.
So you have to make it strong by removing "weak" keyword.
#IBOutlet var collectionView: UICollectionView!
Or make it stay in memory in another way.
...
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
// because collectionView is a weak variable, it will be released here
self.collectionView.dataSource = self // error, collectionView is nil
...
As #Vitaliy1 said, you can make collectionView a strong reference, or use a local variable to hole it before you add it to the view hierarchy.
...
let collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
collectionView.dataSource = self
...
view.addSubview(collectionView)
// view establishes a strong reference to collectionView,
// so you can reference it until it is removed from the view hierarchy.
self.collectionView = collectionView
or, why not just use a subclass of UICollectionViewController
Related
I want to show a collectionView that appears with animation after hitting a button.
The collectionView appears correctly but the collectionViewCells inside don't.
I have set the UICollectionViewDelegate and UICollectionViewDatasource methods correctly but they aren't called (tested with breakPoints).
I'm doing everything programmatically so I also had to register the SettingCell which is a common UICollectionViewCell to the respective cellID.
Also I thought there could be a problem since I was setting the delegate and datasource too late but I don't think so since they are set at the beginning in the init of the mother class.
class SettingsLauncer : NSObject, UICollectionViewDelegate, UICollectionViewDataSource{
var collectionView : UICollectionView = {
let layout = UICollectionViewLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .white
return cv
}()
let CelliD = "CelliD"
func handleMoreButton(){
//creating and adding View
if let windowView = UIApplication.shared.keyWindow {
let width = UIScreen.main.bounds.width
let height = UIScreen.main.bounds.height
let heightCollectionView = CGFloat(300)
let myHeight = height - heightCollectionView
collectionView.frame = CGRect(x: 0, y: height, width: width, height: 0)
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut, animations: {
windowView.addSubview(self.collectionView)
self.collectionView.frame = CGRect(x: 0, y: myHeight, width: width, height: heightCollectionView)
}, completion: nil)
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CelliD, for: indexPath)
return cell
}
override init(){
super.init()
collectionView.register(SettingCell.self, forCellWithReuseIdentifier: CelliD)
collectionView.delegate = self
collectionView.delegate = self
}
}
please add these two lines in your init method
self.collectionView.delegate = self
self.collectionView.dataSource = yourDataSource
currently you are having
collectionView.delegate = self
collectionView.delegate = self
Also make your collectionView lazy ... and assign delgates there
private lazy var collectionView : UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .white
cv.delegate = self
cv.dataSource = yourDataSource
cv.translatesAutoresizingMaskIntoConstraints = false
return cv
}()
I have a UICollectionView that has horizontal scrolling. I need to place all my collection view cells in two rows and both should scroll horizontally. The layout is as shown in the screenshot.
As shown in the above screenshot, I am going to have to build two rows which will scroll horizontally, i.e., both rows will scroll together in the same direction.
I had earlier considered using sections in the scroll view, but then the scrolling would probably be independent, and so, I am hoping to find a better solution.
I looked into this link here : A similar post
This link uses a tableview to hold multiple collection views. Even though solution seems good, I am really not sure if it could work for me, I wish to know if there is a better alternative.
I looked into other stack overflow posts regarding the same (Can’t remember which though), but they did not help much.
Now normally, we would have two columns in the collection view and we can scroll vertically. Instead of this behavior, is there any way to have two rows in a UICollectionView which can scroll horizontally and simultaneously?
Should I consider using two collection views and have some logic that binds the scrolling of both the views? (I’d rather not have two UICollectionviews just to solve this problem)
Also, I am doing this through pure code. No storyboards or xib files have been used.
Can you try the code below?
I was able to do this i guess
class CollectionViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
setUpCollectionView()
}
fileprivate func setUpCollectionView() {
let collectionFlowLayout = UICollectionViewFlowLayout()
collectionFlowLayout.scrollDirection = .horizontal
collectionFlowLayout.itemSize = CGSize(width: 145, height: 145)
collectionView.setCollectionViewLayout(collectionFlowLayout, animated: true)
collectionView.delegate = self
collectionView.dataSource = self
}
}
extension CollectionViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCollectionViewCell", for: indexPath)
if indexPath.row % 2 == 0 {
cell.contentView.backgroundColor = UIColor.red
} else {
cell.contentView.backgroundColor = UIColor.green
}
return cell
}
}
NOTE: I have set collectionView height as 300
OUTPUT
Use 2 collectionView
let CellId1 = "CellId1"
lazy var collectionViewOne: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
let width = collectionView.frame.width
let collectionViewOne = UICollectionView(frame: CGRect(x: 0, y: 100, width: width, height: 100), collectionViewLayout: layout)
collectionViewOne.showsHorizontalScrollIndicator = false
collectionViewOne.backgroundColor = .red
return collectionViewOne
}()
let CellId2 = "CellId2"
lazy var collectionViewTwo: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
let collectionViewTwo = UICollectionView(frame: CGRect(x: 0, y: 250, width: width, height: 100), collectionViewLayout: layout)
collectionViewTwo.backgroundColor = .blue
return collectionViewTwo
}()
then for obtaining the numberOfItem and cellForRow:
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == self.collectionViewOne {
return 10
} else if collectionView == self.collectionViewTwo {
return 20
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.collectionViewOne {
let Cell1 = collectionViewOne.dequeueReusableCell(withReuseIdentifier: CellId1, for: indexPath)
Cell1.backgroundColor = .green
return Cell1
} else if collectionView == self.collectionViewTwo {
let Cell2 = collectionViewTwo.dequeueReusableCell(withReuseIdentifier: CellId2, for: indexPath )
Cell2.backgroundColor = .purple
return Cell2
}
}
and don't forget to register the collectionView in viewDidLoad()
collectionViewOne.delegate = self
collectionViewOne.dataSource = self
collectionViewTwo.delegate = self
collectionViewTwo.dataSource = self
collectionViewOne.register(Cell1.self, forCellWithReuseIdentifier: storyCell)
collectionViewTwo.register(Cell2.self, forCellWithReuseIdentifier: cardCell)
Riddle me this. I have a view that I'm implementing two different collection views on. The two collection views are setup nearly identically.
When I add both to my view, my app calls an error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'could not dequeue a view of kind: UICollectionElementKindCell with identifier CollectionViewPantsCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
Which doesn't make sense, as I registered it the exact same as my CollectionViewShirtCell.
To debug, I removed the pants collection, and rewrote my "cellForItemAt" method to be really simple. It worked fine with one collectionView.
So, what is the difference? I've added some notes in caps in the code.
import UIKit
class HomeController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
let collectionViewShirtsIdentifier = "CollectionViewShirtsCell"
let collectionViewPantsIdentifier = "CollectionViewPantsCell"
var shirtStore: ShirtStore!
var pantStore: PantStore!
public var collectionViewShirts : UICollectionView{
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = UICollectionViewScrollDirection.horizontal
let collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height/2), collectionViewLayout: layout)
collectionView.backgroundColor = UIColor.orange
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: collectionViewShirtsIdentifier)
return collectionView
}
public var collectionViewPants : UICollectionView{
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = UICollectionViewScrollDirection.horizontal
let collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height/2), collectionViewLayout: layout)
collectionView.backgroundColor = UIColor.blue
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: collectionViewPantsIdentifier)
return collectionView
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Hanger"
self.view.addSubview(collectionViewShirts)
///... CANT ADD THE SECOND COLLECTION????
// self.view.addSubview(collectionViewPants)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.collectionViewShirts {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: collectionViewShirtsIdentifier, for: indexPath as IndexPath)
return cell
}
else
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: collectionViewPantsIdentifier, for: indexPath as IndexPath)
return cell
}
}
///... THIS VERSION WORKS IF I ONLY TRY TO MANIPULATE ONE COLLECTION
// func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewShirtsCell", for: indexPath as IndexPath)
//
// return cell
// }
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int{
if(collectionView == collectionViewShirts)
{
print(shirtStore.allShirts.count)
return shirtStore.allShirts.count
}
else if (collectionView == collectionViewPants)
{
return pantStore.allPants.count
}
else
{
return 5//shoeStore.allShoes.count
}
}
}
The way you are setting up your collectionViewShirts and collectionViewPants variables is incorrect. As a result, the if test in cellForItemAt: is falling through to the else and you are attempting to dequeue a 'pants' cell for the 'shirts' collection view.
You need to declare your collection views properly:
public var collectionViewShirts : UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = UICollectionViewScrollDirection.horizontal
let collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height/2), collectionViewLayout: layout)
collectionView.backgroundColor = UIColor.orange
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: collectionViewShirtsIdentifier)
return collectionView
}()
public var collectionViewPants : UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = UICollectionViewScrollDirection.horizontal
let collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height/2), collectionViewLayout: layout)
collectionView.backgroundColor = UIColor.blue
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: collectionViewPantsIdentifier)
return collectionView
}()
Note the = and the () at the end of the closure. This tells Swift to assign the value returned by invoking the anonymous function.
I would also suggest that you use constraints rather than setting the frame directly, as this won't work with view rotation and the frame won't be set correctly at the time you are initialising the collection views, as the view frame is only set when viewDidLayoutSubviews is called.
As I mentioned earlier, doing this sort of stuff in Storyboard is much simpler.
I'm new to iOS development.
I've been learning Swift, and today, I tried using UICollectionViewController.
My code is as follows:
class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var colView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
layout.itemSize = CGSize(width: 90, height: 120)
colView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
colView.dataSource = self
colView.delegate = self
colView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
colView.backgroundColor = UIColor.white
self.view.addSubview(colView)
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 14
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath as IndexPath)
cell.backgroundColor = UIColor.orange
return cell
}
}
However, when I try to run it, I get the following error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'UICollectionView must be initialized with a non-nil layout parameter'
I have looked up previous posts discussing this issue, but I couldn't find anything that fixed my code.
How should I fix this?
Thank you! I incorporated your suggestions. Below is the code that works:
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
let cellId = "CellId"
var colView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
layout.itemSize = CGSize(width: 111, height: 111)
colView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
colView.delegate = self
colView.dataSource = self
colView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
colView.backgroundColor = UIColor.white
self.view.addSubview(colView)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 21
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath as IndexPath)
cell.backgroundColor = UIColor.orange
return cell
}
}
Again, thank you guys very much. Have an awesome day :D
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let cellSize = CGSize(width: 90, height: 120)
//
return CGSizeMake(cellSize)
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(top: 20, left: 10, bottom: 10, right: 10)
}
This should work:
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
return collectionView
}()
Just change the order like below one :
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
layout.itemSize = CGSize(width: 90, height: 120)
colView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
self.view.addSubview(colView)
colView.delegate = self
colView.dataSource = self
colView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
colView.backgroundColor = UIColor.white
You can set your code like this:
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
let cellId = "Cell"
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
// ... layout parameters
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .white
cv.delegate = self
cv.dataSource = self
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
// ... add your auto layout constraints to the collection view
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)
}
// Delegate and Data Source Methods...
}
i have created collection view in swift
language i need to add images to those cells how can i add images?
class ViewController: UIViewController,UICollectionViewDelegateFlowLayout,UICollectionViewDataSource {
var collectionView: UICollectionView?
override func viewDidLoad() {
super.viewDidLoad()
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
layout.itemSize = CGSize(width: 90, height: 120)
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
collectionView!.dataSource = self
collectionView!.delegate = self
collectionView!.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
collectionView!.backgroundColor = UIColor.blueColor()
self.view.addSubview(collectionView!)
// Do any additional setup after loading the view, typically from a nib.
}
Easiest way is to create collection view in interface builder, subclass uicollectionviewcell add outlet for uiimageview
class colCollectionViewCell: UICollectionViewCell {
#IBOutlet var myImageView: UIImageView!
}
Add one dynamic cell in that collectionview, change class of that cell to your subclass. Drop UIImageView on that cell, connect your outlet and access it from UICollectionView delegate/datasource functions.
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as colCollectionViewCell
cell.myImageView.image = UIImage(named:"myimagename")
return cell
}
Here is a great tutorial for UICollectionView: http://www.raywenderlich.com/78550/beginning-ios-collection-views-swift-part-1
you can use Haneke
Sample
if let urlString = self.data?["images"]["standard_resolution"]["url"]{
let url = NSURL(string: urlString.stringValue)
self.imageView.hnk_setImageFromURL(url!)
}
Reference: http://blog.revivalx.com/2015/02/24/uicollectionview-tutorial-in-swift-using-alamofire-haneke-and-swiftyjson/