Multiple collectionViews in single TableCell - ios

I am planning to take multiple collectionView inside single tableView Cell.
What I really want is to show album photos in this manner (UI is important). See image:
how can make this UI ?
Till now I have take a TableView -> TableCell (Single) -> 3 different collectionView inside cell.
Code:
CustomGalleryController
class CustomGalleryController: UIViewController {
var arrayOfPHAsset = [PHAsset]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
CustomAlbum().fetchCustomAlbumPhotos { (array) in
self.arrayOfPHAsset = array
CustomGalleryTableCell().firstCollectionArray = array
CustomGalleryTableCell().secondCollectionArray = array
CustomGalleryTableCell().thirdCollectionArray = array
//self.collectionView.reloadData()
}
}
extension CustomGalleryController : UITableViewDelegate{
}
extension CustomGalleryController : UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomGalleryTableCell", for: indexPath) as! CustomGalleryTableCell
return cell
}
}
CustomGalleryTableCell
class CustomGalleryTableCell: UITableViewCell {
#IBOutlet var firstCollectionView: UICollectionView!
#IBOutlet var secondCollectionView: UICollectionView!
#IBOutlet var thirdCollectionView: UICollectionView!
var firstCollectionArray = [PHAsset]()
var secondCollectionArray = [PHAsset]()
var thirdCollectionArray = [PHAsset]()
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
}
}
extension CustomGalleryTableCell : UICollectionViewDataSource{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// if collectionView == firstCollectionView {
// return 1;//firstCollectionArray.count
// }else if collectionView == secondCollectionView {
// return 1;//secondCollectionArray.count
// }else{
// return 1;//thirdCollectionArray.count
// }
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == firstCollectionView {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FirstCollectionCell", for: indexPath) as! FirstCollectionCell
return cell
}else if collectionView == secondCollectionView {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SecondCollectionCell", for: indexPath) as! SecondCollectionCell
return cell
}else{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ThirdCollectionCell", for: indexPath) as! ThirdCollectionCell
return cell
}
}
}
But getting crash:
Terminating app due to uncaught exception NSInvalidArgumentException',
reason: '-[UITableView collectionView:numberOfItemsInSection:]:
unrecognized selector sent to instance 0x1049ed600
po 0x1049ed600 gives:
<UITableView: 0x1049ed600; frame = (0 0; 414 736); clipsToBounds = YES; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x17424b550>;
layer = ; contentOffset: {0, 0}; contentSize:
{414, 44}>
Please suggest any other and good way to achieve this UI?
What may be the cause of crash if my way of making this UI is correct?

This would achievable using the CHTCollectionViewWaterfallLayout made by chiahsien. I already used this library and I must say it works really great.
You can install it using pods:
pod 'CHTCollectionViewWaterfallLayout/Swift'
The Demo in Swift can be found here.
The only thing different from this example is that you have a certain padding on each column on the top, but I'm sure this is achievable also.

Please suggest any other and good way to achieve this UI?
If you are aiming to achieve what are you mentioning the in screenshot, there is no need to implement it as a tableview which contains collectionviews, instead, the straightforward way to work with UICollectionViewLayout.
As a tip, for the purpose of saving some time and effort, you could find a library that can do the job for you, such as:
CHTCollectionViewWaterfallLayout.
Or if you are interested in reordering the items:
RAReorderableLayout.

Beginner with swift and Xcode here. I've been looking for a solution to populate two CollectionView in a single TableViewCell. Even though this might not exactly be appropriate for your case, the title of your question matches :-) Finally managed to do it by using the tag attribute in the storyboard.
Here's how I did it:
First give a tag number to each CollectionView in the storyboard
In your ViewControllerDataSource, check if it's the right CollectionView by using an if statement and return whatever's needed (numberOfItemsInSection, cellForItemAt).
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView.tag == 0 {
return imageArray1.count
} else {
return imageArray2.count
}
}
}
Hope this helps!

Related

Check Boxes in CollectionView are changing positions when scrolling

