Swift UITableView cellForRowAt indexPath doesn't get called on tableView.reloadData() after view appeared for second time - swift

The summary of my problem is; when the tableView.reloadData() is called upon firestore's local changes on the first time the view loads up it works and updates as it supposed to. However, after I switch forth and back with other viewControllers, although initially on viewDidAppear() tableView get's reloaded, upon local changes it no longer does so.
I've included a simpler version of my project to better explain and to make it reproducable;
Class Item
let db = Firestore.firestore()
static var list = [String:[String:Any]]()
static var listenerSet = Bool()
static var listener: ListenerRegistration!
func setListener(completion: #escaping (String) -> Void) {
if !Item.listenerSet {
print("Attaching item document listener.")
Item.listener = db.collection("Items").document("default").addSnapshotListener({ (document, error) in
let source = document!.metadata.hasPendingWrites ? "Local" : "Server"
print("Updating item data from the \(source).")
Item.listenerSet = true
Item.list = document?.get("List") as! [String:[String:Any]]
completion("Item data is set")
})
} else {
completion("Item listener already exists")
}
}
func add(itemID:String) {
let itemRef = db.collection("Items").document("default")
itemRef.setData([
"List": [
itemID : [
"Count": FieldValue.increment(1.0),
]]
], merge:true)
}
FirstViewController:UIViewController
let item = Item()
var itemList = [[String:Any]]()
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var switchBar: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
// Setting the table views
tableView.delegate = self
tableView.dataSource = self
}
override func viewDidAppear(_ animated: Bool) {
print("View Did appear") // prints OK
item.setListener() { (result) in
print(result)
loadData()
}
}
func loadData() {
itemList.removeAll()
var counter = 0
for (id,data) in Item.list {
itemList.append(data)
itemList[counter]["ID"] = id
counter += 1
}
tableView.reloadData()
}
#IBAction func switchBarChanged(_ sender: UISegmentedControl) {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
switch switchBar.selectedSegmentIndex {
case 1:
let vc = storyBoard.instantiateViewController(identifier: "secondViewController")
show(vc, sender: self)
default:
break
}
}
Extension FirstViewController: UITableViewDataSource,UITableViewDelegate
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("These are items:\(itemList)")
print(itemList.count)
print(tableView.bounds.height)
return itemList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell") as! ItemCell
let itemID = itemList[indexPath.row]["ID"] as! String
let itemName = itemList[indexPath.row]["Name"] as! String
let itemCount = itemList[indexPath.row]["Count"] as! Double
cell.itemID = itemID
cell.itemName.text = itemName
cell.itemCount.text = String(itemCount)
print("Items within the tableView \(itemList)") // prints only in the first run whenever there is an upload, or when refreshed.
return cell
}
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let add = UIContextualAction(style: .normal, title: "Add", handler: { (action, view, completionHandler) in
item.add(itemID:(itemList[indexPath.row]["ID"] as! String))
completionHandler(true)
})
add.image = UIImage(systemName: "plus")
let configuration = UISwipeActionsConfiguration(actions: [add])
configuration.performsFirstActionWithFullSwipe = true
return configuration
}
Class ItemCell:UITableViewCell
var itemID = String()
#IBOutlet weak var itemName: UILabel!
#IBOutlet weak var itemCount: UILabel!
SecondViewController:UIViewController
#IBOutlet weak var switchBar: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func switchBarChanged(_ sender: UISegmentedControl) {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
switch switchBar.selectedSegmentIndex {
case 0:
let vc = storyBoard.instantiateViewController(identifier: "firstViewController")
show(vc, sender: self)
default:
break
}
}
Now this is the flow I'm having issue on;
Application loads, FirstViewController loads, viewDidAppear kicks in. tableView loads.
When swiped right, the value in firestore server increments by one, snapshot listener kicks in, tableview reloads and everything works as it supposed to be.
After I switch to SecondViewController and come back to FirstViewController, viewDidAppear kicks in tableView loads.
This time however, when I swipe right again, the value in firestore server still increments by one and the snapshot listener kicks in, updating the Item.list. static var ItemList gets updated, and tableView does reload, however this time, only numberOfRowsInSection method works as it prints the count and updated itemList, then nothing happens cellForRowAt doesn't work and rows don't get updated.
Now at this stage if I go SecondViewController and comeback to FirstViewController the rows are updated, but when swiped right still no avail.
What am I missing here ? Thanks all for your replies.

