Break error while creating a calendar - ios

Have break error Thread 1: EXC_BAD_INSTRUCTION (code=EXC_1386_INVOP, subcode==0x0). No errors with build, just when run, have a break
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let calendars = self.calendars {
return calendars.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
//error happens here
if self.calendars != nil {
let calendarName = self.calendars?[(indexPath as NSIndexPath).row].title
cell.textLabel?.text = calendarName
} else {
cell.textLabel?.text = "Unknown Calendar Name"
}
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! UINavigationController
let addCalendarVC = destinationVC.viewControllers[0] as! AddCalendarViewController
addCalendarVC.delegate = self
}
func calendarDidAdd() {
self.loadCalendars()
self.refreshTableView()
}
}

tableView.dequeueReusableCell(withIdentifier: "Cell")!
You are unwrapping an optional value which might be nil in the first place. Cell might not have been created yet especially if you haven't registered the cell's class with that identifier so it'll crash first time table tries to populate the cell. You should first check if cell is nil:
var cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: "Cell")
}
...

The immediate red flag I see is here:
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
When dequeueing reusable cells, I like to wrap them in guard statements, so my app doesn't crash. It also tells me a bit more information when something does go wrong:
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") else {
print("couldn't dequeue a reusable cell for identifier Cell in \(#function)")
return UITableViewCell()
}
This crash could be for a few reasons. You may have forgotten to register the reuse identifier, but if you're using storyboards this is handled for you. There may simply be a typo or you forgot to enter a reuse identifier for that cell.

Related

How do I use tableView.indexPathForRow(at: touchPoint) with sections

I use sections to load messages(viewForFooterInSection) and rows to load the reply of specific messages if any.
Previously I was using a long press gesture on the tableView to detect a touch on the tableView and return the indexPath using tableView.indexPathForRow(at: touchPoint), however I have not found a similar method to get indexPath of long pressed cell
Can anyone help?
I am not sure why you are going for cell-level gesture when you have already achieved getting indexPath using gesture on tableview. In case you are trying to get cell from indexPath then you can try like
guard let cell = tableView.cellForRow(at: indexPath) else { return }
Anyhow coming to answer for your question, we can do the following way to get indexPath from cell-level.
protocol CustomCellDelegate: AnyObject {
func longPressAction(onCell: CustomCell)
}
class CustomCell: UITableViewCell {
weak var delegate: CustomCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
let lg = UILongPressGestureRecognizer(target: self, action: #selector(longPress))
lg.minimumPressDuration = 0.5
lg.delaysTouchesBegan = true
self.addGestureRecognizer(lg)
}
#objc func longPress(gestureReconizer: UILongPressGestureRecognizer) {
if gestureReconizer.state != UIGestureRecognizer.State.ended {
return
}
delegate?.longPressAction(onCell: self)
}
}
And in your tableview cell for row method, assign the delegate.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as? CustomCell else { return UITableViewCell() }
cell.delegate = self
return cell
}
And confirm to the CustomCellDelegate protocol in your viewController.
extension ViewController: CustomCellDelegate {
func longPressAction(onCell: CustomCell) {
guard let indexPath = tableView.indexPath(for: onCell) else { return }
print(indexPath.section, indexPath.row)
}
}

Make tableView cell with struct and data by indexpath

