I have been stuck on this problem for a few days now. I have an image view set as invisible until the long press gesture is triggered. However, I can not wrap my head around on how to make the UIImageView to become visible after such. This is within a Cell in tableView. I have used reloadData(), but the issue is the table loses its place. It scrolls upwards by the time you release your finger. The long gesture triggers a boolean on the database side that toggles saveShow. When data is loaded, the query checks and displays the UIImageView if true. So I believe I would have to reload the data since it is based on the query..? Any help will be greatly appreciated!
class SecondViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIPickerViewDelegate, UIPickerViewDataSource {
let db = DatabaseController()
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var cityPicker: UIPickerView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
cityPicker.delegate = self
cityPicker.dataSource = self
self.tableView.autoresizingMask = UIView.AutoresizingMask.flexibleHeight;
self.db.createTable()
//////////////////////////////////////////////////////////////////
let longPressGesture:UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPress))
longPressGesture.minimumPressDuration = 1.0 // 1 second press
longPressGesture.delegate = self as? UIGestureRecognizerDelegate
self.tableView.addGestureRecognizer(longPressGesture)
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////
Alamofire.request("http://url.com/jsonfile").responseJSON { response in
if (response.result.value != nil) {
let strOutput = JSON(response.result.value ?? "nil")
if let resObj = strOutput.arrayObject {
arrRes2 = resObj as! [[String:AnyObject]]
self.db.addShowsToDatabase(arrRes: arrRes2)
}
}
self.db.deleteOldShows()
self.grabData(id: 1)
self.tableView.reloadData()
}
////////////////////////////////////////////////////////////////////
}
func grabData(id: Int) -> Void {
regionResults = self.db.listAllShows(id: id)
self.tableView.reloadData()
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return cities.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return cities[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
cheapFix = row + 1
grabData(id: row + 1)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if regionResults.isEmpty {
return 1
} else {
return regionResults.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "bandCustomCell") as! BandTableViewCell
if regionResults.isEmpty {
cell.bandPic.image = UIImage(named: "flyer.jpg")
cell.bandSummary?.text = "No Shows Announced!"
cell.bandVenue?.text = "None"
cell.bandDate?.text = "None"
} else {
let show = regionResults[indexPath.row]
//print(try! show.get(Expression<String>("show_summary")))
let strDateFull = try! show.get(Expression<String>("show_date"))
var strDate: Array = ((strDateFull as AnyObject).components(separatedBy: "T"))
// Setting up the date
var dateArr: Array = strDate[0].components(separatedBy: "-")
let dateStr: String = "\(dateArr[1])-\(dateArr[2])-\(dateArr[0])"
// Setting up the time
var timeArr: Array = strDate[1].components(separatedBy: ":")
var timeStr: Int? = Int(timeArr[0])
// Changing military time to standard
if timeStr! > 12 {
timeStr = timeStr! - 12
}
let saved = try! show.get(Expression<Bool>("save_show"))
if (saved == true) {
cell.bandSavedShow.isHidden = false
} else if (saved == false) {
cell.bandSavedShow.isHidden = true
}
cell.bandSummary?.text = "\(try! show.get(Expression<String>("show_summary"))), \(timeStr!):\(timeArr[1])PM"
cell.bandVenue?.text = try! show.get(Expression<String>("venue_name"))
/*if (show["shows_img"] as? String != "null") {
let url = URL(string: show["shows_img"] as! String)
cell.bandPic.kf.setImage(with: url)
} else {*/
cell.bandPic.image = UIImage(named: "flyer.jpg")
//}
cell.bandDate?.text = dateStr
tableView.isScrollEnabled = true
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
myIndex2 = indexPath.row
//performSegue(withIdentifier: "segue2", sender: self)
}
#objc func longPress(_ longPressGestureRecognizer: UILongPressGestureRecognizer) {
if longPressGestureRecognizer.state == UIGestureRecognizer.State.began {
let touchPoint = longPressGestureRecognizer.location(in: self.tableView)
if let indexPath = self.tableView.indexPathForRow(at: touchPoint) {
let show = regionResults[indexPath.row]
let summary = try! show.get(Expression<String>("show_summary"))
let date = try! show.get(Expression<String>("show_date"))
let saved = try! show.get(Expression<Bool>("save_show"))
self.db.toogleSaveShows(summary: summary, date: date, saved: saved)
reload(tableView: self.tableView, indexPath: indexPath)
}
//grabData(id: cheapFix)
}
}
func reload(tableView: UITableView, indexPath: IndexPath) {
//found on stackoverflow
let contentOffset = tableView.contentOffset
tableView.beginUpdates()
tableView.reloadRows(at: [indexPath], with: UITableView.RowAnimation.none)
tableView.setContentOffset(contentOffset, animated: false)
tableView.endUpdates()
}
}
update the longPressGesture method as below:
let touchPoint = longPressGestureRecognizer.location(in: self.tableView)
if let indexPath = self.tableView.indexPathForRow(at: touchPoint) {
let cell = tableView.cellForRow(at: indexPath) as? BandTableViewCell;
cell.bandPic.image = ....//provide image here.
}
Here, it is not required to reload full table, instead just access the cell and set imageview there.
Firstly you must add gesture recogniser on the cell being used instead of the whole table view as in selector method of gesture you are trying to get the indexpath of cell being pressed.
Then try reloading the cell with found indexpath.
self.tableView.reloadRows(at: [indexpath], with: .automatic)
Related
I'm working on an App where you can track your reading progress for Books.
The HomeViewController contains a TableView that lists the books you have added. It has a progress Bar and shows what page you're on. The AddBookController is for adding Data about a book that gets delegated to the HomeViewController and is then listed as a TableView Row. The BookDetailController is shown when you select a Row and is for updating the Page you're on. I'll provide some screenshots.
I'm stuck at changing the "currentPage" property of the TableView Cell once you update it in the BookDetailViewController. I'm able to send the updatedPage to the HomeViewController (where the TableView is) but I don't know how to align the values of the Cells with the new value. My idea was to use an didSet-observer but I can't figure out how to set it up the right way.
Here is my code:
HomeViewController
class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, SendingBookDataProtocol {
var updatedPage = String() {
didSet { // probably at the wrong place but everything I've tried didn't work
print(updatedPage)
tableView?.reloadData()
}
}
var items = [BookItem]()
var item: BookItem?
override func viewDidLoad() {
super.viewDidLoad(){
tableView?.delegate = self
tableView?.dataSource = self
let nib = UINib(nibName: "BookCell", bundle: nil)
tableView?.register(nib, forCellReuseIdentifier: "BookCell")
}
func sendDataToHomeController(bookEntry item:BookItem) {
items.append(item)
tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
items.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let bookDetailVc = self.storyboard?.instantiateViewController(withIdentifier: "BookDetailView") as? BookDetailViewController
let item = items[indexPath.row]
let currentPageInt = Float(item.currentPage)!
let totalPagesInt = Float(item.totalPages)!
let result = Int(totalPagesInt - currentPageInt)
let percentageRead = Int((currentPageInt / totalPagesInt) * 100)
bookDetailVc?.lblName = item.title
bookDetailVc?.lblCurrentPage = Int(Float(item.currentPage)!)
// ...some more Code. Basically just sending the Data to BookDetailViewController
self.navigationController!.pushViewController(bookDetailVc!, animated: true)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BookCell", for: indexPath) as! BookCell
item = items[indexPath.row]
cell.title.text = item?.title
//... and so on
cell.pageNumbers.text = "S. " + item!.currentPage + " / " + item!.totalPages //here currentPage needs to be updated
return cell
}
}
BookDetailViewController
class BookDetailViewController: HomeViewController, UIPickerViewDelegate, UIPickerViewDataSource{
#IBOutlet weak var bookTitle: UILabel!
//...
#IBOutlet weak var numberPicker: UIPickerView!
var lblName = String()
//...
var lblCurrentPage = Int()
override func viewDidLoad() {
super.viewDidLoad()
self.numberPicker.delegate = self
self.numberPicker.dataSource = self
//...
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let valueSelected = pickerData[row] as String
if let homeVc = storyboard?.instantiateViewController(withIdentifier: "getBookData") as? HomeViewController{
homeVc.updatedPage = valueSelected
homeVc.tableView?.reloadData()
}
}
}
AddBookController
protocol SendingBookDataProtocol {
func sendDataToHomeController(bookEntry: BookItem)
}
struct BookItem {
let title,author,currentPage,totalPages:String
let image: UIImage?
}
class AddBookController: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var delegate: SendingBookDataProtocol? = nil
#IBAction func buttonSave(_ sender: Any) {
let bookEntry = BookItem(title: textfieldTitle.text!, author: textfieldAuthor.text!, currentPage: fieldCurrentPage.text!, totalPages: fieldTotalPages.text!, image: bookImage)
self.delegate?.sendDataToHomeController(bookEntry: bookEntry)
dismiss(animated: true, completion: nil)
}
}
// rest should be irrelevant
Here are the screenshots:
You have to create a delegate in BookDetailViewController, like this :
protocol BookDetailDelegate: AnyObject {
func updatePage(for bookItem : BookItem)
}
class BookDetailViewController: HomeViewController, UIPickerViewDelegate, UIPickerViewDataSource {
var item: BookItem
var delegate: BookDetailDelegate?
//...
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let valueSelected = pickerData[row] as String
delegate?.updatePage(valueSelected)
}
}
And then in the HomeViewController:
class HomeViewController {
}
//....
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let bookDetailVc = self.storyboard?.instantiateViewController(withIdentifier: "BookDetailView") as? BookDetailViewController
let item = items[indexPath.row]
bookDetailVc?.item = item
bookDetailVc?.delegate = self
self.navigationController!.pushViewController(bookDetailVc!, animated: true)
}
//.....
extension HomeViewController: BookDetailDelegate {
func updatePage(for bookItem: BookItem) {
let index = item.firstIndex(where: {$0.id = bookItem.id}) //here you should have a UUID or something
item[index] = bookItem
tableView.performBatchUpdates({
self.tableView.reloadRows(at: IndexPath(row: index, section: 0, with: .none)}
}, completion: nil))
}
ableView. I'm currently working on this gym app and I'm hoping to get a little bit of guidance here.
Basically what I want is to be able to swipe the cell and delete the row and the section associated with that row.
When I run my code, this is the error I get
'attempt to delete row 0 from section 1, but there are only 1 sections before the update'
Any help would be much appreciated.
class WorkoutsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, SwipeTableViewCellDelegate, CollapsibleTableSectionDelegate {
let realm = try! Realm()
var workouts : Results<Workouts>?
var days : Results<WeekDays>!
var daysOfWeek : [String] = ["Monday", "Tuesday", "Wednsday", "Thursday", "Friday", "Saturday", "Sunday"]
let picker = UIPickerView()
#IBOutlet weak var WorkoutsTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
WorkoutsTableView.delegate = self
WorkoutsTableView.dataSource = self
picker.delegate = self
picker.dataSource = self
loadCategories()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
tableView.rowHeight = 80.0
//Populate based on the # of workouts in each day.
let day = days[section]
return day.workouts.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return days[section].day
}
func numberOfSections(in tableView: UITableView) -> Int {
return days.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! SwipeTableViewCell
cell.delegate = self
if (days?[indexPath.section]) != nil {
cell.accessoryType = .disclosureIndicator
//Populate with titles of workouts based on section/day of the week.
//cell.textLabel?.text = days?[indexPath.row].workouts[indexPath.row].name
cell.textLabel?.text = days[indexPath.section].workouts[indexPath.row].name
}
return cell
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
guard orientation == .right else { return nil }
let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
self.updateModel(at: indexPath)
}
// customize the action appearance
deleteAction.image = UIImage(named: "delete-icon")
return [deleteAction]
}
func tableView(_ tableView: UITableView, editActionsOptionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeOptions {
var options = SwipeOptions()
options.expansionStyle = .destructive
return options
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
#IBAction func AddWorkoutButton(_ sender: UIButton) {
var textField = UITextField()
textField.becomeFirstResponder()
let alert = UIAlertController(title: "New Workout", message: "Please name your workout...", preferredStyle: .alert)
let addAction = UIAlertAction(title: "Add Workout", style: .default) { (UIAlertAction) in
//Add workout to database
//Create a two dimensional array object in Realm with numbers corresponding to each day of the week.
//Append workouts to the day in the dictionary that the user selects.
let newWorkout = Workouts()
let dow = WeekDays()
dow.day = self.daysOfWeek[self.picker.selectedRow(inComponent: 0)]
newWorkout.name = textField.text!
dow.workouts.append(newWorkout)
self.save(newDay: dow)
}
alert.addTextField { (alertTextField) in
alertTextField.placeholder = "Muscle Group"
textField = alertTextField
alertTextField.inputView = self.picker
}
alert.addAction(addAction)
present(alert, animated: true, completion: nil)
}
func save(newDay: WeekDays){
do {
try realm.write {
realm.add(newDay)
}
} catch {
print("Error saving workout \(error)")
}
WorkoutsTableView.reloadData()
}
func updateModel(at indexPath: IndexPath){
if let workoutForDeletion = self.days?[indexPath.section]{
do {
try self.realm.write {
self.realm.delete(workoutForDeletion)
}
} catch {
print("Error deleting workout, \(error)")
}
}
self.WorkoutsTableView.reloadData()
}
func loadCategories(){
days = realm.objects(WeekDays.self)
WorkoutsTableView.reloadData()
}
#IBAction func EditWorkout(_ sender: UIBarButtonItem) {
}
}
extension WorkoutsViewController : UIPickerViewDelegate, UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return 7
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return daysOfWeek[row]
}
}
class WeekDays : Object {
#objc dynamic var day : String = ""
let workouts = List<Workouts>()
}
class Workouts : Object {
#objc dynamic var name : String = ""
var parentDay = LinkingObjects(fromType: WeekDays.self, property: "workouts")
}
If you are trying to delete a whole section, then your code is correct. Try calling loadCategories instead of reloading your tableView to see if that helps. See updated code below:
Change that line to this instead:
func updateModel(at indexPath: IndexPath){
if let workoutForDeletion = self.days?[indexPath.section] {
do {
try self.realm.write {
self.realm.delete(workoutForDeletion)
}
} catch {
print("Error deleting workout, \(error)")
}
}
self.loadCategories()
}
I have created a tableview which takes date difference and create that number of tableview cell, each table view cell consist of textview and and picker view against textview to pick value. The problem is when i pick value for the 1 position using PickerView then the selected value is also reflected on the other cell
code for the contorller
here hfDetailsArr contains total number of days difference
--function for getting date difference count {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd/MM/yyyy"
let currentCalendar = NSCalendar.current
dateFormatter.date(from: dateFormatter.string(from:date1 as Date))
let temp = currentCalendar.dateComponents([.day], from: dateFormatter.date(from: dateFormatter.string(from:date1 as Date))!, to: dateFormatter.date(from: dateFormatter.string(from:date2 as Date))!)
hfDetailsArr.removeAllObjects();
//print(temp.day!)
if !(temp.day! < 0) && (temp.day! != 0) {
for i in 0 ..< (temp.day! + 1){
let HFDetailsDates = currentCalendar.date(byAdding: .day, value: i, to: date1 as Date)
DataDetails = dateFormatter.string(from: HFDetailsDates!)
hfDetailsArr.add(DataDetails)
ApplyForDate.append( dateFormatter.string(from: HFDetailsDates!) + ",")
}
//print(ApplyForDate)
}
HFDEtailsTable.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if hfDetailsArr.count < 0 && hfDetailsArr.count != 0 {
return 0
}else{
return hfDetailsArr.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : HFDetailsCell! = tableView.dequeueReusableCell(withIdentifier: "cell") as! HFDetailsCell
cell.txtHFDateFeild.text = hfDetailsArr[indexPath.row] as? String
cell.cellDelegate = self
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("indeddsdsd \(indexPath.row)")
}
code for UITableview cell with pickerview inside it
protocol HFDetailsCellDelegate : class {
func ValueSelectedChage(sender:HFDetailsCell ,Id:String ,selectedCell:String)
}
class HFDetailsCell: UITableViewCell,UITextFieldDelegate,UIPickerViewDelegate,UIPickerViewDataSource{
#IBOutlet var txtHFPicker: UITextField!
#IBOutlet var txtHFDateFeild: UITextField!
#IBOutlet var pickerView: UIPickerView! = UIPickerView()
var HfDtailsOneArr = ["Select","Full Day Leave","Half Day Leave"]
var position = 0
//var intHFStatusDetail = ""
/// internal static var HFtableView = UITableView()
weak var cellDelegate: HFDetailsCellDelegate?
//var LeaveDataIDArr = [String?]()
override func awakeFromNib() {
txtHFDateFeild.delegate = self
pickerView.delegate = self
txtHFPicker.inputView = pickerView
pickerView.backgroundColor = UIColor.white
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func txtleaveType(_ sender: UITextField) {
}
//print("buttonclick")
// //UIpickerView
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
//Set number of rows in components
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return self.HfDtailsOneArr.count
}
//Set title for each row
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return HfDtailsOneArr[row]
}
// Update textfield text when row is selected
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
// self.cellDelegate?.ValueSelectedChage(sender:self, Id: HFDetailsLeaveCount(position: row), selectedCell: "")
txtHFPicker.text = HfDtailsOneArr[row]
print("pickerText:\(txtHFPicker.text!)")
}
func HFDetailsLeaveCount(position:Int)-> String {
switch position {
case 0:
return "0"
case 1:
return "8"
case 2:
return "4"
default:
return "nil"
}
}
}
in above image if i select value for date 04/08 then the same value is reflected for 12/08
-- any kind of help will be appreciated. Thank you
in table cell, on picker selection call ValueSelectedChage delegate method
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
self.cellDelegate?.ValueSelectedChage(sender:self, Id: HFDetailsLeaveCount(position: row), selectedCell: HfDtailsOneArr[row])
txtHFPicker.text = HfDtailsOneArr[row]
print("pickerText:\(txtHFPicker.text!)")
}
create a variable in viewcontroller to keep selected value
var hfSelectedDetails: [String: String?]?
in viewcontroller implement the delegate method
func ValueSelectedChage(sender:HFDetailsCell ,Id:String ,selectedCell:String) {
let indexpath = self.tableView.indexPathForRowAtPoint(cell.center)
let key = "\(indexpath.row)"
hfSelectedDetails[key] = selectedCell
....//other code if any
}
and in cellForRow add
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : HFDetailsCell! = tableView.dequeueReusableCell(withIdentifier: "cell") as! HFDetailsCell
cell.txtHFDateFeild.text = hfDetailsArr[indexPath.row] as? String
cell.cellDelegate = self
let key = "\(indexPath.row)"
if let selectedValue = hfSelectedDetails[key] {
cell.txtHFPicker.text = selectedValue
} else {
cell.txtHFPicker.text = nil
}
return cell
}
I am using core data to store events into an agenda app I am making. I have a segment controller that allows the app to sort via date or tags. When I add or delete an event to the table view, nothing is changing in the view and the segment bar breaks, nothin happens. However, I when I restart the app, all the elements are sorted, the segment bar works and all elements are there. Its just when I add or delete elements where it breaks.
heres the code:
import UIKit
import CoreData
class MainVC: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var segControl: UISegmentedControl!
var fetchedResultsController: NSFetchedResultsController<Event>!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
attemptFetchRequest()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
attemptFetchRequest()
tableView.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "eventCell", for: indexPath) as? EventCell {
configureCell(cell: cell, indexPath: indexPath as NSIndexPath)
return cell
}
return EventCell()
}
//calling configure cell in this VC too
func configureCell(cell: EventCell, indexPath: NSIndexPath) {
let event = fetchedResultsController.object(at: indexPath as IndexPath)
cell.configureCell(event: event)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let events = fetchedResultsController.fetchedObjects , events.count > 0 {
let event = events[indexPath.row]
performSegue(withIdentifier: "DetailsVC", sender: event)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "DetailsVC" {
if let dest = segue.destination as? DetailsVC {
if let event = sender as? Event {
dest.eventToEdit = event
}
}
}
}
func numberOfSections(in tableView: UITableView) -> Int {
if let sections = fetchedResultsController.sections {
return sections.count
}
return 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = fetchedResultsController.sections {
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
return 0
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?{
if let sections = fetchedResultsController.sections {
let currentSection = sections[section]
return currentSection.name
}
return nil
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 126
}
func attemptFetchRequest() {
let fetchRequest: NSFetchRequest<Event> = Event.fetchRequest()
let dateSort = NSSortDescriptor(key: "date", ascending: false) //sort by date
let tagSort = NSSortDescriptor(key: "tag", ascending: false) //sort by tag
var key: String!
if segControl.selectedSegmentIndex == 0 {
key = "date"
fetchRequest.sortDescriptors = [dateSort]
} else {
key = "tag"
fetchRequest.sortDescriptors = [tagSort]
}
let controller = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: key, cacheName: nil)
controller.delegate = self
self.fetchedResultsController = controller
do {
try fetchedResultsController.performFetch()
} catch {
let err = error as NSError
print("\(err)")
}
tableView.reloadData()
}
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
//tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.reloadData()
//tableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch(type) {
case.insert:
if let indexPath = newIndexPath {
tableView.insertRows(at: [indexPath], with: .fade)
}
break
case.delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .fade)
}
break
case.update:
if let indexPath = newIndexPath {
if let cell = tableView.cellForRow(at: indexPath) as? EventCell {
configureCell(cell: cell, indexPath: indexPath as NSIndexPath)
}
}
break
case.move:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .fade)
}
if let indexPath = newIndexPath {
tableView.insertRows(at: [indexPath], with: .fade)
}
break
}
}
#IBAction func segControllerChanged(_ sender: Any) {
attemptFetchRequest()
tableView.reloadData()
}
}
Heres my other VC
import UIKit
import CoreData
class DetailsVC: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UITextFieldDelegate {
#IBOutlet weak var image: UIImageView!
#IBOutlet weak var titleTextField: CustomTextField!
#IBOutlet weak var locationTextField: CustomTextField!
#IBOutlet weak var DescTextField: CustomTextField!
#IBOutlet weak var datePicker: UIDatePicker!
#IBOutlet weak var tagPicker: UIPickerView!
private var _tags = ["Meeting", "Breakfast/Lunch/Dinner", "Appointment", "Other"]
var tags: [String] {
return _tags
}
var eventToEdit: Event?
var imgPicker: UIImagePickerController!
override func viewDidLoad() {
super.viewDidLoad()
titleTextField.delegate = self
locationTextField.delegate = self
DescTextField.delegate = self
tagPicker.delegate = self
tagPicker.dataSource = self
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMMM dd, YYYY H:mm a"
if eventToEdit != nil {
loadEventData()
}
imgPicker = UIImagePickerController()
imgPicker.delegate = self
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
let tag = _tags[row]
return tag
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return _tags.count
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
//do something
}
#IBAction func saveBtnPressed(_ sender: UIButton) {
var event: Event! //guarenteed to have Event
let pic = Image(context: context)
pic.image = image.image
if eventToEdit == nil {
event = Event(context: context)
} else {
event = eventToEdit
}
if let title = titleTextField.text {
event.title = title
}
if let location = locationTextField.text {
event.location = location
}
if let desc = DescTextField.text {
event.detail = desc
}
event.toImage = pic
event.tag = _tags[tagPicker.selectedRow(inComponent: 0)]
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMMM dd, YYYY HH:mm"
event.fullDate = datePicker.date as NSDate?
dateFormatter.dateFormat = "MMMM dd, YYYY"
event.date = dateFormatter.string(from: datePicker.date)
dateFormatter.dateFormat = "hh:mm a"
event.time = dateFormatter.string(from: datePicker.date)
ad.saveContext()
_ = navigationController?.popViewController(animated: true)
}
func loadEventData() {
if let event = eventToEdit {
titleTextField.text = event.title
locationTextField.text = event.location
DescTextField.text = event.detail
image.image = event.toImage?.image as? UIImage
if let tag = event.tag {
var i = 0
repeat {
let t = _tags[i]
if t == tag {
tagPicker.selectRow(i, inComponent: 0, animated: false)
}
i += 1
} while (i < _tags.count)
}
}
}
#IBAction func deleteBtnPressed(_ sender: UIBarButtonItem) {
if eventToEdit != nil {
context.delete(eventToEdit!)
ad.saveContext()
}
_ = navigationController?.popViewController(animated: true)
}
#IBAction func imgBtnPressed(_ sender: UIButton) {
present(imgPicker, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let img = info[UIImagePickerControllerOriginalImage] as? UIImage {
image.image = img
}
imgPicker.dismiss(animated: true, completion: nil)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
titleTextField.resignFirstResponder()
locationTextField.resignFirstResponder()
DescTextField.resignFirstResponder()
return true
}
}
thanks!
I assume you are getting a waring in the console along the lines of The number of rows contained in an existing section after the update (0) must be equal to the number of rows contained in that section before the update (0), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted). After you get the error a tableview will no longer do any updates.
These error can occur when you do not update the tableview correctly in response to the fetchedResultsController delegate events. You can test if that is the issue by replacing your implementation of controllerDidChangeContent with tableview.reloadData() and removing everything you are doing in controllerWillChangeContent controllerDidChange. Unfortunately Apple's example code for updating a tableview from a fetchedResultsController is wrong and will not deal with all situations. see my answer here: App crashes after updating CoreData model that is being displayed in a UITableView for a full explanation on how to fix it. But if you are lazy and just want it to work you can simply do reloadData.
I have developed something very similar to DateCell Demo from Apple. https://developer.apple.com/library/ios/samplecode/DateCell/Introduction/Intro.html
The only difference is that I have a UIPickerView instead of a UIDatePicker. And whenever the cell is touched a new UIPickerView is instantiated and added to the cell below once touched again it is removed from the cell. And the part I have problem with is to select a specific item in the UIPickerView.
Here is some code:
var selectedPlatform = "Mac"
var platformPickerDataSource = ["iOS", "Mac", "PC", "Android"]
func updatePlatformPicker()
{
if let indexPath = platformPickerIndexPath {
let associatedPlatformPickerCell = tableView.cellForRowAtIndexPath(indexPath)
if let targetedPlatformPicker = associatedPlatformPickerCell?.viewWithTag(Constants.kPlatformPickerTag) as! UIPickerView? {
targetedPlatformPicker.dataSource = self
targetedPlatformPicker.delegate = self
// I cannot do selectRow(selectedPlatform, inComponent: 0,
animated : true) here since the row since the rows in the UIPickerView are not set
}
}
}
So my questions is, if you refer to the code from Apple, where can I set?
selectRow(selectedPlatform, inComponent: 0,
animated : true)
Edit: (I added the whole code, which is basically the swift version of Apple's DateCell Example and DatePicker replaced with a UIPickerView)
import UIKit
class PickerTableViewController: UITableViewController, UIPickerViewDataSource, UIPickerViewDelegate {
var platformPickerIndexPath : NSIndexPath?
var pickerCellRowHeight: CGFloat = 140
var selectedPlatform = "Mac"
var platformPickerDataSource = ["iOS", "Mac", "PC", "Android"]
private struct Constants {
static let kPlatformCellID: String = "platformCell"
static let kPlatformPickerID: String = "platformPicker"
static let kOtherCell: String = "otherCell"
static let kPlatformRow : Int = 1
static let kPlatformPickerTag : Int = 99
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var rows = 3
if hasInlinePlatformPicker()
{
rows = 4
}
return rows
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell : UITableViewCell?
print("\(indexPath.row)")
var cellID = Constants.kOtherCell
if indexPathHasPicker(indexPath)
{
cellID = Constants.kPlatformPickerID
}
else if indexPathHasPlatform(indexPath)
{
cellID = Constants.kPlatformCellID
}
cell = tableView.dequeueReusableCellWithIdentifier(cellID)
var modelRow = indexPath.row
if (platformPickerIndexPath != nil && platformPickerIndexPath?.row <= indexPath.row) {
modelRow -= 1
}
if cellID == Constants.kPlatformCellID
{
cell?.textLabel?.text = "Select a platform"
cell?.detailTextLabel?.text = selectedPlatform
}
else if cellID == Constants.kOtherCell
{
cell?.textLabel?.text = "Other Cell"
}
return cell!
}
func indexPathHasPicker(indexPath: NSIndexPath) -> Bool
{
return hasInlinePlatformPicker() && platformPickerIndexPath?.row == indexPath.row
}
func indexPathHasPlatform(indexPath : NSIndexPath) -> Bool
{
var hasPlatform = false
if indexPath.row == Constants.kPlatformRow || (hasInlinePlatformPicker() && (indexPath.row == Constants.kPlatformRow + 1))
{
hasPlatform = true
}
return hasPlatform
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)
if cell?.reuseIdentifier == Constants.kPlatformCellID
{
displayInlinePlatformPickerForRowAtIndexPath(indexPath)
}
else
{
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
}
func displayInlinePlatformPickerForRowAtIndexPath(indexPath : NSIndexPath)
{
// display the platform picker inline with the table content
tableView.beginUpdates()
var before = false
if hasInlinePlatformPicker()
{
before = platformPickerIndexPath?.row < indexPath.row
}
let sameCellClicked = (platformPickerIndexPath?.row == indexPath.row + 1)
// remove any picker cell if it exists
if hasInlinePlatformPicker()
{
tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: platformPickerIndexPath!.row, inSection: 0)], withRowAnimation: .Fade)
platformPickerIndexPath = nil
}
if (!sameCellClicked)
{
// hide the old picker and display the new one
let rowToReveal = (before ? indexPath.row - 1 : indexPath.row)
let indexPathToReveal = NSIndexPath(forRow: rowToReveal, inSection: 0)
togglePlatformPickerForSelectedIndexPath(indexPathToReveal)
platformPickerIndexPath = NSIndexPath(forRow: indexPathToReveal.row + 1, inSection: 0)
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)
tableView.endUpdates()
updatePlatformPicker()
}
func updatePlatformPicker()
{
if let indexPath = platformPickerIndexPath {
let associatedPlatformPickerCell = tableView.cellForRowAtIndexPath(indexPath)
if let targetedPlatformPicker = associatedPlatformPickerCell?.viewWithTag(Constants.kPlatformPickerTag) as! UIPickerView? {
targetedPlatformPicker.dataSource = self
targetedPlatformPicker.delegate = self
}
}
}
func togglePlatformPickerForSelectedIndexPath(indexPath: NSIndexPath)
{
tableView.beginUpdates()
let indexPaths = [NSIndexPath(forRow: indexPath.row + 1, inSection: 0)]
// check if 'indexPath' has an attached picker below it
if hasPickerForIndexPath(indexPath)
{
// found a picker below it, so remove it
tableView.deleteRowsAtIndexPaths(indexPaths, withRowAnimation: .Fade)
}
else
{
// didn't find a picker below it, so we should insert it
tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: .Fade)
}
tableView.endUpdates()
}
func hasPickerForIndexPath(indexPath: NSIndexPath) -> Bool
{
var hasPlatformPicker = false
let targetedRow = indexPath.row + 1
let checkPlatformPickerCell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: targetedRow, inSection: 0))
let checkPlatformPicker = checkPlatformPickerCell?.viewWithTag(Constants.kPlatformPickerTag)
hasPlatformPicker = checkPlatformPicker != nil
return hasPlatformPicker
}
func hasInlinePlatformPicker() -> Bool
{
return platformPickerIndexPath != nil
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return (indexPathHasPicker(indexPath) ? pickerCellRowHeight : tableView.rowHeight)
}
// MARK: - Platform Picker View Data Source and Delegates
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return platformPickerDataSource.count
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return platformPickerDataSource[row]
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
print("Platform Selected: \(platformPickerDataSource[row])")
var targetedCellIndexPath: NSIndexPath?
if hasInlinePlatformPicker() {
// inline picker: update the cell's above the picker cell
//
targetedCellIndexPath = NSIndexPath(forRow: platformPickerIndexPath!.row - 1, inSection: 0)
}
let cell = tableView.cellForRowAtIndexPath(targetedCellIndexPath!)
cell?.detailTextLabel?.text = platformPickerDataSource[row]
}
}