Related

How to input the value from UI button in Teble View cell to the variable declared in Table View Controller?

I want to save the value of filled button(●) into the array "q.answer[indexPath.row]" about each question "q.question[indexPath.row]).
currentQuizButtonIndex is currently renewed every time when ◯ changes to ● by tapping. However, I have no idea how to save in to variable q which is declared in TableViewController.
View Controller display
Code about QuizCell.swift (TableCell which is about 5 buttons and UIlabel.)
import UIKit
import Foundation
protocol QuizCellDelegate {
func quizCellDidChangeCurrentButtonIndex(_ cell: QuizCell, index: Int)
}
class QuizCell: UITableViewCell {
var currentQuizButtonIndex: Int = 0 {
didSet {
let value = self.currentQuizButtonIndex
self.updateCurrentQuizButton(value)
if let delegate = self.delegate {
delegate.quizCellDidChangeCurrentButtonIndex(self, index: value)
}
}
}
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet var answerButtons: [UIButton]!
var delegate: QuizCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
//print("ここまできてるか確認")
// Initialization code
}
#IBAction func didTapQuizButton(_ sender: UIButton) {
if let index = self.answerButtons.firstIndex(of: sender){
self.currentQuizButtonIndex = index
delegate?.quizCellDidChangeCurrentButtonIndex(self, index: index)
print(index)
}
}
private func updateCurrentQuizButton(_ currentIndex: Int){
for (index, answerButton) in self.answerButtons.enumerated(){
if index == currentIndex {
answerButton.setTitle("●", for: .normal)
} else {
answerButton.setTitle("○", for: .normal)
}
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Code about View Controller
import UIKit
class AnswerQuizViewController: UIViewController, UITableViewDelegate {
var q: QuestionSeries!
#IBOutlet weak var quizTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
quizTableView.dataSource = self
quizTableView.delegate = self
// cell xibファイルを使うときは書く必要があるやつ。
// quizTableView.register(UINib(nibName: K.Cells.QuizCellNibName, bundle: nil), forCellReuseIdentifier: K.Cells.QuizCellIdentifier)
quizTableView.register(UINib(nibName: "QuizCell", bundle: nil), forCellReuseIdentifier: "QuizCellIdentifier")
// Do any additional setup after loading the view.
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
// override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// // Get the new view controller using segue.destination.
// // Pass the selected object to the new view controller.
//// if segue.identifier == K.Segue.checkResult {
//// let resultViewController = segue.destination as! ResultViewController
//// answerQuizViewController.q =
//// print(answerQuizViewController.q)
// }
}
// MARK: - quizTableViewのアレンジ
extension AnswerQuizViewController: UITableViewDataSource, QuizCellDelegate {
func quizCellDidChangeCurrentButtonIndex(_ cell: QuizCell, index: Int) {
if let indexPath = self.quizTableView.indexPath(for: cell){
self.q.question[indexPath.row].answer = index
print(index)
}else{
print("ここきてます")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return q.question.count
//print(q.question.count)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let question = q.question[indexPath.row]
let cell = quizTableView.dequeueReusableCell(withIdentifier: K.Cells.QuizCellIdentifier, for: indexPath) as! QuizCell
cell.questionLabel.text = question.text
// print(question.text)
return cell
}
}
It is also helpful if you have any idea of implementing this by alternative way.
Thanks.
How about you create a static array and store your data into that array.
when the button is tapped you can append it into that static array.
Create a new file. Just a basic "Swift file".
struct structName {
static var qArray: [String] = []
}
Then append data by:
structName.q.append()
Finally get your data trough:
structName.q[index]

How to save array data locally

Here is my code. I want to learn how to save my dataSource for the like button of each cell. So that when the user leaves the app and returns later, the same cells that were liked remain liked.
This is new code from my last post. Since I don't want to keep making new arrays, I can try and locally save my dataSource variable which stores each cells Status of being liked or not.
ViewController -
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
var dataSource: [TableModel] = []
var updatedCell = TableViewCell()
var updatedIndex = Int()
override func viewDidLoad() {
super.viewDidLoad()
overrideUserInterfaceStyle = .light
self.dataSource = Array(repeating: TableModel(isLiked: false), count: 8)
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.showsVerticalScrollIndicator = false
self.tableView.reloadData()
}
#IBAction func buttonSelected(_ sender: Any) {
// Update Cell for which UIButton (Like Button) was tapped.
dataSource[(sender as AnyObject).tag].isLiked = !dataSource[(sender as AnyObject).tag].isLiked
let indexPath = IndexPath(row: (sender as AnyObject).tag, section: 0)
tableView.reloadRows(at: [indexPath], with: .automatic)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
// Saved updatedCell & updatedIndex variables for delegate pattern. To Update this VC's cells data when edited on secondVC (moreInfoViewController) in a way, made them accessible outside this function
// To Get Specific TableView Cell the user is interacting with.
updatedCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
updatedIndex = indexPath.row
// Go to Second VC and Send cell tapped data to next view
let vc = (storyboard?.instantiateViewController(identifier: "secondVC") as? moreInfoViewController)!
vc.delegate = self
// Get Status of Liked Button in the cell the user tapped and display if the user liked it previously in the SecondVC
let isLiked = dataSource[indexPath.row].isLiked
if isLiked {
// print("Liked")
vc.isLiked = true
} else {
// print("Not Liked")
vc.isLiked = false
}
present(vc, animated: true)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 8
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
// cell.moreBtn.isUserInteractionEnabled = false
cell.likeBtn.tag = indexPath.row
// cell.likeBtn.addTarget(self, action: #selector(buttonSelected(_:)), for: .touchUpInside)
// Get Each Cell Liked Button Status and display if the user liked or not Liked each cell
let isLiked = dataSource[indexPath.row].isLiked
if isLiked {
// User liked the post
cell.likeBtn.setImage(UIImage(named: "liked"), for: UIControl.State.normal)
} else {
// User Unliked the post
cell.likeBtn.setImage(UIImage(named: "unLiked"), for: UIControl.State.normal)
}
return cell
}
}
// Conform VC to protocol (VC2Delegate) located in "Structs.swift" File
extension ViewController: VC2Delegate {
func likeStatusDidChange(_ vc2: moreInfoViewController, to title: Bool) {
// set the text of the table cell here...
dataSource[updatedIndex].isLiked = !dataSource[updatedIndex].isLiked
let indexPath = IndexPath(row: updatedIndex, section: 0)
tableView.reloadRows(at: [indexPath], with: .automatic)
}
}
moreInfoViewController -
import UIKit
class moreInfoViewController: UIViewController {
#IBOutlet var backBtn: UIButton!
#IBOutlet var titleLabel: UILabel!
#IBOutlet var locationLabel: UILabel!
#IBOutlet var theImage: UIImageView!
#IBOutlet var mainlikeBtn: UIButton!
#IBOutlet var mainTypeLbl: UILabel!
var currentID:String = ""
var isLiked = Bool()
weak var delegate: VC2Delegate?
override func viewDidLoad() {
super.viewDidLoad()
overrideUserInterfaceStyle = .light
self.navigationController?.setNavigationBarHidden(true, animated: true)
styles()
// "isLiked" variable to display whether or user liked this event
if (isLiked == true) {
// is Liked
mainlikeBtn.setImage(UIImage(named: "liked"), for: UIControl.State.normal)
} else {
// Not Liked
mainlikeBtn.setImage(UIImage(named: "unLiked"), for: UIControl.State.normal)
}
}
// Heart/Like Button Action. User can like event in this VC with this button and it will tell the firstVC (ViewController) to update "Like Status" there also
#IBAction func likeBtnAction(_ sender: Any) {
if (isLiked == true) {
// is Liked
isLiked = false
mainlikeBtn.setImage(UIImage(named: "unLiked"), for: UIControl.State.normal)
} else {
isLiked = true
mainlikeBtn.setImage(UIImage(named: "liked"), for: UIControl.State.normal)
}
// When User interacts with like Button, this function gets called that tells the firstVC (ViewController) to update as well.
// likeStatusDidChange function is located at the bottom of the (ViewController) with extension ViewController.
delegate?.likeStatusDidChange(self, to: true)
}
// Go Back To FirstVC (ViewController)
#IBAction func previousVC(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
func styles() {
titleLabel.numberOfLines = 1
titleLabel.adjustsFontSizeToFitWidth = true
locationLabel.numberOfLines = 1
locationLabel.adjustsFontSizeToFitWidth = true
backBtn.transform = CGAffineTransform(rotationAngle: -CGFloat.pi / 2)
theImage.layer.borderColor = UIColor.black.cgColor
theImage.layer.borderWidth = 2
theImage.layer.cornerRadius = 10
}
}
When you get array from userdefaults it gives you Any type array so you need to cast it to Int array and check it nil or not before you use. So you can use this code block.
if let savedDataKey = UserDefaults.standard.array(forKey: "savedDataKey") as? [Int] {
print(savedDataKey)
}