I have collectionView (3*3) with Images I am loading from server and I placed a checkBox in the top left corner of each cell so that I can select the cells and based on the selected cells I will get ids for the respective cells images(ids coming from server) and I am able do everything right. But, the problem is if there is are 20 images and if I check the 5 random cells which are loaded for the first time and when I scroll down to select other cells 5 other random checkBoxes are already checked and if I scroll up again some other 5 random cells are checked. It appears that the checked checkBoxes are changing positions because of the dequeue reusable property in the cellForItemAtIndexPath of UICollectionView DataSource method..
I have no Idea how to overcome this problem. Please help me If any one knows how to do this. I am posting below the code I wrote so far and some simulator screenshots for better understanding of the problem...
EditCertificatesViewController:
import UIKit
import Alamofire
protocol CheckBoxState {
func saveCheckBoxState(cell: EditCertificateCell)
}
class EditCertificatesViewController: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
#IBOutlet weak var certificatesCollectionView: UICollectionView!
var certificatesArray = [Certificates]()
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Delete Certificates"
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return certificatesArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "editCertificate", for: indexPath) as! EditCertificateCell
if let certificateURL = URL(string: certificatesArray[indexPath.item].imagePath) {
cell.certificateImage.af_setImage(withURL: certificateURL)
}
cell.certificateId.text = "\(certificatesArray[indexPath.item].imageId)"
cell.selectCertificate.customBox()
if selectedCellIndex.contains(indexPath.item) {
cell.selectCertificate.on = true
}
else {
cell.selectCertificate.on = false
}
cell.selectCertificate.tag = indexPath.item
cell.checkState = self
return cell
}
}
extension EditCertificatesViewController: CheckBoxState {
func saveCheckBoxState(cell: EditCertificateCell) {
if cell.selectCertificate.on == true {
cell.selectCertificate.on = false
}
else {
cell.selectCertificate.on = true
}
if selectedCellIndex.contains(cell.selectCertificate.tag) {
selectedCellIndex = selectedCellIndex.filter{$0 != cell.selectCertificate.tag}
}
else {
selectedCellIndex.append(cell.selectCertificate.tag)
}
print("Status1 \(selectedCellIndex.sorted { $0 < $1 })")
// certificatesCollectionView.reloadData()
}
}
EditCertificateCell:
import UIKit
class EditCertificateCell: UICollectionViewCell {
#IBOutlet weak var certificateImage: UIImageView!
#IBOutlet weak var selectCertificate: BEMCheckBox!
#IBOutlet weak var certificateId: UILabel!
#IBOutlet weak var selectCertificateBtn: UIButton!
var checkState: CheckBoxState?
override func awakeFromNib() {
super.awakeFromNib()
self.selectCertificateBtn.addTarget(self, action: #selector(btnTapped(_:event:)), for: .touchUpInside)
}
#objc func btnTapped(_ sender: UIButton,event: UIEvent) {
self.checkState?.saveCheckBoxState(cell: self)
}
}
CollectionView dequeue's your cell. To rid of this you need to maintain array of selected certificates. Follow below procedure.
Create an array arrSelectedIndex : [Int] = []
In cellForRow,
First check either current index in available in arrSelectedIndex or not? If yes, then make your cell as selected otherwise keep it uncheck.
Give tag to your check button as like this buttonCheck.tag = indexPath.item
If you wanted to select images on check button action, do below.
Get the button tag let aTag = sender.tag
Now check wther this index is available in arrSelectedIndex or not? If yes then remove that index from from the arrSelectedIndex otherwise append that array.
reload your cell now.
If you wanted to select images on didSelectItem instaead check button action, do below.
Now check wther this selected index (indexPath.item) is available in arrSelectedIndex or not? If yes then remove that index from from the arrSelectedIndex otherwise append that array.
reload your cell now.
As this procedure is lengthy so I can only explain you how to do this. If need further help then you can ask.
This is expected. Because you are reusing the cells.
Consider this. You select the first 2 cells, and now scroll down. This function of yours will be called func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {. Now this might get the views from the first 2 cells, that you had selected, and their checkboxes are already selected too.
You need to unset them, and set them, depending upon their last state.
I would recommend adding another property isCertificateSelected to your Certificate model. Each time the user taps on a cell, you retrieve the model, and set/unset this bool. When collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) is called, you retrieve the isCertificateSelected again, and set the checkbox accordingly.
Create an array var Status1CheckList = [Int]()
And in cellForItemAt indexPath check the condition like
if Status1CheckList.contains(indexPath.row) {
cellOfCollection.CheckBtn.setImage(UIImage(named: "check"), for: .normal)
} else {
cellOfCollection.CheckBtn.setImage(UIImage(named: "uncheck"), for: .normal)
}
cellOfCollection.CheckBtn.tag = indexPath.row
cellOfCollection.CheckBtn.addTarget(self, action: #selector(self.checkList), for: .touchUpInside)
And checklist method, After selecting button reload the collectionview
#objc func checkList(_ sender: UIButton) {
if Status1CheckList.contains(sender.tag) {
Status1CheckList = Status1CheckList.filter{ $0 != sender.tag}
} else {
Status1CheckList.append(sender.tag)
}
print("Status1 \(Status1CheckList.sorted { $0 < $1 })")
self.collectionviewObj.reloadData()
}