I already known how to make a basic tableView, below is my basic code
class ShoppingTableViewController: UITableViewController{
var description = ["D1", "299900", "D2", "P201712310000003000", "D3", "ASS+DfDFxSuu", "D10", "901", "D11", "00,46246226301561000110001001", "D12", "20201231123030"]
var dictDescription = ["D10": "901", "D11": "00,46246226301561000110001001", "D3": "ASS+DfDFxSuu", "D12": "20201231123030", "D2": "P201712310000003000", "D1": "299900"]
override func viewDidLoad() {
super.viewDidLoad()
// Xib
tableView.register(UINib(nibName:PropertyKeys.pictureCell , bundle: nil), forCellReuseIdentifier: PropertyKeys.pictureCell)
tableView.register(UINib(nibName:PropertyKeys.infoCell , bundle: nil), forCellReuseIdentifier: PropertyKeys.infoCell)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: PropertyKeys.pictureCell, for: indexPath) as! PictureWithTableViewCell
cell.iconImageView.image = UIImage(named: "amount")
cell.lbDescription.text = "Type"
cell.lbName.text = "Shopping"
return cell
case 1:
let cell = tableView.dequeueReusableCell(withIdentifier: PropertyKeys.infoCell, for: indexPath) as! InfoTableViewCell
cell.lbDescription.text = "Taiwan dollar"
cell.lbName.text = m_dictDescription["D1"]
return cell
case 2:
let cell = tableView.dequeueReusableCell(withIdentifier: PropertyKeys.pictureCell, for: indexPath) as! PictureWithTableViewCell
cell.iconImageView.image = UIImage(named: "info")
cell.lbDescription.text = "BankName"
cell.lbName.text = m_dictDescription["D11"]
return cell
case 3:
if m_dictDescription["D2"] != nil {
let cell = tableView.dequeueReusableCell(withIdentifier: PropertyKeys.infoCell, for: indexPath) as! InfoTableViewCell
cell.lbDescription.text = "orderNumber"
cell.lbName.text = m_dictDescription["D2"]
return cell
}else {
let cell = tableView.dequeueReusableCell(withIdentifier: PropertyKeys.infoCell, for: indexPath) as! InfoTableViewCell
cell.isHidden = true
return cell
}
but this is an unsafe way because i write func number of rows and cell for row as hardcode.
so I want to change tableView composing way from decide cell format first then fill data in (like my basic code) to let data decide my number of rows and cell for row. (use indexPath)
but I got some problems:
I try to write:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let test = self.m_arrDescription[indexPath.row]
cell.lbName.text = test
It works but every cell looks the same while I want to present different cells.
I search some information on internet, perhaps I can use struct and combine m_arrDescription to make tableview cell.
// use struct to decide cell label or image , for example: cell.lbDescription.text ...
struct CellFormat {
var title : String
var image : UIImage
var name : String
}
So far this is what i've written, can anyone please kindly help me to go on?
Be clearly, how do I use [indexPath.row] in this code ?
please create first one enum
enum CellType: String, CaseIterable {
case title, image, name
}
and then use this code in tableview delegate methods.
switch CellType(rawValue: indexPath.row) {
case .title:
break
case .image:
break
case .name:
break
case .none:
break
}
If any problem let me know

Simplifying cellForRowAt in TableView

I am writing a personal iOS app to keep track of some things. my app is working well but i am turning my attention to tidying up the code and cleaning things up. in my tableview, one of the cells is uicollectionview that depending on which collectionviewcell I select, a custom tableviewcell is loaded in the same table. At this time I have about a dozen items in my collectionview that i can select from and in turn one of about a dozen different tableviewcells to load. each cell collects different bits of info.
everything is working as i expect it but i don't like the fact that throughout this tableviewcontroller, i have many repetitive sections based on all the tableviewcells i have to handle
override func viewDidLoad() {
super.viewDidLoad()
// register the various tablecells
tableView.register(UINib(nibName: "eventO2TableViewCell", bundle: nil), forCellReuseIdentifier: "eventO2TableViewCell")
...
tableView.register(UINib(nibName: "eventTmpTableViewCell", bundle: nil), forCellReuseIdentifier: "eventTmpTableViewCell")
tableView.register(UINib(nibName: "eventDXTableViewCell", bundle: nil), forCellReuseIdentifier: "eventDXTableViewCell")
similarly cellForRowAt is very big (i.e a switch statement, a dozen cases , each with a corresponding
switch selectedIndexPath.row { // the index of the uicollectionviewcell
case 1:
let cell = tableView.dequeueReusableCell(withIdentifier: "eventTmpTableViewCell", for: indexPath) as! eventTmpTableViewCell
return cell
...
case 11:
let cell = tableView.dequeueReusableCell(withIdentifier: "eventO2TableViewCell", for: indexPath) as! eventO2TableViewCell
return cell
default:
let cell = tableView.dequeueReusableCell(withIdentifier: "eventDXTableViewCell", for: indexPath) as! eventDXTableViewCell
return cell
}
and in
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
another switch statement with a dozen case evaluation to figure out which cell was used and pull out the information i need to save.
was contemplating the idea that was raised in this similar question Is it possible to store custom UITableViewCell into Array? but curious if there are other suggestions ? still consider myself a beginner in this space. thanks
I created extensions for register and deque in UITableView and identifier in UITableViewCell, here is some sample code:
UITableViewExtension.swift
public extension UITableView {
func register<CellClass: UITableViewCell>(_ cellClass: CellClass.Type) {
register(cellClass, forCellReuseIdentifier: cellClass.identifier)
}
func dequeue<CellClass: UITableViewCell>(
_ cellClass: CellClass.Type,
for indexPath: IndexPath,
setup: ((CellClass) -> Void)? = nil) -> UITableViewCell {
let cell = dequeueReusableCell(withIdentifier: cellClass.identifier, for: indexPath)
if let cell = cell as? CellClass {
setup?(cell)
}
return cell
}
}
UITableViewCell.swift
extension UITableViewCell {
static var identifier: String {
return String(describing: self)
}
}
Then in ViewController.swift
class Cell1: UITableViewCell {
// ...
}
class Cell2: UITableViewCell {
// ...
}
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
let cells = [Cell1.self, Cell2.self]
tableView.register(cells)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return tableView.dequeue(Cell1.self, for: indexPath) { cell in
// customise cell
}
}
}
Note: As you have asked I'm storing UITableViewCells into cells array and get my job done. Even though there are minor changes in our implementations, I hope this will help.
You can create a protocol DequeueInitializable and write its extension like this
import Foundation
import UIKit
protocol DequeueInitializable {
static var reuseableIdentifier: String { get }
}
extension DequeueInitializable where Self: UITableViewCell {
static var reuseableIdentifier: String {
return String(describing: Self.self)
}
static func dequeue(tableView: UITableView) -> Self {
guard let cell = tableView.dequeueReusableCell(withIdentifier: self.reuseableIdentifier) else {
return UITableViewCell() as! Self
}
return cell as! Self
}
static func register(tableView: UITableView) {
let cell = UINib(nibName: self.reuseableIdentifier, bundle: nil)
tableView.register(cell, forCellReuseIdentifier: self.reuseableIdentifier)
}
}
Then in you cell class you confirm to that protocol
class Cell1: UITableViewCell, DequeueInitializable { }
class Cell2: UITableViewCell, DequeueInitializable { }
Now you can register and dequeue cell easily
return Cell1.dequeue(tableView: tableView)
to register
Cell1.register(tableView: tableView)

How to animate UITableViewCell using Swift?

I have a UITableView and inside the tableViewCell I have a UICollectionView.
My requirement is while tapping on a button of first tableView cell I have to animate second tableViewCell.
Below is my code :-
//Cell For Row at indexPath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (0 == indexPath.section) {
let cell = tableView.dequeueReusableCell(withIdentifier: "FirstRowCell") as! FirstRowCell
cell.btnReview.addTarget(self, action: #selector(GotoReview), for: UIControlEvents.touchUpInside)
cell.btnMyProduct.addTarget(self, action: #selector(AnimateCollectionView), for: UIControlEvents.touchUpInside)
//cell.
return cell
} else if ( 1 == indexPath.section) {
let identifier = "TableCollectionCell"
var tableCollectionCell = tableView.dequeueReusableCell(withIdentifier: identifier) as? TableCollectionCell
if(tableCollectionCell == nil) {
let nib:Array = Bundle.main.loadNibNamed("TableCollectionCell", owner: self, options: nil)!
tableCollectionCell = nib[0] as? TableCollectionCell
tableCollectionCell?.delegate = self
}
return tableCollectionCell!
} else {
let identifier = "BrandImagesTableCell"
var brandImagesTableCell = tableView.dequeueReusableCell(withIdentifier: identifier)
if(brandImagesTableCell == nil) {
let nib:Array = Bundle.main.loadNibNamed("BrandImagesTableCell", owner: self, options: nil)!
brandImagesTableCell = nib[0] as? BrandImagesTableCell
}
//brandImagesTableCell.
return brandImagesTableCell!
}
}
In my code you can see:
if (0 == indexPath.section)
In that I have a button target (#selector(AnimateCollectionView)).
I want to animate tableCollectionCell which is at (1 == indexPath.section).
See my AnimateCollectionView method :-
func AnimateCollectionView() {
let identifier = "TableCollectionCell"
var tableCollectionCell = tableView.dequeueReusableCell(withIdentifier: identifier) as? TableCollectionCell
if(tableCollectionCell == nil) {
let nib:Array = Bundle.main.loadNibNamed("TableCollectionCell", owner: self, options: nil)!
tableCollectionCell = nib[0] as? TableCollectionCell
tableCollectionCell?.delegate = self
}
tableCollectionCell?.alpha = 0
UIView.animate(withDuration: 1.50, animations: {
//self.view.layoutIfNeeded()
tableCollectionCell?.alpha = 1
})
}
If you want to animate its change, you can just change some state variables, call tableView.reloadRows(at:with:), and have your cellForRowAt then check those state variables to know what the final cell should look like (i.e. whether it is the "before" or "after" cell configuration).
For example, here is an example, with two cells, toggling the second cell from red to blue cells.
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "RedCell", bundle: nil), forCellReuseIdentifier: "RedCell")
tableView.register(UINib(nibName: "BlueCell", bundle: nil), forCellReuseIdentifier: "BlueCell")
}
#IBAction func didTapButton(_ sender: UIButton) {
isRedCell = !isRedCell
tableView.reloadRows(at: [IndexPath(row: 1, section: 0)], with: .fade)
}
var isRedCell = true
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "ButtonCell", for: indexPath)
return cell
} else if indexPath.row == 1 {
if isRedCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RedCell", for: indexPath)
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "BlueCell", for: indexPath)
return cell
}
}
fatalError("There are only two rows in this demo")
}
}
Now, assuming you really needed to use NIBs rather than cell prototypes, I would register the NIBs with the tableview like above, rather than having cellForRow have to manually instantiate them itself. Likewise, for my cell with the button, I just used a prototype cell in my storyboard and hooked the button directly to my #IBOutlet, avoiding a NIB for that cell entirely.
But all of that is unrelated to your main problem at hand: You can create your cells however you want. But hopefully this illustrates the basic idea, that on the tap of the button, I'm not trying to load any cells directly, but I just update some status variable that cellForRow will use to know which cell to load and then tell the tableView to animate the reloading of that IndexPath.
By the way, I assumed from your example that the two different potential cells for the second row required different NIBs. But if you didn't, it's even easier (use just one NIB and one cell identifier), but the idea is the same, just update your state variable and reload the second row with animation.