How can I get user to input text in a text field inside a xib cell in swift

I have created a xib cell with two textfields. The textfield are connected as IBoutlets to the xib swift file. The xib cell is registered on the view controller as a Nib. When I run the app on a simulator I cannot get the keyboard to show up. Secondly, the textfields are not editable at all, that is the cursor doesn't even show. I would like to get help with this as I have tried using a label the same thing happens. I'm just not sure how to fix this one. I'm not getting errors on build. I have installed IQKeyboardManager from cocoapods. Thanks in advance.
Heres the code:
import UIKit
class DictionaryCell: UITableViewCell {
#IBOutlet weak var DictBubble: UIView!
#IBOutlet weak var DictSymbolTextfield: UITextField!
#IBOutlet weak var SymbolMeaningTextfield: UITextView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Here is the view controller code:
import UIKit
import Firebase
class PersonalDreamDictionaryViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var dictionaryTextfield: UITextField!
let db = Firestore.firestore()
var dreamDictionary: [DreamDictionary] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
tableView.dataSource = self
tableView.delegate = self
title = K.Dictionary.appName
tableView.register(UINib(nibName: K.Dictionary.cellNibName, bundle: nil), forCellReuseIdentifier:
K.Dictionary.cellIdentifier)
loadDictionaryList()
}
func loadDictionaryList() {
db.collection(K.Fstore1.collectionName)
.order(by: K.Fstore1.date)
.addSnapshotListener{ (QuerySnapshot, error) in
self.dreamDictionary = []
if let e = error {
print("There was an issue retrieving data from Firestore. \(e)")
} else {
if let snapshotDocuments = QuerySnapshot?.documents {
for doc in snapshotDocuments {
//print(doc.data())
let data = doc.data()
if let symbolRetrieved = data[K.Fstore1.symbol] as? String, let meaningRetrieved = data[K.Fstore1.meaning] as? String {
let newDictList = DreamDictionary(mysymbol: symbolRetrieved, mymeaning: meaningRetrieved)
self.dreamDictionary.append(newDictList)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
}
}
}
}
extension PersonalDreamDictionaryViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dreamDictionary.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let dictCell = tableView.dequeueReusableCell(withIdentifier: K.Dictionary.cellIdentifier, for: indexPath)
as! DictionaryCell
if let symbolInput = dictCell.DictSymbolTextfield.text, let meaningInput = dictCell.SymbolMeaningTextfield.text { db.collection(K.Fstore1.collectionName).addDocument(data: [
K.Fstore1.symbol: symbolInput,
K.Fstore1.meaning: meaningInput,
K.Fstore1.date: Date().timeIntervalSince1970
]) { (error) in
if let e = error {
print("There was an issue saving data to firesore, \(e)")
} else {
print("Successfully saved data.")
}
}
}
return dictCell
}
}
extension PersonalDreamDictionaryViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
print(indexPath.row)
}
}