CollectionView in TableView displays false Data swift

I'm trying to combine a CollectionViewwith a TableView, so fare everything works except one problem, which I cant fix myself.
I have to load some data in the CollectionViews which are sorted with the header of the TableViewCell where the CollectionView is inside. For some reason, every time I start the app, the first three TableViewCells are identical. If I scroll a little bit vertically, they change to the right Data.
But it can also happen that while using it sometimes displays the same Data as in on TableViewCell another TableViewCell, here again the problem is solved if I scroll a little.
I think the problem are the reusableCells but I cant find the mistake myself. I tried to insert a colletionView.reloadData() and to set the cells to nil before reusing, sadly this didn`t work.
My TableViewController
import UIKit
import RealmSwift
import Alamofire
import SwiftyJSON
let myGroupLive = DispatchGroup()
let myGroupCommunity = DispatchGroup()
var channelTitle=""
class HomeVTwoTableViewController: UITableViewController {
var headers = ["LIVE","Channel1", "Channel2", "Channel3", "Channel4", "Channel5", "Channel6"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.navigationBar.isTranslucent = false
DataController().fetchDataLive(mode: "get")
DataController().fetchDataCommunity(mode: "get")
}
//MARK: Custom Tableview Headers
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return headers[section]
}
//MARK: DataSource Methods
override func numberOfSections(in tableView: UITableView) -> Int {
return headers.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
//Choosing the responsible PrototypCell for the Sections
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellBig", for: indexPath) as! HomeVTwoTableViewCell
print("TableViewreloadMain")
cell.collectionView.reloadData()
return cell
}
else if indexPath.section >= 1 {
// getting header Titel for reuse in cell
channelTitle = self.tableView(tableView, titleForHeaderInSection: indexPath.section)!
let cell = tableView.dequeueReusableCell(withIdentifier: "cellSmall", for: indexPath) as! HomeVTwoTableViewCellSmall
// anti Duplicate protection
cell.collectionView.reloadData()
return cell
}
else {
channelTitle = self.tableView(tableView, titleForHeaderInSection: indexPath.section)!
let cell = tableView.dequeueReusableCell(withIdentifier: "cellSmall", for: indexPath) as! HomeVTwoTableViewCellSmall
// anti Duplicate protection
cell.collectionView.reloadData()
return cell
}
}
}
}
My TableViewCell with `CollectionView
import UIKit
import RealmSwift
var communities: Results<Community>?
class HomeVTwoTableViewCellSmall: UITableViewCell{
//serves as a translator from ChannelName to the ChannelId
var channelOverview: [String:String] = ["Channel1": "399", "Channel2": "401", "Channel3": "360", "Channel4": "322", "Channel5": "385", "Channel6": "4"]
//Initiaize the CellChannel Container
var cellChannel: Results<Community>!
//Initialize the translated ChannelId
var channelId: String = ""
#IBOutlet weak var collectionView: UICollectionView!
}
extension HomeVTwoTableViewCellSmall: UICollectionViewDataSource,UICollectionViewDelegate {
//MARK: Datasource Methods
func numberOfSections(in collectionView: UICollectionView) -> Int
{
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return (cellChannel.count)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCellSmall", for: indexPath) as? HomeVTwoCollectionViewCellSmall else
{
fatalError("Cell has wrong type")
}
//removes the old image and Titel
cell.imageView.image = nil
cell.titleLbl.text = nil
//inserting the channel specific data
let url : String = (cellChannel[indexPath.row].pictureId)
let name :String = (cellChannel[indexPath.row].communityName)
cell.titleLbl.text = name
cell.imageView.downloadedFrom(link :"link")
return cell
}
//MARK: Delegate Methods
override func layoutSubviews() {
myGroupCommunity.notify(queue: DispatchQueue.main, execute: {
let realm = try! Realm()
//Getting the ChannelId from Dictionary
self.channelId = self.channelOverview[channelTitle]!
//load data from Realm into variables
self.cellChannel = realm.objects(Community.self).filter("channelId = \(String(describing: self.channelId)) ")
self.collectionView.dataSource = self
self.collectionView.delegate = self
print("collectionView layout Subviews")
self.collectionView.reloadData()
})
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedCommunity = (cellChannel[indexPath.row].communityId)
let home = HomeViewController()
home.showCommunityDetail()
}
}
Thanks in advance.
tl;dr make channelTitle a variable on your cell and not a global variable. Also, clear it, and your other cell variables, on prepareForReuse
I may be mistaken here, but are you setting the channelTitle on the cells once you create them? As I see it, in your viewController you create cells based on your headers, and for each cell you set TableViewController's channelTitle to be the title at the given section.
If this is the case, then the TableViewCell actually isn't receiving any information about what it should be loading before you call reloadData().
In general, I would also recommend implementing prepareForReuse in your HomeVTwoTableViewCellSmall, since it will give you a chance to clean up any stale data. Likely you would want to do something like set cellChannel and channelId to empty strings or nil in that method, so when the cell is reused that old data is sticking around.
ALSO, I just reread the cell code you have, and it looks like you're doing some critical initial cell setup in layoutSubviews. That method is going to be potentially called a lot, but you really only need it to be called once (for the majority of what it does). Try this out:
override the init with reuse identifier on the cell
in that init, add self.collectionView.dataSource = self and self.collectionView.delegate = self
add a didSet on channelTitle
set channelTitle in the viewController
So the code would look like:
var channelTitle: String = "" {
didSet {
self.channelId = self.channelOverview[channelTitle]!
self.cellChannel = realm.objects(Community.self).filter("channelId = \(String(describing: self.channelId)) ")
self.collectionView.reloadData()
}
}
This way you're only reloading your data when the cell is updated with a new channel, rather than every layout of the cell's views.
Sorry... one more addition. I wasn't aware of how your channelTitle was actually being passed. As I see it, you're using channelTitle as a global variable rather than a local one. Don't do that! remove channelTitle from where it is currently before implementing the code above. You'll see some errors, because you're setting it in the ViewController and accessing it in the cell. What you want is to set the channelTitle on the cell from the ViewController (as I outlined above). That also explains why you were seeing the same data across all three cells. Basically you had set only ONE channelTitle and all three cells were looking to that global value to fetch their data.
Hope that helps a little!
(also, you should be able to remove your else if block in the cellForRowAtIndexPath method, since the else block that follows it covers the same code. You can also delete your viewDidLoad, since it isn't doing anything, and you should, as a rule, see if you can get rid of any !'s because they're unsafe. Use ? or guard or if let instead)

UITableView populated but not displaying data or rows

I know this particular question has been asked and answered previously in SO but cross checked those answers and still not able to fix this issue. Can be a silly mistake but unable to nail it.
Cross checked :
Cell Identifierid
datasource and delegate added through Interface builder
Code :
var sensorFields = [Sensor]()
override func viewDidLoad() {
super.viewDidLoad()
readParseDataFromCSV(file: "csvFile")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(self.sensorFields.count)// has count 120
return self.sensorFields.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SensorTableCell") as! SensorTableViewCell
cell.lblSensorName.text = sensorFields[indexPath.row].labelName
cell.lblSensorValue.text = sensorFields[indexPath.row].labelValue
print(sensorFields[indexPath.row].labelName) // doesn't seem to enter to this code block
print(sensorFields[indexPath.row].labelValue)
return cell
}
func readParseDataFromCSV(file:String){
let filepath = Bundle.main.path(forResource: file, ofType: "csv")!
do {
let csv = try CSV(contentsOfURL: filepath)
let rows = csv.rows
for row in rows{
let sensorlblName = row["data column 1"]
let sensorlblValue = row["data column 2"]
let sensor = Sensor(labelName: sensorlblName!, labelValue: sensorlblValue!)
sensorFields.append(sensor)
}
} catch {
print(error.localizedDescription)
}
}
class SensorTableViewCell: UITableViewCell {
#IBOutlet weak var lblSensorName: UILabel!
#IBOutlet weak var lblSensorValue: 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
}
}
Is there anything missed out , using other tableviews in the app already which is working perfectly apart from this one.
Using Xcode 8.1 and Swift 3.0
Edit :
Added reloadData()
Seems like
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {}
is not firing too.
Please help.
Thanks in advance.
Just appending data to your sensorFields array has no impact on the cells displayed in your tableView.
You need to call reloadData on the tableView so it knows the data did change and repopulates the cells with the new data.
for row in rows {
let sensorlblName = row["data column 1"]
let sensorlblValue = row["data column 2"]
let sensor = Sensor(labelName: sensorlblName!, labelValue: sensorlblValue!)
sensorFields.append(sensor)
}
tableView.reloadData()
I would also prefer
// newer dequeue method guarantees a cell is returned and resized properly, assuming identifier is registered
// Swift 2
func dequeueReusableCellWithIdentifier(identifier: String, forIndexPath indexPath: NSIndexPath) -> UITableViewCell
// Swift 3
func dequeueReusableCell(withIdentifier identifier: String, for indexPath: IndexPath) -> UITableViewCell
to
// Used by the delegate to acquire an already allocated cell, in lieu of allocating a new one.
// Swift 2
func dequeueReusableCellWithIdentifier(identifier: String) -> UITableViewCell?
// Swift 3
func dequeueReusableCell(withIdentifier identifier: String) -> UITableViewCell?
because dequeueReusableCellWithIdentifier(identifier: String) does not guarantee returning a UITableViewCell when the cell with identifier was not yet allocated. ("[...] acquire an already allocated cell, in lieu of allocating a new one.")
And make sure your IBOutlets are connected! (lblSensorName and lblSensorValue)
check this
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell = tableView.dequeueReusableCellWithIdentifier("SensorTableCell") as UITableViewCell!
if !cell {
cell = UITableViewCell(style:.Default, reuseIdentifier: "SensorTableCell")
}
}

