I've made a subclass of NSCollectionView which conforms to NSCollectionViewDataSource, NSCollectionViewDelegate but it hangs everytime - I only get the spinning beach ball.
public class SequenceCollectionView : NSCollectionView, NSCollectionViewDataSource, NSCollectionViewDelegate {
// MARK: Inits
public required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
func commonInit() {
self.dataSource = self
self.delegate = self
}
//MARK: Datasource
public func collectionView(collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
public func collectionView(collectionView: NSCollectionView, itemForRepresentedObjectAtIndexPath indexPath: NSIndexPath) -> NSCollectionViewItem {
if indexPath.item == 0 {
let item = NSCollectionViewItem(nibName: "ItemView", bundle: nil)
}
return NSCollectionViewItem()
}
}
It's obviously in some kind of loop or retain cycle but I'm finding it hard to debug.
It transpires that the issue lies with the optional delegate call
public func collectionView(collectionView: NSCollectionView, willDisplayItem item: NSCollectionViewItem, forRepresentedObjectAtIndexPath indexPath: NSIndexPath) {
}
By adding this to the class has fixed the issue.
Adding to Chris' answer, the NSCollectionView will hang on scrolling or resizing the view unless the following optional delegate method is overriden:
- (void)collectionView:(NSCollectionView *)collectionView didEndDisplayingItem:(NSCollectionViewItem *)item forRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath {}
Related
So I have an issue I've been battling with for days now. I have a collectionView which is designed to have large cells (width-wise) which go off screen. The height is fixed. The idea is to basically scroll to the end (see below):
To achieve being able to scroll left and right, I've had to override the width inside collectionViewContentSize in my flowLayout. Like below.
override var collectionViewContentSize: CGSize {
var size = super.collectionViewContentSize
size.width = largeWidth
return size
}
This achieves increasing the horizontal scroll area (which is what I want) but the cells start to disappear once I reach a certain point. It's almost as if the cells are being dequeued when they shouldn't be. Any ideas on this. This is the final straw for my project but I'm out of ideas.
Many thanks
Code snippet can be found below. You should be able to just copy and paste this into any project:
class HomeViewController: UIViewController {
let collectionView: UICollectionView
let collectionViewLayout = CustomCollectionViewFlowLayout()
init() {
collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
super.init(nibName: nil, bundle: nil)
collectionView.backgroundColor = UIColor.red.withAlphaComponent(0.4)
collectionView.register(SomeCell.self, forCellWithReuseIdentifier: "SomeCell")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
collectionView.delegate = self
collectionView.dataSource = self
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView.frame = view.bounds
view.addSubview(collectionView)
}
}
class SomeCell: UICollectionViewCell {
}
extension HomeViewController: UICollectionViewDataSource,
UICollectionViewDelegate,
UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 150
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SomeCell", for: indexPath) as! SomeCell
cell.backgroundColor = .blue
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 10000, height: 70)
}
}
class CustomCollectionViewFlowLayout: UICollectionViewFlowLayout {
override var collectionViewContentSize: CGSize {
var size = super.collectionViewContentSize
size.width = 10000
return size
}
}
I pushed my version 7.6 of my iOS app to the App Store and noticed that the app suddenly contained many bugs (strange behaviours) that did not show up during debugging.
Since the app worked perfectly in xCode 9.x I suspect strongly that the issues started happening with Xcode 10.3.
I am using Swift 4.2 (conversion of Swift 5 is for next update)
After investigating for many hours I located the issue: when Compilation mode = "whole module" the bugs appear and when set to "incremental" the disappear. In debug mode (when app is run out of Xcode) the Compilation mode is set to "incremental" for release its "whole module" (this is the standard configuration when you create a new project in Xcode 10.x I suspect) this explains why we did not see the issues during debug testing.
Also note that changing to legacy build system did not solve the issues. Only setting Compilation mode = "incremental" solved the issues.
Analysis:
I tracked the issue to the fact that for all my TableViews the delegate was not being called.
I have the following simple hierarchy :
Code of the ViewTableRoot:
class ViewTableRoot : UITableView, UITableViewDelegate, UITableViewDataSource {
var didScrollToOffset : ( (CGFloat) -> Void )?
var didEndScrolling : ( (CGFloat) -> Void )?
var didChangeEditing : ( ( ) -> Void )?
//MARK: lifecycle
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override init(frame: CGRect, style: UITableView.Style) {
super.init(frame: frame, style: style)
setup();
}
func setup() {
//set the corner radius of the layer so that the sliding of the cells underneath the rounded headers does not show up
layer.cornerRadius = 5
//setup myself as delegate and data source
delegate = self
dataSource = self
}
deinit {
let className = String(describing: self)
log.debug("**********\(className)")
}
//MARK: - public API
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
didChangeEditing?()
}
//MARK: - scrollview delegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
//because we are also getting events when swiping on the cells, we need to see the difference between
//swipig on the cell and swiping in the "actual" table => we do this by checking the frame size
guard scrollView.frame == frame else { return }
didScrollToOffset?(scrollView.contentOffset.y)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
//because we are also getting events when swiping on the cells, we need to see the difference between
//swipig on the cell and swiping in the "actual" table => we do this by checking the frame size
guard scrollView.frame == frame else { return }
if !decelerate {
didEndScrolling?(scrollView.contentOffset.y)
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
//because we are also getting events when swiping on the cells, we need to see the difference between
//swipig on the cell and swiping in the "actual" table => we do this by checking the frame size
guard scrollView.frame == frame else { return }
didEndScrolling?(contentOffset.y)
}
//MARK: - UITableViewDataSource
func numberOfSections(in tableView: UITableView) -> Int {
preconditionFailure("Must be implemented by derrived class")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
preconditionFailure("Must be implemented by derrived class")
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
preconditionFailure("Must be implemented by derrived class")
}
//MARK: - UITableViewDelegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
}
Code of the ViewTableSettings:
class ViewTableSettings : ViewTableRoot {
var settings : [[Setting]]? {
didSet {
reloadData()
}
}
var didPressSetting : ((Setting, CGRect) -> (Void))?
//MARK: lifecycle
override func setup() {
super.setup()
log.debug("delegate : \(delegate)")
//register xibs
register(CellTableSetting.nib, forCellReuseIdentifier: CellTableSetting.reuseIdentifier)
}
//MARK: - UITableView
override func numberOfSections(in tableView: UITableView) -> Int {
let count = settings?.count ?? 0
log.debug("count: \(count)")
log.debug("delegate : \(delegate)")
return count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let count = settings?[section].count ?? 0
log.debug("count: \(count)")
log.debug("delegate : \(delegate)")
return count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
log.debug("delegate : \(delegate)")
//ask for a new cell
let cell = tableView.dequeueReusableCell(withIdentifier: CellTableSetting.reuseIdentifier, for: indexPath) as! CellTableSetting
guard let setting = settings?[indexPath.section][indexPath.row] else {
preconditionFailure("Asking CellTableSetting but no Setting model defined")
}
//load up!
cell.setting = setting
cell.lastCell = indexPath.section != numberOfSections - 1 ? false : indexPath.row == (numberOfRows(inSection:indexPath.section) - 1)
//return cell to use
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
log.debug("-")
return CellTableSetting.height
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
log.debug("-")
tableView.deselectRow(at:indexPath, animated: true)
guard let setting = settings?[indexPath.section][indexPath.row] else {
return
}
didPressSetting?(setting,rectForRow(at: indexPath))
}
func tableView(_: UITableView, viewForFooterInSection: Int) -> UIView? {
log.debug("-")
guard viewForFooterInSection < (numberOfSections-1) else {
let version = UILabel()
version.text = UIApplication.AppVersionAndBuildPrettyPrint
version.font = UIFont.defaultBoldFont(size: 12)
version.textColor = PaintCode.mainLightGray_a50
version.textAlignment = .center
return version
}
let v = UIView()
v.backgroundColor = PaintCode.mainLightGray_a50
return v
}
func tableView(_: UITableView, heightForFooterInSection: Int) -> CGFloat {
log.debug("-")
return heightForFooterInSection < (numberOfSections-1) ? 5 : 40
}
}
as you can see the ViewTableRoot declares compliance to the UITableViewDelegate (also to UITableViewDataSource but that is besides the issue for now)
the delegate is actually assigned to self in the ViewTableRoot but the actual delegate functions are implemented in the derived ViewTableSettings(again this worked perfectly in Xcode 9.x)
when compilation mode = "Whole Module" these delegate functions are not being call => this is the bug
when set to "incremental" these delegate functions are called just fine!
Additional tests I have done to get more insight in the issue:
switching to the "legacy build system" (via Xcode/file/project settings) does not solve the issue; as long as the Whole Module is enabled the issue remains
when I create empty delegate functions in the ViewTableRoot and override them in the ViewTableSettings it does work :-o
I did verify in ViewTableSettings that the delegate was indeed set to an instance of ViewTableSettings and not ViewTableRoot (in which case there would not be any delegate functions implemented)
My thoughts
I get the feeling that I stumbled upon a bug in the (new?) build system?
Anybody else run into similar issues?
Yes, I have the same issue as you. When the Compilation Mode is "Whole Module", the Collectionview controllers are messed up but not the Tableview controllers. result on simulator device. Maybe you can try to use UITableViewController directly rather than conform TableView protocol.
Installed the latest Xcode 11.4 (11E146) and the problem seams to be resolved in this version. I have re-enabled the whole module optimization and everything seams to be working as expected. So... turns out that it was a bug XCdoe!
I would like to create list of rows using UICollectionView with each row can click and go to another controller.
I create custom cell in xib and class for it.
This is my code:
class CollectionCellXIB: UICollectionViewCell {
#IBOutlet weak var label: UILabel!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
}
}
class Collection: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell: CollectionCellXIB
self.collectionView!.registerNib(UINib(nibName: "CollectionCellXIB", bundle: nil), forCellWithReuseIdentifier: "cell")
if indexPath.row == 0 {
cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! CollectionCellXIB
cell.label.text = "cell 1"
} else {
cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! CollectionCellXIB
cell.label.text = "cell 2"
}
return cell
}
}
Unfortunatelly, segues not working. When I clicked on a row, nothing happend. Method shouldPerformSegueWithIdentifier wasn't called.
So, question is, how to use custom cells with xib and using segues?
I am trying to implement an alarm clock app just like apple's Clock app.On the click of Edit button on the left hand side I want to make table enter into Editing mode with red circles on left side of every cell(custom UITableViewCell) And on the click of that red circle want to show "Delete" button/action on the right side.
I have been trying a lot and went through many sites but still could not figure out. Can someone please see what mistake I am making?
I have referred below and many others links:
How to enable swipe to delete cell in a TableView?
UITableViewCell, show delete button on swipe
class SavedAlarmListViewController: UIViewController,UITableViewDataSource,UITableViewDelegate{
#IBOutlet weak var tableView: UITableView!
var alarms = [AlarmDataObject]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
self.navigationItem.leftBarButtonItem = self.editButtonItem()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
refreshList()
}
func refreshList() {
alarms = AlarmsList.sharedInstance.allSavedAlarms()
tableView.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return alarms.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "cell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! AlarmTableViewCell
// custom code to set data ....
return cell
}
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true // all cells are editable
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let alarm = alarms.removeAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
AlarmsList.sharedInstance.removeAnAlarm(alarm)
}
}
}
class AlarmTableViewCell:UITableViewCell {
// IBOutlets
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
You should implement
tableView(_:editingStyleForRowAtIndexPath:) and return .Delete
In swift, if I create a new cocoa touch class of type UITableViewController, I am given this file called TableViewController.swift:
import UIKit
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return 0
}
/*override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath) as UITableViewCell
// Configure the cell...
return cell
}*/
/*
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView!, canEditRowAtIndexPath indexPath: NSIndexPath!) -> Bool {
// Return NO if you do not want the specified item to be editable.
return true
}
*/
/*
// Override to support editing the table view.
override func tableView(tableView: UITableView!, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath!) {
if editingStyle == .Delete {
// Delete the row from the data source
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
*/
/*
// Override to support rearranging the table view.
override func tableView(tableView: UITableView!, moveRowAtIndexPath fromIndexPath: NSIndexPath!, toIndexPath: NSIndexPath!) {
}
*/
/*
// Override to support conditional rearranging of the table view.
override func tableView(tableView: UITableView!, canMoveRowAtIndexPath indexPath: NSIndexPath!) -> Bool {
// Return NO if you do not want the item to be re-orderable.
return true
}
*/
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
}
When I uncomment this provided method:
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath) as UITableViewCell
// Configure the cell...
return cell
}
I am greeted with this compiler error: "Overriding method with selector 'tableView: cellForRowAtIndex path has incomplete type..."
Is there something I can do to fix this or is this just a beta issue?
The tableView:didSelectRowAtIndexPath: method's declaration has changed between Xcode 6 beta 6 and Xcode 6 beta 7. This method now requires non-optional parameters and returns a non-optional UITableViewCell.
So, with Xcode 6 beta 7, you have to set the following lines in your UITableViewController subclasses in order to avoid error messages:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
...
}
instead of:
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
...
}
See Apple Developper Library for more details.