Tab Bar Item hidden behind tableview / not being shown?

I have an empty view with a tab bar pictured below, when i load a routine a table appears containing the contents, however it seems to overlay the tab bar killing off app navigation. Its not sized in the storyboard to overlay it and its constraint locked to not do so, so im unsure why this is happening, pics of the issue and VC's code below:
VC Code:
import Foundation
import UIKit
import CoreData
class RoutineController: UIViewController, UITableViewDataSource, UITableViewDelegate {
// MARK: - DECLARATIONS
#IBAction func unwindToRoutine(segue: UIStoryboardSegue) {}
#IBOutlet weak var daysRoutineTable: UITableView!
#IBOutlet weak var columnHeaderBanner: UIView!
#IBOutlet weak var todaysRoutineNavBar: UINavigationBar!
#IBOutlet weak var addTOdaysRoutineLabel: UILabel!
let date = Date()
let dateFormatter = DateFormatter()
let segueEditUserExerciseViewController = "editExerciseInRoutineSegue"
//This is the selected routine passed from the previous VC
var selectedroutine : UserRoutine?
// MARK: - VIEWDIDLOAD
override func viewDidLoad() {
super.viewDidLoad()
setupView()
daysRoutineTable.delegate = self
daysRoutineTable.dataSource = self
view.backgroundColor = (UIColor.customBackgroundGraphite())
dateFormatter.dateStyle = .short
dateFormatter.dateFormat = "dd/MM/yyyy"
let dateStr = dateFormatter.string(from: date)
todaysRoutineNavBar.topItem?.title = dateStr + " Routine"
}
// MARK: - VIEWDIDAPPEAR
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.daysRoutineTable.reloadData()
self.updateView()
}
// MARK: - TABLE UPDATE COMPONENTS
private func setupView() {
updateView()
}
// MARK: - TABLE SETUP
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let count = self.selectedroutine?.userexercises?.count
{
print("exercises: \(count)")
return count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? TodaysRoutineTableViewCell else {
fatalError("Unexpected Index Path")
}
cell.backgroundColor = UIColor.customBackgroundGraphite()
cell.textLabel?.textColor = UIColor.white
configure(cell, at: indexPath)
return cell
}
// MARK: - VIEW CONTROLER ELEMENTS VISIBILITY CONTROL
fileprivate func updateView() {
var hasUserExercises = false
if let UserExercise = self.selectedroutine?.userexercises {
hasUserExercises = UserExercise.count > 0
}
addTOdaysRoutineLabel.isHidden = hasUserExercises
columnHeaderBanner.isHidden = !hasUserExercises
daysRoutineTable.isHidden = !hasUserExercises
}
// MARK: - SETTING DATA FOR A TABLE CELL
func configure(_ cell: TodaysRoutineTableViewCell, at indexPath: IndexPath) {
if let userExercise = selectedroutine?.userexercises?.allObjects[indexPath.row]
{
print("\((userExercise as! UserExercise).name)")
cell.todaysExerciseNameLabel.text = (userExercise as! UserExercise).name
cell.todaysExerciseRepsLabel.text = String((userExercise as! UserExercise).reps)
cell.todaysExerciseSetsLabel.text = String((userExercise as! UserExercise).sets)
cell.todaysExerciseWeightLabel.text = String((userExercise as! UserExercise).weight)
}
}
}
requested table constraints
Debug hierarchy
The Segue that sends the user back to the view that looses its tab bar
if segue.identifier == "addToTodaySegue" {
let indexPath = workoutTemplateTable.indexPathForSelectedRow
let selectedRow = indexPath?.row
print("selected row\(selectedRow)")
if let selectedRoutine = self.fetchedResultsController.fetchedObjects?[selectedRow!]
{
if let todaysRoutineController = segue.destination as? RoutineController {
todaysRoutineController.selectedroutine = selectedRoutine
}
}
}
I also feel perhaps the viewDidAppear code may cause the issue, perhaps the super class?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.daysRoutineTable.reloadData()
self.updateView()
Updated storyboard image
I suspect you need to embed your viewController in a UINavigationController.
Consider the following setup:
I suspect your setup is like the upper one:
TapBar -> ViewController -show segue-> ViewController
Which results in a hidden tapbar, like in your description:
While the bottom setup:
TapBar -> NavigationCntroller -rootView-> ViewController -show segue-> ViewController
results in:
which is what you want, how I understood.
Update
It's hard to see. The screenshot of your Storyboard is in pretty low resulution, but the segues look wrong. Double check them. A Segue of type show (e.g push) looks like this:
Also clear project and derived data. Segue type changes sometime are ignored until doing so.
Try calling this self.view.bringSubviewToFront(YourTabControl).
The previous suggestion should work. But the content at the bottom part of tableview will not be visible as the tabbar comes over it. So set the bottom constraint of tableview as the height of tabbar.

