One of my UIViewController with UICollectionView crashes sometime when I swipe very fast. It crashes in let option = self.options[indexPath.row]:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell= collectionView.dequeueReusableCellWithReuseIdentifier("optionCell", forIndexPath: indexPath) as MyCell
let option = self.options[indexPath.row]
return cell
}
I found the problem is that the self.options is not created successfully sometime. I create the self.options in viewWillAppear:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.options = ...
}
There are two possibilities:
The viewWillAppear is not called at all
The viewWillAppear will be called after cellForItemAtIndexPath
Did I miss anything in my code? Thanks
In a default implementation, collectionView:cellForItemAtIndexPath: will get called after viewWillAppear:.
However, collectionView:cellForItemAtIndexPath: is initially triggered by a call to [UICollectionView -layoutSubviews]. You may be doing something else to make this method get called, which explains the behavior you're seeing. If you want, you can set a breakpoint in collectionView:cellForItemAtIndexPath:, and look at the stack trace to see why it's being called.
It appears to me the answer lies in another method for your data source: the number of items you have to present.
I would guess the value you are returning in that data source method is not based on the options array and instead hard coded. That would explain why you are being asked to provide a cell for data you don't have set up yet.
I would work under the assumption that everything needed must be present after viewDidLoad.
For your options array, the safest solution is a getter that calculates the options array when it is not available yet. And of course the count should be taken from the options array, not from another array.
Related
After any row is deleted by deleteRows(at:with:) from my Table View, I cannot get textfield in a reusable custom cell to get focus by becomeFirstResponder() (which is called after insertRows(at:with:) is called). becomeFirstResponder() is always called within cellForRow(at:).
This problem always starts occuring after a row is deleted from the table view by deleteRows(at:with:). Before any row is deleted there is no problem, becomeFirstResponder() can give focus to the textfield as intended.
I made some tests to understand why this problem occurs after a row is deleted... My tests showed an interesting difference:
Case 1: After any row is deleted (i.e. while becomeFirstResponder() returns false), textFieldShouldBeginEditing(_:) is called BEFORE cellForRow(at:) returns. textFieldDidBeginEditing(_:) is never called.
Case 2: On the other hand, before any row is deleted (i.e. while becomeFirstResponder() returns true), textFieldShouldBeginEditing(_:) is called AFTER cellForRow(at:) returns. textFieldDidBeginEditing(_:) is called immediately after textFieldShouldBeginEditing(_:), as expected.
The problem might be related to this difference, but I couldn't solve this mystery after trying & researching for many hours.
In a related Q&A, it is suggested not to reload tableView from within textFieldDidEndEditing(). In my case, deleteRows(at:with:) method are sometimes called from within textFieldDidEndEditing() and sometimes called from tableView(_:commit:forRowAt:) (i.e. by swiping to left). So this does not explain my problem. Another related Q&A might be this one.
getFocus() in CustomCell's class, called from cellForRow(at:) in TableViewController:
func getFocus() {
if let itemTextField = itemTextField {
itemTextField.delegate = self
itemTextField.isUserInteractionEnabled = true
itemTextField.becomeFirstResponder()
}
}
cellForRow(at:):
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = items[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.setCell(item: item)
cell.delegate = self
if insertingRow == true {
if isTempItem(item: item, indexPath: indexPath) {
cell.getFocus()
} else {
print("isTempItem returned false, no need to give focus.")
}
}
return cell
}
Don't call getFocus in cellForRowAt, Insteed of when you insert new row, You can scroll to new row and call getFocus in willDisplayCell.
Hope to help you.
My app was working fine without 3Dtouch implementation; But with the 3Dtouch added the app continues to work great and rotates normally until 3D touch is used (peek or pop);I had a tableViewCell handled peek/pop and the preview delegate.The presentation would be done twice and trigger this in the console:#"Warning: Attempt to present on which is already presenting (null)
I have experienced the same problem. The cause of it is that you are registering the table view cell in cellForRowAtIndexPath.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// myTableViewCell is created or reused here
...
registerForPreviewingWithDelegate(self, sourceView: myTableViewCell)
...
}
This causes the cell to be registered multiple times when it is reused by the table view. But registering the same view multiple times with registerForPreviewingWithDelegate is not allowed and leads to the warning and rotation problems you describe.
See the Apple documentation
You can designate more than one source view for a single registered
view controller, but you cannot designate a single view as a source
view more than once.
The solution is simple. Check if the view is already registered before you register it a second time and unregister if necessary.
private var previewingContexts = [MyTableViewCellClass: UIViewControllerPreviewing]()
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// myTableViewCell is created or reused here
...
if let previewingContext = previewingContexts[myTableViewCell] {
unregisterForPreviewingWithContext(previewingContext)
}
let previewingContext = registerForPreviewingWithDelegate(self, sourceView: myTableViewCell)
previewingContexts[myTableViewCell] = previewingContext
...
}
You can also use the method described here:
http://krakendev.io/peek-pop/
This uses the location parameter which is passed to the delegate to find out which cell was tapped. In this way you just have to register the whole table view and not every single cell. This might be an ever better solution. In my case this was not possible because I have nested collection views inside tableview cells, so it is way more complex.
I have been learning swift through the last few days and I have come across an error that I have been stuck on for quite a while now.
I am attempting to get the selected indexPath so that I can then push data according to which item he selected. I have searched through and tried many different solutions I have found on stack overflow as well as different websites but I am not able to get this figured out still.
The code is below:
#IBOutlet var selectGroceryTable: UITableView!
/* Get size of table */
func tableView(tableView: UITableView, numberOfRowsInSection: Int) ->Int
{
return grocery.count;
}
/* Fill the rows with data */
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let myCell:UITableViewCell = selectGroceryTable.dequeueReusableCellWithIdentifier("groceryListRow", forIndexPath:indexPath) as! UITableViewCell
myCell.textLabel?.text = grocery[indexPath.row];
myCell.imageView?.image = UIImage(named: groceryImage[indexPath.row]);
return myCell;
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
print("Row Selected");
NSLog("Row Selected");
}
Nothing ever prints acting like the function is not being called. However, I do not understand why this would not be called?
Any help would be greatly appreciated!
UPDATE:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
selectGroceryTable.data = self;
selectGroceryTable.delegate = self; //gives error states you can not do this
}
There are a couple of things to check in cases like this:
First, what kind of method is didSelectRowAtIndexPath?
Answer: It's a UITableViewDelegate method. Did you set your view controller up as the delegate of the table view? If not, this method won't get called.
Second, have you made absolutely certain that the method signature is a perfect match for the method from the protocol? A single letter out of place, the wrong upper/lower case, a wrong parameter, and it is a different method, and won't be called. it pays to copy the method signature right out of the protocol header file and then fill in the body to avoid minor typos with delegate methods.
It looks to me like your method signature is correct, so my money is on forgetting to set your view controller up as the table view's delegate.
I want to implement a simple tableView in my Viewcontroller but the output is not complete. The content is just visible in one sometimes in two rows.
The classic things:
The class use this:
class MealOfWeekView: UIViewController, UITableViewDelegate, UITableViewDataSource {...}
I set the delegates
override func viewDidLoad() {
super.viewDidLoad()
self.tableViewFood.delegate = self
self.tableViewFood.dataSource = self
self.tableViewFood.reloadData()
}
I use the right identifier:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("foodIdent", forIndexPath: indexPath) as! FoodTableViewCell
cell.dayLabel?.text = "\(day[indexPath.row])"
return cell
}
return 1 section and return 7 rows
=> I use the first time the Tab Bar Controller, in my first tab there is already a tableView. This one works perfect.
The tableView shows as far as I know the days tuesday, saturday or sunday... don't know, whether the info is important :)
EDIT
So with your help I figured out, that my daylabel is nil.
My FoodTableViewCell
class FoodTableViewCell: UITableViewCell {
#IBOutlet weak var dayLbl: UILabel!
}
I add to my viewDidLoad this line:
self.tableViewFood.registerClass(FoodTableViewCell.self, forCellReuseIdentifier: "foodIdent")
But it doesn't work.
If you need more code, give a sign.
Thank you!
Looks like this:
Your issue is with your custom class FoodTableViewCell, but I would verify the following first.
Confirm that the label is being set with the day of the week for the row index. You can do this by setting a breakpoint or printing out statements such as where you create your cells.
print("dayLabel: (cell.dayLabel?.text)")
print("day[indexPath.row]: (day[indexPath.row]")
Confirm you are registering the FoodTableViewCell with the table view.
Confirm that your subclass FoodTableViewCell's dayLabel property is setup correctly. Try changing the background color so you know it is least being displayed in the UI.
Check that your subclass of UITableViewCell is overriding and setting up the dayLabel for reuse correctly.prepareForReuse()
Background information for working with table views
SOLUTION
I found my answer here
When you use Tab Bar Controller you have to use viewDidAppear or viewWillAppear
this lines worked for me:
override func viewWillAppear(animated: Bool) {
self.tableViewFood.delegate = self
self.tableViewFood.dataSource = self
dispatch_async(dispatch_get_main_queue(), {
self.tableViewFood.reloadData()
})
}
I have a tableView where I want to display different Cells depending on what a variable seguedDisplayMonth is set to. Is this possible and if so can I get any hint on how to do this? I've tried the following but it doesn't seem to work.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Income Cell", forIndexPath: indexPath)
let income = myIncomeArray[indexPath.row]
if seguedDisplayMonth == "All" {
var text = "\(income.money) kr"
cell.textLabel?.text = text
cell.detailTextLabel?.text = income.name
}
return cell
}
I also thought that maybe I need to reload the data after changing the seguedDisplayMonth which gets changed from a different tableView and through a segue.
Call mcTableSwag.reloadData() once seguedDisplayMonth is changed. (Likely call it in the function that actually changes seguedDisplayMonth.
Alternatively you could reload certian cells with some method like reloadVisibleCellsAtIndexPath(...) (Im not sure what it is called exactly, but it should be on the Apple UITableView documentation.
I managed to fix it finally. I will explain how I did it incase anyone runs into the same problem.
I implemented another array myVisibleIncomeArray.
In viewDidLoad() I called a function which does the following:
for inc in myIncomeArray {
if self.monthLabel.text == "All" {
self.myVisibleIncomeArray.append(inc)
totalSum += inc.money
print("Added to myVisible")
}
}
Then I reloadData() and use myVisibleIncomeArray for the other functions.
Not sure if it was the smartest fix, but it's a fix nonetheless.