UICollectionView attempts to dequeue cells beyond data source bounds while reordering is in progress

I have implemented collection view with cell reordering
class SecondViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
private var numbers: [[Int]] = [[], [], []]
private var longPressGesture: UILongPressGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
for j in 0...2 {
for i in 0...5 {
numbers[j].append(i)
}
}
longPressGesture = UILongPressGestureRecognizer(target: self, action: "handleLongGesture:")
self.collectionView.addGestureRecognizer(longPressGesture)
}
func handleLongGesture(gesture: UILongPressGestureRecognizer) {
switch(gesture.state) {
case .Began:
guard let selectedIndexPath = self.collectionView.indexPathForItemAtPoint(gesture.locationInView(self.collectionView)) else {
break
}
collectionView.beginInteractiveMovementForItemAtIndexPath(selectedIndexPath)
case .Changed:
collectionView.updateInteractiveMovementTargetPosition(gesture.locationInView(gesture.view!))
case .Ended:
collectionView.endInteractiveMovement()
default:
collectionView.cancelInteractiveMovement()
}
}
}
extension SecondViewController: UICollectionViewDataSource {
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return numbers.count
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
let number = numbers[section].count
print("numberOfItemsInSection \(section): \(number)")
return number
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
print("cellForItemAtIndexPath: {\(indexPath.section)-\(indexPath.item)}")
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! TextCollectionViewCell
cell.textLabel.text = "\(numbers[indexPath.section][indexPath.item])"
return cell
}
func collectionView(collectionView: UICollectionView, moveItemAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) {
let temp = numbers[sourceIndexPath.section].removeAtIndex(sourceIndexPath.item)
numbers[destinationIndexPath.section].insert(temp, atIndex: destinationIndexPath.item)
}
}
It works fine until I try to drag an item from section 0 to section 2 (which is off screen). When I drag the item to the bottom of collection view, it slowly starts scrolling down. At some point (when it scrolls past section 1) application crashes with fatal error: Index out of range because it attempts to request cell at index 6 in section 1 while there are only 6 items in this section. If I try to drag an item from section 1 to section 2, everything works fine.
Here's an example project reproducing this problem (credit to NSHint):
https://github.com/deville/uicollectionview-reordering
Is this a bug in framework or am I missing something? If it is the former, what would be the workaround?
I actually ran into this issue today. Ultimately the problem was in itemAtIndexPath - I was referencing the datasource to grab some properties for my cell : thus the source of the out of bounds crash. The fix for me was to keep a reference to the currently dragging cell and in itemAtIndexPath; check the section's datasource length versus the passed NSIndexPath and if out of bounds; refer to the dragging cell's property.
Likesuchas (in Obj-C ... haven't moved to Swift yet) :
NSArray* sectionData = [collectionViewData objectAtIndex:indexPath.section];
MyObject* object;
if (indexPath.row < [sectionData count]) {
object = [dataObject objectAtIndex:indexPath.row];
} else {
object = draggingCell.object;
}
[cell configureCell:object];
Not sure if this is similar to your exact issue; but it was mine as everything is working as expected now. :)