pass core data between views swift

I'm currently working on an inventory app that will hold information for two entities. The first entity holds two attributes "site name" and "site address". second entity holds "itemName" "itemQuantity" and "item picture". I'm able to save the data to core data. I'm having issues with fetching the data right now. Does any one know how to pass data between the view controllers.
I'm uploading the site view controller, detail view controller, and my new item view controller.
class SiteTableViewController: UIViewController {
var managedContext : NSManagedObjectContext = CoreDataHelper.sharedInstance.myContext
var sites : [Site]? = [Site]()
//variable to hold selected site
var selectedSite : Site?
let myCellIdentifier = "cell"
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
// Do any additional setup after loading the view.
sites = [Site]()
reloadData()
automaticallyAdjustsScrollViewInsets = false
//let barButtonItem = UIBarButtonItem(barButtonSystemItem: .Compose, target: self, action: "addSite:")
//self.navigationItem.leftBarButtonItems = [barButtonItem]
}
//load new data into table
func reloadData() {
let fetchRequest = NSFetchRequest(entityName: "Site")
let sortDescriptor = NSSortDescriptor(key: "siteName", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
do {
sites = try self.managedContext.executeFetchRequest(fetchRequest) as! [Site]
tableView.reloadData()
} catch {
print("Error fetching objects")
}
tableView.reloadData()
}
override func viewWillAppear(animated: Bool) {
reloadData()
}
}
extension SiteTableViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(myCellIdentifier, forIndexPath: indexPath)
let site = sites![indexPath.row]
cell.textLabel?.text = site.valueForKey("siteName") as? String
cell.detailTextLabel?.text = site.valueForKey("siteAddress") as? String
return cell
}
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let logItemToDelete = sites![indexPath.row]
sites?.removeAtIndex(indexPath.row)
managedContext.deleteObject(logItemToDelete)
do {
try managedContext.save()
} catch {
print("Could not save or delete object")
}
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sites!.count
}
// func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//
// selectedSite = sites![indexPath.row]
//
// }
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "detailSegue" {
var nextVC = segue.destinationViewController as! DetailTableViewController
var selectedItem : Site = sites![self.tableView.indexPathForSelectedRow!.row] as Site
fetchSite = selectedItem.valueForKey("siteName") as? NSManagedObject
nextVC.valueToPass = selectedItem.siteName!
}
}
}
here is my detail view controller
import UIKit
import CoreData
public var fetchSite : NSManagedObject?
class DetailTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var holdSiteData: NSMutableArray = NSMutableArray()
var managedContext : NSManagedObjectContext = CoreDataHelper.sharedInstance.myContext
#IBOutlet weak var tableView: UITableView!
var sites : [Site]!
//string to hold fetchResults controll
//open string to hold siteName
var valueToPass = ""
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "\(valueToPass)"
// Do any additional setup after loading the view.
tableView.delegate = self
tableView.dataSource = self
automaticallyAdjustsScrollViewInsets = false
tableView.estimatedRowHeight = 75.0
tableView.rowHeight = UITableViewAutomaticDimension
retrieveItemRelationship()
}
override func viewWillAppear(animated: Bool) {
retrieveItemRelationship()
}
func retrieveItemRelationship() {
// fetchSite.setValue(NSSet(keyCommands), forKey: "test")
let fetchRequest = NSFetchRequest(entityName: "SiteItem")
let sortDescriptors = NSSortDescriptor(key: "itemName", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptors]
do {
let result = try fetchSite?.managedObjectContext?.executeRequest(fetchRequest)
} catch let error as NSError {
print(error)
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! TableViewCell
// let cellInfo = fetchedResultsController.objectAtIndexPath(indexPath) as! SiteInfo
cell.cellName?.text = "Test"
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return holdSiteData.count
}
}
this is my last view to save the data to. I figured i need to pass the data here then associate the two entities so that I can save it correctly. For instance, for site name "Staples Center". I need to pull this over to the final view controller then save the information to this object.
import UIKit
import CoreData
class ItemNewViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
//var managedObjectContext : NSManagedObjectContext!
var managedContext : NSManagedObjectContext = CoreDataHelper.sharedInstance.myContext
//categories array for picker view
var categories = ["Building Materials", "Electrical", "Cleaning Products",
"Tools & Hardware", "Plumbing", "Paint", "Appliances", "Other"]
//textFields
#IBOutlet weak var itemNameTextField: UITextField!
#IBOutlet weak var quantityTextField: UITextField!
#IBOutlet weak var imageView: UIImageView!
//Label properties
#IBOutlet weak var newItemLabel: UILabel!
#IBOutlet weak var quantityLabel: UILabel!
#IBOutlet weak var categoryLabel: UILabel!
//pickerLabel
#IBOutlet weak var pickerListIcon: UIPickerView!
//clicker property label and textlabel for number
#IBOutlet weak var increaseNumberClicker: UIStepper!
#IBOutlet weak var numberFieldClicker: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
//label property customize view code
newItemLabel.layer.masksToBounds = true
newItemLabel.layer.borderWidth = 2.0
newItemLabel.layer.borderColor = UIColor.whiteColor().CGColor
newItemLabel.layer.cornerRadius = 6
//label property customize view code
quantityLabel.layer.masksToBounds = true
quantityLabel.layer.borderWidth = 2.0
quantityLabel.layer.borderColor = UIColor.whiteColor().CGColor
quantityLabel.layer.cornerRadius = 6
//lable property customize view code
categoryLabel.layer.masksToBounds = true
categoryLabel.layer.borderWidth = 3.0
categoryLabel.layer.borderColor = UIColor.whiteColor().CGColor
categoryLabel.layer.cornerRadius = 6
//label picker customize view
pickerListIcon.layer.masksToBounds = true
pickerListIcon.layer.borderWidth = 3.0
pickerListIcon.layer.borderColor = UIColor.whiteColor().CGColor
pickerListIcon.layer.cornerRadius = 6
pickerListIcon.layer.backgroundColor = UIColor.blackColor().CGColor
self.pickerListIcon.dataSource = self
self.pickerListIcon.delegate = self
numberFieldClicker.enabled = false
//UIStepper value
increaseNumberClicker.wraps = true
increaseNumberClicker.autorepeat = true
increaseNumberClicker.layer.masksToBounds = true
increaseNumberClicker.layer.borderWidth = 3.0
increaseNumberClicker.layer.borderColor = UIColor.whiteColor().CGColor
increaseNumberClicker.layer.cornerRadius = 6
increaseNumberClicker.layer.backgroundColor = UIColor.blackColor().CGColor
//below code declares a variable that inherits from UITapGesture
//this allows the image view to be an action
//*Make sure you enable user interaction in storyBoard very IMPORTANT!
let imageTapRecognizer = UITapGestureRecognizer(target: self, action: "enableImage")
self.imageView.addGestureRecognizer(imageTapRecognizer)
}
// below function allows the camera view to be accessible
func enableImage(){
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera) {
let cameraView = UIImagePickerController()
cameraView.sourceType = UIImagePickerControllerSourceType.Camera
cameraView.delegate = self
self.presentViewController(cameraView, animated: true, completion: nil)
}
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) {
self.imageView.image = image
picker.dismissViewControllerAnimated(true, completion: nil)
}
//function used to increase UIStepper. increment by integers of one
#IBAction func clickerIncreaser(sender: UIStepper) {
numberFieldClicker.text = Int(sender.value).description
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return categories.count
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return categories[row]
}
func pickerView(pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
//passing the categorories array into the new constant myCategoriesArray
let myCategoriesArray = categories[row]
let myTextColor = NSAttributedString(string: myCategoriesArray, attributes: [NSForegroundColorAttributeName: UIColor.whiteColor()])
return myTextColor
}
//save data to core data
#IBAction func saveButton(sender: AnyObject) {
if (itemNameTextField.text?.isEmpty)! {
let displayAlertController = UIAlertController(title: "Form Incomplete", message: "Please provide item with a name", preferredStyle: .Alert)
let okButton : UIAlertAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
displayAlertController.addAction(okButton)
self.presentViewController(displayAlertController, animated: true, completion: nil)
} else {
//declare site object and object
let siteInfoEntity = NSEntityDescription.entityForName("SiteItem", inManagedObjectContext: self.managedContext)
//declare item and item object
let siteObject = SiteItem(entity: siteInfoEntity!, insertIntoManagedObjectContext: self.managedContext)
//convert String to Integer
let quantityNumber: Int? = Int(quantityTextField.text!)
siteObject.setValue(itemNameTextField.text?.capitalizedString, forKey: "itemName")
siteObject.setValue(quantityNumber, forKey: "itemQnty")
fetchSite?.setValue(NSSet(object: siteObject), forKey: "reverseItems")
self.managedContext.saveOrLogError()
self.navigationController?.popViewControllerAnimated(true)
}
}
}
Once again if someone can point me in the right direction for saving my relationships to core data and retrieving these relationships would be great. Still new to core data so need all the help i can get!
Thank you
Your detail controller can have a property Site. In prepareForSegue you pass the selected Site object to the detail controller.
Both the site list and the list of items should be backed by NSFetchedResultsController, not in-memory arrays.