How do use different cell styles in a UITableView

I'm creating a view for accessing settings in an app which will be presented in a UITableView. Some cells will have a UITableViewCellAccessoryDisclosureIndicator, I need one with a UISwitch, and another with a UISegmentedControl sort of like this:
In other words, I think I need custom cells. Through a lot of research, I've gotten pretty close but I can't wire up everything I've learned to work together. Here's what I've done:
Created three UITableViewCell classes:
class DisclosureCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
class func defaultIdentifier() -> String {
return NSStringFromClass(self)
}
}
class SegmentedControlCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
class func defaultIdentifier() -> String {
return NSStringFromClass(self)
}
}
class SwitchCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
class func defaultIdentifier() -> String {
return NSStringFromClass(self)
}
}
I have my Sections class and Section struct that will provide the table view sections and each choice available:
class SettingsData {
func getSectionsFromData() -> [Section] {
var settings = [Section]()
let preferences = Section(title: "OPTIONS", objects: ["Formulas", "Lifts", "Units", "Allow 15 reps"])
let patronFeatures = Section(title: "PATRON FEATURES", objects: ["Support OneRepMax"])
let feedback = Section(title: "FEEDBACK", objects: ["Send Feedback", "Please Rate OneRepMax"])
settings.append(preferences)
settings.append(patronFeatures)
settings.append(feedback)
return settings
}
}
struct Section {
var sectionHeading: String
var items: [String]
init(title: String, objects: [String]) {
sectionHeading = title
items = objects
}
}
Finally, my UITableViewController:
class SettingsViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.topItem?.title = "Settings"
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: .Plain, target: self, action: #selector(self.dismissSettings(_:)))
}
func dismissSettings(sender: AnyObject?) {
self.dismissViewControllerAnimated(true, completion: nil)
}
// MARK: - Table view data source
var sections: [Section] = SettingsData().getSectionsFromData()
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sections.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].items.count
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section].sectionHeading
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("DisclosureCell", forIndexPath: indexPath)
cell.textLabel?.text = sections[indexPath.section].items[indexPath.row]
return cell
}
I think what I need help with is:
As I create each cell, how do I determine which of my three types each cell is so I can display them based on type as well as dequeue each type properly
I created the func defaultIdentifier() -> String in each custom class AND specified the reuse identifiers in the Storyboard for each cell type. Do I need to do both?
do I need to register these cells or is that unnecessary because I'm using a storyboard?
I've found a lot of threads about this subject but either they're in Objective-C and/or they don't speak to the particular parts I seem to be stuck on.
Thanks!
UPDATE:
I've updated my override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell as follows:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = tableView.cellForRowAtIndexPath(indexPath)?.reuseIdentifier
if cellIdentifier == "DisclosureCell" {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "DisclosureCell")
cell.textLabel?.text = sections[indexPath.section].items[indexPath.row]
return cell
} else if cellIdentifier == "SegmentedControlCell" {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "SegmentedControlCell")
cell.textLabel?.text = sections[indexPath.section].items[indexPath.row]
return cell
}
else {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "SwitchCell")
cell.textLabel?.text = sections[indexPath.section].items[indexPath.row]
return cell
}
}
I hope this is a step in the right direction but I'm still missing something because I get this error:
2016-07-28 07:15:35.894 One Rep Max[982:88043] Terminating app due to uncaught exception 'NSRangeException', reason: ' -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 0]’
And I need to figure out a way for each cell to know which of the three types it SHOULD be, then based on which type it IS, I can set it up in the code above.
UPDATE 2
I've now got it kind of working by using the indexPath.row:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomTableViewCell", forIndexPath: indexPath) as! CustomTableViewCell
if indexPath.row == 0 {
cell.selectionStyle = .None
cell.textLabel!.text = sections[indexPath.section].items[indexPath.row]
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
return cell
} else if indexPath.row == 1 {
cell.selectionStyle = .None
cell.textLabel!.text = sections[indexPath.section].items[indexPath.row]
//cell.accessoryView = useAuthenticationSwitch
let switchView: UISwitch = UISwitch()
cell.accessoryView = switchView
switchView.setOn(false, animated: false)
return cell
} else {
cell.selectionStyle = .None
cell.textLabel!.text = sections[indexPath.section].items[indexPath.row]
let segmentedControl: UISegmentedControl = UISegmentedControl(items: ["lbs", "kg"])
segmentedControl.selectedSegmentIndex = 0
segmentedControl.addTarget(self, action: nil, forControlEvents: .ValueChanged)
cell.accessoryView = segmentedControl
return cell
}
}
The problem is that by using the indexPath.row, not all of the rows have the correct accessoryView. In the example above, I want the first two rows to have disclosureIndicators, the third one to have the UISegmentedControl, and the fourth to have a UISwitch. Now, I completely understand why my code produces this - it's because the accessoryView is being determined by the cell's position. Instead, I want to be able to tell each cell as it's being created, "You're the cell for this setting, so you get a [fill in the blank] accessoryView". I have custom cell classes created for each type and tried to do what I'm trying to do with those but couldn't figure it out. You may notice that in the code above I went to one custom cell class and reuse identifier.
I suppose I could do things like if indexPath.row == 0 | 1 { blah, blah } but that approach won't hold up through each section. If I try to deal with each section like this, then the code will get messier than it already is. There must be a smarter way.
Is there?
I'd suggest you to use static cells (not prototype) for such screens like settings.
You can change type of cells in storyboard.
After you can assign IBOutlets to all controls and make them interact with your model.

Resources