How to access UITableView from within UITableViewCell in Swift

I have TableView with cells and I have button on each cell. I want to add some functionality - when user presses button in cell some request is made to server and cell disappears from TableView.
How i can perform this? I know method to delete cell from tableView but to use it i need to get access to tableView from it's cell.
One way could be to add a callback closure to a custom cell and execute it upon cell selection. your view controller would hook this callback up in tableView(_,cellForIndexPath:) or tableView(_, cellWillAppear:)
import UIKit
class SelectionCallbackTableViewCell: UITableViewCell {
var selectionCallback: (() -> Void)?
#IBOutlet weak var button: UIButton!
#IBAction func buttonTapped(sender: UIButton) {
self.selectionCallback?()
}
override func layoutSubviews() {
super.layoutSubviews()
self.contentView.addSubview(self.button)
}
}
register the cell for the tableview. I did it in the storyboard.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var array = [1,1]
override func viewDidLoad() {
super.viewDidLoad()
for x in stride(from: 2, through: 30, by: 1){
let i = array[x-2]
let j = array[x-1]
array.append(i+j)
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! SelectionCallbackTableViewCell
cell.selectionCallback = {
println("\(self.array[indexPath.row]) at \(indexPath.row) on \(tableView) selected")
}
cell.textLabel?.text = "\(self.array[indexPath.row])"
return cell
}
}
Now implement the callback closure as needed
cell.selectionCallback = {
println("\(self.array[indexPath.row]) at \(indexPath.row) on \(tableView) selected")
}
will result in logs like
832040 at 29 on <UITableView: 0x7fe290844200; frame = (0 0; 375 667); clipsToBounds = YES; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x7fe2907878e0>; layer = <CALayer: 0x7fe290711e60>; contentOffset: {0, 697}; contentSize: {375, 1364}> selected
all possible helpful information are present:
indexPath
datasource object
table view
cell
if you want delete the cell after successfully informing your server, for a simple solution do:
cell.selectionCallback = {
println("\(self.array[indexPath.row]) at \(indexPath.row) on \(tableView) selected")
self.apiManager.deleteObject(obj, success: { response in
self.array.removeAtIndex(indexPath.row)
tableView.reloadData()
}, failure:{ error in
// handle error
})
}
Disclaimer:
Personally I don't recommend to implement datasources as UIViewController. It violates the SOLID principles.
Instead datasources should reside in their own classes.
For my own Projects I use an architecture I call "OFAPopulator", that allows me to create independent datasources for each section of a table or collection view.
I chose to implement it inside the view controller here for brevity.
I missed the button part. edited.
Example code: https://github.com/vikingosegundo/CellSelectionCallback

Resources