I am building an iOS application for my year 12 project and it entails users being able to record information about a bill they have received and having the ability to take a photo of the bill and to save it. I have used core data for my users data to be saved to. I have currently been able to get the photo taken by the user to be able to be seen on a seperate screen when the user selects a bill. Where I am having trouble is that the YouTube video I used has only shown me how to display only 1 specific photo in position zero, as shown on line 39. I need help in getting a different image being displayed dependent on what bill the user selects. For example, if a user taps a water bill, on the viewing screen, they will see a water bill. Then if the user taps a gas bill, on the viewing screen, they will see the gas bill. Currently, what is happening is regardless of whether the user selects the gas or water bill, a water bill is displayed. I have tried to explain this the best I can, if there are any other concerns, please let me know.
Thank you for your assistance
import UIKit
import CoreData
class ViewControllerViewing: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Getting keyboard to retract
}
func fetchImage() -> [Bravo] {
var fetchingImage = [Bravo]()
let fetchRequest = NSFetchRequest<Bravo>(entityName: "Bravo")
do {
fetchingImage = try context.fetch(fetchRequest)
} catch {
print("Error while fetching the image")
}
return fetchingImage
}
// Outlets
#IBOutlet weak var imgDisplay: UIImageView!
var selectedImage: String?
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
#IBOutlet weak var lblBill: UILabel!
// Actions
#IBAction func btnDisplay(_ sender: Any) {
let arr = DataBaseHelper.shareInstance.getAllImages()
// Got to get the numbered one change dependent on what bill is pressed
self.imgDisplay.image = UIImage(data: arr[0].photo!) // Only position zero photo displays
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
// Screen before photo screen
Class CarViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number bills
return bills.count
}
// Editing function
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Selected Person
let bravo = self.bills[indexPath.row]
// Create alert
let alert = UIAlertController(title: "Edit Bill", message: "Edit Provider", preferredStyle: .alert)
alert.addTextField()
// Edit text feild to edit provider
let txtProvider = alert.textFields![0]
// Configure button handler
let saveButton = UIAlertAction(title: "Save", style: .default) {
(action) in
// Edit provider property
bravo.provider = txtProvider.text
// Save new data
do {
try self.context.save()
}
catch {
}
// Refetch data
self.fetchBravo()
}
// Add button
alert.addAction(saveButton)
// Show alert
self.present(alert, animated: true, completion: nil)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//ensure the cell identifier has been labelled "cell"
// let bravob = self.bills[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
// Recieves information from core data
let display = self.bills[indexPath.row]
// Displays the provider in the title
cell.textLabel?.text = display.provider
// Displays the date in the subtitle
cell.detailTextLabel?.text = display.date
//How to add photo into tableview research
return cell
}
func fetchBravo() {
// Fetch data from core data to display in a tableview
do {
let request = Bravo.fetchRequest() as NSFetchRequest<Bravo>
// Set the filtering and sorting on the request This is the sorting method (For setting car filter, try to adjust here) Look at predecite https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Predicates/AdditionalChapters/Introduction.html
let pred = NSPredicate(format: "category CONTAINS '0'")
request.predicate = pred
self.bills = try context.fetch(request)
// Sort descripter
let sort = NSSortDescriptor(key: "provider", ascending: true)
request.sortDescriptors = [sort]
DispatchQueue.main.async {
self.tblCar.reloadData()
}
}
catch {
}
}
// Swipe to delete function https://www.youtube.com/watch?v=gWurhFqTsPU&list=RDCMUC2D6eRvCeMtcF5OGHf1-trw&start_radio=1
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
// Create swipe action
let action = UIContextualAction(style: .destructive, title: "Delete") { (action, view, completionHandler) in
// Which bill to removes
let BravoRemove = self.bills[indexPath.row]
// Remove Bill
self.context.delete(BravoRemove)
// Save updated delete
do {
try self.context.save()
}
catch{
}
// Refetch new data
self.fetchBravo()
}
// Return swipe action
return UISwipeActionsConfiguration(actions: [action])
}
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var bills:[Bravo] = []
override func viewDidLoad() {
super.viewDidLoad()
tblCar.delegate = self
tblCar.dataSource = self
tblCar.reloadData()
// Do any additional setup after loading the view.
}
// Outlets
#IBOutlet weak var txtDate: UITextField!
#IBOutlet weak var txtBill: UITextField!
#IBOutlet weak var tblCar: UITableView!
#IBOutlet weak var segCategory: UISegmentedControl!
// Actions
#IBAction func btnSearch(_ sender: Any) {
// Re-Fetch data for core data
self.fetchBravo()
print(bills)
tblCar.reloadData()
}
Related
I'm creating a quiz app with custom cells that include a label of questions and then an answer coming from a UISegmentedControl.
The values of the segmentedcontrols get changed when scrolling and this leads to an inaccurate score. I understand that this is due to UITableView reusing cells.
My tableview's datasource in my main vc is simply the labels for all my questions coming from a plist file.
The code for my custom tableviewcell class is
class QuestionsTableViewCell: UITableViewCell {
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet weak var selection: UISegmentedControl!
var question: String = "" {
didSet {
if (question != oldValue) {
questionLabel.text = question
}
}
}
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
}
//Just for testing
#IBAction func segmentChanged(_ sender: UISegmentedControl) {
print("value is ", sender.selectedSegmentIndex);
}
}
where the View is stored in an .XIB file.
And the code for my main vc is
class ViewController: UIViewController, UITableViewDataSource {
let questionsTableIdentifier = "QuestionsTableIdentifier"
#IBOutlet var tableView:UITableView!
var questionsArray = [String]();
var questionsCellArray = [QuestionsTableViewCell]();
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let path = Bundle.main.path(forResource:
"Questions", ofType: "plist")
questionsArray = NSArray(contentsOfFile: path!) as! [String]
tableView.register(QuestionsTableViewCell.self,
forCellReuseIdentifier: questionsTableIdentifier)
let xib = UINib(nibName: "QuestionsTableViewCell", bundle: nil)
tableView.register(xib,
forCellReuseIdentifier: questionsTableIdentifier)
tableView.rowHeight = 108;
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return questionsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(
withIdentifier: questionsTableIdentifier, for: indexPath)
as! QuestionsTableViewCell
let rowData = questionsArray[indexPath.row]
cell.question = rowData
return cell
}
#IBAction func calculate(_ sender: UIButton) {
var score = 0
for cell in tableView.visibleCells as! [QuestionsTableViewCell] {
score += cell.selection.selectedSegmentIndex
}
let msg = "Score is, \(score)"
print(msg)
}
#IBAction func reset(_ sender: UIButton) {
for cell in tableView.visibleCells as! [QuestionsTableViewCell] {
cell.selection.selectedSegmentIndex = 0;
}
}
}
What I'd like to do is just keep track of all 'selection' changes of the Questions cells in an array, and then use that array in cellForRowAt. I'm just confused as to how i can dynamically keep track of changes from a view in another class. I'm new to Swift and would like to solve this is a proper MVC fashion. Thanks
Instead of a simple string array as data source create a class holding the text and the selected index
class Question {
let text : String
var answerIndex : Int
init(text : String, answerIndex : Int = 0) {
self.text = text
self.answerIndex = answerIndex
}
}
Declare questionArray as
var questions = [Question]()
Populate the array in viewDidLoad with
let url = Bundle.main.url(forResource: "Questions", withExtension: "plist")!
let data = try! Data(contentsOf: url)
let questionsArray = try! PropertyListSerialization.propertyList(from: data, format: nil) as! [String]
questions = questionsArray.map {Question(text: $0)}
In the custom cell add a callback and call it in the segmentChanged method passing the selected index, the property question is not needed, the label is updated in cellForRow of the controller
class QuestionsTableViewCell: UITableViewCell {
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet weak var selection: UISegmentedControl!
var callback : ((Int) -> ())?
#IBAction func segmentChanged(_ sender: UISegmentedControl) {
print("value is ", sender.selectedSegmentIndex)
callback?(sender.selectedSegmentIndex)
}
}
In cellForRow add the callback and update the model in the closure
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: questionsTableIdentifier, for: indexPath) as! QuestionsTableViewCell
let question = questions[indexPath.row]
cell.questionLabel.text = question.text
cell.selection.selectedSegmentIndex = question.answerIndex
cell.callback = { index in
question.answerIndex = index
}
return cell
}
To reset the segmented controls in the cells set the property in the model to 0 and reload the table view
#IBAction func reset(_ sender: UIButton) {
questions.forEach { $0.answerIndex = 0 }
self.tableView.reloadData()
}
Now you could calculate the score directly from the model instead of the view.
Don't try to use cells to hold information. As the user scrolls through your table view, cells that scroll out of view will get recycled and their field settings will be lost. Also, newly dequeued cells will have the settings from the last time they were used.
You need to refactor your code to read/write information into a data model. Using an array of Structs as a data model is a reasonable way to go. (Or, as vadian suggests in his answer, and array of Class objects, so you get reference semantics.)
You have an IBAction segmentChanged() in your custom cell class. The next trick is to notify the view controller when the user changes the selection, and to update cells when you set them up in cellForRowAt.
I suggest defining a protocol QuestionsTableViewCellProtocol, and have the view controller conform to that protocol:
protocol QuestionsTableViewCellProtocol {
func userSelected(segmentIndex: Int, inCell cell: UITableViewCell)
}
}
Add a delegate property to your QuestionsTableViewCell class:
class QuestionsTableViewCell: UITableViewCell {
weak var delegate: QuestionsTableViewCellProtocol?
//The rest of your class goes here...
}
Update your cell's segmentChanged() method to invoke the delegate's userSelected(segmentIndex:inCell:) method.
In your view controller's cellForRowAt, set the cell's delegate to self.
func userSelected(segmentIndex: Int, inCellCell cell: UITableViewCell) {
let indexPath = tableView.indexPath(for: cell)
let row = indexPath.row
//The code below assumes that you have an array of structs, `dataModel`, that
//has a property selectedIndex that remembers which cell is selected.
//Adjust the code below to match your actual array that keeps track of your data.
dataModel[row].selectedIndex = segmentIndex
}
Then update cellforRowAt() to use the data model to set the segment index on the newly dequeued cell to the correct index.
Also update your calculate() function to look at the values in your dataModel to calculate the score, NOT the tableView.
That's a rough idea. I left some details out as "an exercise for the reader." See if you can figure out how to make that work.
I am making an application that allows the user to keep order at usernames and passwords. Currently, I am having a problem when the user quit the app, the data or TableViewCell is not stored or showing up. I am using an array.
I assume because the data is not getting stored after. Is CoreData or UserDefaults a simple solution for this? I want to avoid Firebase.
Can someone explain or show, how to implement CoreData/UserDefaults into the code?
I have searched a lot but I simply find it hard to understand how to apply it in my code, especially with arrays and TableViewCell. Help is deeply appreciated.
Pictures are below.
Here is my code:
MainViewController:
class MainViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView:UITableView?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return informasjoner.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "InformasjonTableViewCell") as! InformasjonTableViewCell
let informasjon:Informasjon = informasjoner[indexPath.row];
if(informasjon.image != nil){
cell.imageViewInfo?.image = informasjon.image
} else {
cell.imageViewInfo?.image = UIImage(named: informasjon.imageName!)
}
cell.epostLabel?.text = informasjon.labelEpost
cell.passordLabel?.text = informasjon.labelPassord
cell.applikasjonLabel?.text = informasjon.labelApplikasjon
return cell
}
// EDIT / UPDATE CELL
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let informasjon = informasjoner[indexPath.row]
let deleteAction = UITableViewRowAction(style: .default, title: "Delete"){
(action, indexPath) in
self.deleteAction(informasjon: informasjon, indexPath: indexPath)
}
//call delete action
deleteAction.backgroundColor = .red;
return [deleteAction]
}
private func deleteAction(informasjon: Informasjon, indexPath: IndexPath){
let alert = UIAlertController(title: "Delete",
message: "Are u sure?",
preferredStyle: .alert)
let deleteAction = UIAlertAction(title: "Yes",
style: .default){ (action) in
informasjoner.remove(at: indexPath.row)
self.tableView?.deleteRows(at: [indexPath], with: .automatic)
}
let cancelAction = UIAlertAction(title: "No",
style: .default,
handler: nil);
alert.addAction(deleteAction);
alert.addAction(cancelAction);
present(alert, animated: true);
}
}
InformasjonTableViewCell:
import UIKit
class InformasjonTableViewCell: UITableViewCell {
#IBOutlet weak var imageViewInfo: UIImageView?
#IBOutlet weak var epostLabel: UILabel?
#IBOutlet weak var passordLabel: UILabel?
#IBOutlet weak var applikasjonLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
AddInfoVC:
import UIKit
class AddInfoVC: UIViewController, UITextFieldDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
#IBOutlet weak var txtEpost:UITextField?
#IBOutlet weak var ImageInfo:UIImageView?
#IBOutlet weak var txtPassord:UITextField?
#IBOutlet weak var txtApplikasjon: UITextField!
var newInfo = Informasjon()
#IBAction func btnSave(sender:UIButton){
print("Press Save!")
if(newInfo.image == nil || newInfo.labelEpost?.count == 0 || newInfo.labelPassord?.count == 0 || newInfo.labelApplikasjon?.count == 0){
let alertController = UIAlertController(title: "Alert", message: "Please set", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default){
(action) in
}
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil);
} else{
informasjoner.append(newInfo);
navigationController?.popViewController(animated: true)
let mainViewController = self.navigationController?.topViewController as? MainViewController
mainViewController?.tableView?.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
//Tap to ImageView
let tapGestureToImageView = UITapGestureRecognizer(target: self, action: #selector(tapToImageView(sender:)))
tapGestureToImageView.numberOfTapsRequired = 1
ImageInfo?.isUserInteractionEnabled = true;
ImageInfo?.addGestureRecognizer(tapGestureToImageView);
self.txtEpost?.delegate = self;
self.txtPassord?.delegate = self;
self.txtApplikasjon?.delegate = self;
}
Models
Informasjon.swift:
import Foundation
import UIKit
class Informasjon {
var imageName: String?
var image: UIImage?
var labelEpost: String?
var labelPassord: String?
var labelApplikasjon: String?
convenience init(imageName:String, labelEpost:String, labelPassord:String,labelApplikasjon:String ) {
self.init()
self.imageName = imageName
self.labelEpost = labelEpost
self.labelPassord = labelPassord
self.labelApplikasjon = labelApplikasjon
}
}
Picture 1 -> How the Application looks from the Storyboard.
Picture 2 -> How the Application looks from Simulator.
UserDefaults usually is the simplest database to use. If you have only one array to store/retrieve then stick with it. If you have much more information go with something bigger like CoreData/Realm.
let array = []()
let defaults = UserDefaults.standard
defaults.set(array, forKey: "SavedStringArray")
Feel free to check out following answer and tutorial.
how to save and read array of array in NSUserdefaults in swift?
https://medium.com/#nimjea/userdefaults-in-swift-4-d1a278a0ec79
If it's still hard to understand I would recommend to check the Objective-C version of UserDefaults - NSUserDefaults. Even if you are not familiar with Objective-C syntax it's probably easier to understand.
Save string to the NSUserDefaults?
Now the question comes where to use these methods when working with UITableView.
Whenever you want to change or retrieve the data from the store (UserDefaults in our case).
For the instance you can change the stored array calling tableView(:cellForRowAt:) methods in the tableView(:didSelectRowAt:), or you can retrieve the data in tableView(_:cellForRowAt:)
For using Realm check the official website
https://realm.io/docs/swift/latest
You should use NSUserDefaults to store preferences only – just preferences for your app’s functionality and nothing else. Anything else like user data should be stored someplace else. Especially if said that is sensitive and you don’t want to risk your user’s data as the plist file is directly available in the apps directory.
One more disadvantage for using UserDefaults to store app data is all data will be stored as a property list file. The entire file is read in and written out as a whole, so if you use UserDefaults to store a large amount of data that only changes in parts, you will be wasting a lot of time doing unnecessary I/O.
Rather, CoreData/Realm is always a better option to persist app data.
To learn how to store in CoreData -
https://www.raywenderlich.com/173972/getting-started-with-core-data-tutorial-2
https://in.udacity.com/course/ios-persistence-and-core-data--ud325
https://medium.com/xcblog/core-data-with-swift-4-for-beginners-1fc067cca707
https://code.tutsplus.com/tutorials/core-data-and-swift-managed-objects-and-fetch-requests--cms-25068
To learn how to store in Realm -
https://realm.io/docs/tutorials/realmtasks/
https://www.appcoda.com/realm-database-swift/
https://www.raywenderlich.com/112544/realm-tutorial-getting-started
I have a table view (controller: MetricsViewController) which gets updated from a CoreData database. I have used prototype cells (MetricsViewCell) which I have customized for my needs. It contains a segmented control, a UIView (metricsChart, which is used to display a chart - animatedCircle), and some UILabels.
MetricsViewCell:
class MetricsViewCell: UITableViewCell {
var delegate: SelectSegmentedControl?
var animatedCircle: AnimatedCircle?
#IBOutlet weak var percentageCorrect: UILabel!
#IBOutlet weak var totalPlay: UILabel!
#IBOutlet weak var metricsChart: UIView! {
didSet {
animatedCircle = AnimatedCircle(frame: metricsChart.bounds)
}
}
#IBOutlet weak var recommendationLabel: UILabel!
#IBOutlet weak var objectType: UISegmentedControl!
#IBAction func displayObjectType(_ sender: UISegmentedControl) {
delegate?.tapped(cell: self)
}
}
protocol SelectSegmentedControl {
func tapped(cell: MetricsViewCell)
}
MetricsViewController:
class MetricsViewController: FetchedResultsTableViewController, SelectSegmentedControl {
func tapped(cell: MetricsViewCell) {
if let indexPath = tableView.indexPath(for: cell) {
tableView.reloadRows(at: [indexPath], with: .none)
}
}
var container: NSPersistentContainer? = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer { didSet { updateUI() } }
private var fetchedResultsController: NSFetchedResultsController<Object>?
private func updateUI() {
if let context = container?.viewContext {
let request: NSFetchRequest<Object> = Object.fetchRequest()
request.sortDescriptors = []
fetchedResultsController = NSFetchedResultsController<Object>(
fetchRequest: request,
managedObjectContext: context,
sectionNameKeyPath: "game.gameIndex",
cacheName: nil)
try? fetchedResultsController?.performFetch()
tableView.reloadData()
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Object Cell", for: indexPath)
if let object = fetchedResultsController?.object(at: indexPath) {
if let objectCell = cell as? MetricsViewCell {
objectCell.delegate = self
let request: NSFetchRequest<Object> = Object.fetchRequest()
...
...
}
}
}
return cell
}
When a user selects one of the segments in a certain section's segmented control, MetricsViewController should reload the data in that particular row. (There are two sections with one row each). Hence, I've defined a protocol in MetricsViewCell to inform inform my controller on user action.
Data is being updated using FetchedResultsTableViewController - which basically acts as a delegate between CoreData and TableView. Everything is fine with that, meaning I am getting the correct data into my TableView.
There are two issues:
I have to tap segmented control's segment twice to reload the data in the row where segmented control was tapped.
The table scrolls back up and then down every time a segment from segmented control is selected.
Help would be very much appreciated. I've depended on this community for a lot of issues I've faced during the development and am thankful already :)
For example, in Animal Recognition section, I have to hit "Intermediate" two times for its row to be reloaded (If you look closely, the first time I hit Intermediate, it gets selected for a fraction of second, then it goes back to "Basic" or whatever segment was selected first. Second time when I hit intermediate, it goes to Intermediate). Plus, the table scroll up and down, which I don't want.
Edit: Added more context around my usage of CoreData and persistent container.
Instead of using indexPathForRow(at: <#T##CGPoint#>) function to get the indexPath object of cell you can directly use indexPath(for: <#T##UITableViewCell#>) as you are receiving the cell object to func tapped(cell: MetricsViewCell) {} and try to update your data on the UI always in main thready as below.
func tapped(cell: MetricsViewCell) {
if let lIndexPath = table.indexPath(for: <#T##UITableViewCell#>){
DispatchQueue.main.async(execute: {
table.reloadRows(at: lIndexPath, with: .none)
})
}
}
Your UISegmentedControl are reusing [Default behaviour of UITableView].
To avoid that, keep dictionary for getting and storing values.
Another thing, try outlet connection as Action for UISegmentedControl in UIViewController itself, instead of your UITableViewCell
The below code will not reload your tableview when you tap UISegmentedControl . You can avoid, delegates call too.
Below codes are basic demo for UISegmentedControl. Do customise as per your need.
var segmentDict = [Int : Int]()
override func viewDidLoad() {
super.viewDidLoad()
for i in 0...29 // number of rows count
{
segmentDict[i] = 0 //DEFAULT SELECTED SEGMENTS
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! SOTableViewCell
cell.mySegment.selectedSegmentIndex = segmentDict[indexPath.row]!
cell.selectionStyle = .none
return cell
}
#IBAction func mySegmentAcn(_ sender: UISegmentedControl) {
let cellPosition = sender.convert(CGPoint.zero, to: tblVw)
let indPath = tblVw.indexPathForRow(at: cellPosition)
segmentDict[(indPath?.row)!] = sender.selectedSegmentIndex
print("Sender.tag ", indPath)
}
I working on a project that is written in swift 3.0. My requirement is to save data (on CoreData) that I enter on some text fields and populate one of those attributes in to a table view, thus once a row is selected I wants to update that record (re-assign values on my text fields and save).
Basically I have an entity named "Task" and it got three attributes, and I wants to populate one of those attributes(called "name") that I have saved on core data, in to a table view. Hence when I wants to edit the data that I entered, I tap on a row and it'll direct me to the ViewController where I initially entered those data. However when I click the back button without saving the data it'll duplicate the array and populate in my table view. how can I stop this. The code of the table view class as follow.
import UIKit
import CoreData
class TableViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var stores = [Store] ()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.reloadData()
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
let request = NSFetchRequest <NSFetchRequestResult> (entityName: "Store")
request.returnsObjectsAsFaults = false
do {
let results = try context.fetch(request) as! [Store]
// check data existance
if results.count>0 {
print(results.count)
for resultGot in results {
if let expName = resultGot.name {
print("expence name is :", expName)
stores += [resultGot]
print("my array is : \(stores)")
}
}
self.tableView.reloadData()
}
}catch{
print("No Data to load")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return stores.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell ()
let store = stores [indexPath.row]
cell.textLabel?.text = store.name
//cell.textLabel?.text = myExpensesArray[indexPath.row]
return cell
}
#IBAction func nextButtonPressed(_ sender: AnyObject) {
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "editStore", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "editStore"{
let v = segue.destination as! ViewController
let indexPath = self.tableView.indexPathForSelectedRow
let row = indexPath?.row
v.store = stores[row!]
}
}
This is happening because already loaded elements are present inside your array. When you came back to previously loaded ViewController its method viewWillAppear, viewDidAppear called everytime according to the viewController's life cycle.
You need to clear your previously loaded array using removeAll() method when you came back.
Use below code:
override func viewDidAppear(_ animated: Bool) {
stores.removeAll() // clears all element
let request = NSFetchRequest <NSFetchRequestResult> (entityName: "Store")
request.returnsObjectsAsFaults = false
do {
let results = try context.fetch(request) as! [Store]
// check data existance
if results.count>0 {
print(results.count)
for resultGot in results {
if let expName = resultGot.name {
print("expence name is :", expName)
stores += [resultGot]
print("my array is : \(stores)")
}
}
self.tableView.reloadData()
}
}catch{
print("No Data to load")
}
}
You populate your tableView in the viewDidAppear method which is execute everytime the view is shown (either for first time or coming back from a anbother detail view controller).
You either
populate it once by moving the populate code to viewDidLoad
or clean (remove all objects from) the stores before repopulating it, if you need fresh data to be shown. so before for resultGot in results
insert something like
stores = []
I'm new to Swift and coding, only about a month in and I am trying to build some simple UItableViews to set up some CoreData attributes.
The structure of my CoreData entities is a many-to-many relationship between the Workouts Entity and the Exercises Entity. (I would post some images but I don't have a high enough rep!)
What I'm trying to achieve is a simple settings menu where users can create a Workout and then create a series of Exercises within that Workout by using tableViews with a navigationController at the top (just like the iOS settings menu)
Currently I've got it working so that you can add some Workouts and then you can go to the Excercises tableView to add some Exercises. However I haven't been able to do two things:
1) How can I ensure that when a user adds an Exercise that it is assigned to the correct Workout that they've selected from the previous tableView?
2) How can I ensure that the Exercise tableView only shows Exercises of the Workout that they've selected?
I've done a lot of reading around the subject and think the answer is something to do with the segue from Workouts to Exercises (to pass the workoutName to the Exercises tableView by using a delegate? And then using NSPredicate to limit the Exercises shown to the Workout selected?
I'm not 100% sure, but any help would be greatly appreciated!
Here's the code for the segue from Workouts to Exercises:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "excerciseMaster" {
let ExcerciseMasterTableViewController = segue.destinationViewController as UIViewController
let indexPath = tableView.indexPathForSelectedRow()!
let workout = workouts[indexPath.row]
let destinationTitle = workout.workoutName
ExcerciseMasterTableViewController.title = destinationTitle
}
}
Here's the code for my Exercises tableViewController:
import UIKit
import CoreData
class ExcerciseMasterTableViewController: UITableViewController {
// Create an empty array of Excercises
var excercises = [Excercises]()
// Retreive the managedObjectContext from AppDelegate
let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext
override func viewDidLoad() {
super.viewDidLoad()
// Use optional binding to confirm the managedObjectContext
if let moc = self.managedObjectContext {
}
fetchExcercises()
}
func fetchExcercises() {
let fetchRequest = NSFetchRequest(entityName: "Excercises")
// Create a sort descriptor object that sorts on the "excerciseName"
// property of the Core Data object
let sortDescriptor = NSSortDescriptor(key: "excerciseName", ascending: true)
// Set the list of sort descriptors in the fetch request,
// so it includes the sort descriptor
fetchRequest.sortDescriptors = [sortDescriptor]
if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [Excercises] {
excercises = fetchResults
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// How many rows are there in this section?
// There's only 1 section, and it has a number of rows
// equal to the number of excercises, so return the count
return excercises.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("Excercise Cell", forIndexPath: indexPath) as UITableViewCell
// Get the Excercises for this index
let excercise = excercises[indexPath.row]
// Set the title of the cell to be the title of the excercise
cell.textLabel!.text = excercise.excerciseName
cell.detailTextLabel!.text = "\(excercise.sets)x\(excercise.reps)"
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
return cell
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if(editingStyle == .Delete ) {
// Find the Excercise object the user is trying to delete
let excerciseToDelete = excercises[indexPath.row]
// Delete it from the managedObjectContext
managedObjectContext?.deleteObject(excerciseToDelete)
// Refresh the table view to indicate that it's deleted
self.fetchExcercises()
// Tell the table view to animate out that row
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
save()
}
}
// MARK: UITableViewDelegate
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let excercise = excercises[indexPath.row]
}
let addExcerciseAlertViewTag = 0
let addExcerciseTextAlertViewTag = 1
#IBAction func addExcerciseButton(sender: AnyObject) {
var namePrompt = UIAlertController(title: "Add Excercise",
message: "Enter Name",
preferredStyle: .Alert)
var excerciseNameTextField: UITextField?
namePrompt.addTextFieldWithConfigurationHandler {
(textField) -> Void in
excerciseNameTextField = textField
textField.placeholder = "Title"
}
namePrompt.addAction(UIAlertAction(title: "Ok",
style: .Default,
handler: { (action) -> Void in
if let textField = excerciseNameTextField {
self.saveNewItem(textField.text, workoutName: "Workout A")
}
}))
self.presentViewController(namePrompt, animated: true, completion: nil)
}
func saveNewItem(excerciseName : String, workoutName: String) {
// Create the new excercise item
var newExcercise = Excercises.createExcerciseInManagedObjectContext(self.managedObjectContext!, name: excerciseName)
println(excerciseName)
println(workoutName)
// Update the array containing the table view row data
self.fetchExcercises()
// Animate in the new row
// Use Swift's find() function to figure out the index of the newExcercise
// after it's been added and sorted in our Excercises array
if let newExcerciseIndex = find(excercises, newExcercise) {
// Create an NSIndexPath from the newExcerciseIndex
let newExcerciseIndexPath = NSIndexPath(forRow: newExcerciseIndex, inSection: 0)
// Animate in the insertion of this row
tableView.insertRowsAtIndexPaths([ newExcerciseIndexPath ], withRowAnimation: .Automatic)
save()
}
}
func save() {
var error : NSError?
if(managedObjectContext!.save(&error) ) {
println(error?.localizedDescription)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "excerciseSettings" {
let ExcerciseSettingsDetailViewController = segue.destinationViewController as UIViewController
let indexPath = tableView.indexPathForSelectedRow()!
let excercise = excercises[indexPath.row]
let destinationTitle = excercise.excerciseName
ExcerciseSettingsDetailViewController.title = destinationTitle
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
1.The segue is close. Your ExcerciseMasterTableViewController doesn't have a property named workout. You need to add
var workout:Workout!
to your ExcerciseMasterTableViewController.
Your seque should look more like this
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "excerciseMaster" {
let desitnationController = segue.destinationViewController as ExcerciseMasterTableViewController
let indexPath = tableView.indexPathForSelectedRow()!
let workout = workouts[indexPath.row]
destinationController.workout = workout
let destinationTitle = workout.workoutName
desitnationController.title = destinationTitle // Usually you would put this in the viewDidLoad method of the destinationController
}
}
Then in your when you add exercises in your ExcerciseMasterTableViewController simply set their workout property
workout.exercises = exercises // See note in #2
To make sure only the correct exercises are shown, in your viewDidLoad set your exercises array to workout.exercises. Note that workout.exercises should be an NSSet, so you either need to convert your set to an array, or have exercises be of type NSSet instead of an array.