I am trying to use a button to move an entire section of my tableView at once. I want it to move all of the selected rows to another tableView and delete them from the first. The first tableView data is saved in SList the second is SCList. Both Entities have attributes item, qty, desc just the first letters change from sl to sc (i.e. slitem to scitem). The problem I am having is getting the data to change from the SList entity to the SCList entity. How do I get it to remove the data from SList entity and change to SCList and add it to the respective tableView?
Button Code:
#IBAction func Move(sender: AnyObject) {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! SLTableViewCell
let passValue = cell.cellLabel!.text
print(passValue)
self.performSegueWithIdentifier("moveToSC", sender: self)
}
Segue Code:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "editItem" {
let SListController:SLEdit = segue.destinationViewController as! SLEdit
SListController.item = selectedItem
}
if segue.identifier == "moveToSC" {
let SCListController:SCart = segue.destinationViewController as! SCart
SCListController.item = item
}
I commented out the "if segue.identifier == "moveToSC"" code and it segues but doesn't pass any information. All that prints is "Optional("Label")" and nothing passes or deleted.
I'm not sure if any other code is needed. If you need to review any please let me know and i'll edit my code.
I'm trying not to use the didSelectRow method because that changes the row from section 0 to section 1. I would like to use a button to move all at once. Do I even have to mess with the Entities or should it move and change it by itself? I'm pretty lost on this...
The direct answer to your question is that you need to create an instance of SCList, set its attribute values to be the same as the attributes of the SList object, and then delete the SList object. I understand from your previous questions that you are using a fetchedResultsController for the first tableView, with two sections; the objects to move are in the section 1:
#IBAction func Move(sender: AnyObject) {
let sectionInfo = self.fetchedResultsController.sections![1]
let objectsToMove = sectionInfo.objects as! [SList]
for item in objectsToMove {
let newItem = NSEntityDescription.insertNewObjectForEntityForName("SCList", inManagedObjectContext:self.managedObjectContext!) as! SCList
newItem.scitem = item.slitem
newItem.scqty = item.slqty
newItem.scdesc = item.scdesc
self.managedObjectContext.deleteObject(item)
}
self.performSegueWithIdentifier("moveToSC", sender: self)
}
The longer answer to your question is that copying data from one entity to another like this is not ideal - it suggests you might need to think through your data model some more. You could, for example, have an additional attribute which reflects which tableView the item should appear in. You would specify predicates for the fetched results controllers to ensure the table views display only the appropriate items. "Moving" an item would then be a simple matter of changing the value of the new attribute.
Related
So I am currently in the process of making a todo-list app: incorporating timers and also other things. While I was adding some finishing touches to the application, I wanted to make sure that I could set a due date to the task that the person sets. The problem however, arises when the application switches from the "ToDoList" table-view controller to a separate view controller with a date picker and a UITextField for adding the task to the "REALM" database with a given category, name, due date and date created.
Everything was set up and the whole system was working until I clicked save task and no task was added to the realm directory. After hours of testing with print statements, I discovered that the error arose RIGHT after a function was called in the other view controller which imported the data to the ToDoList view controller for saving. As can be seen in the code snippet below, in order for the task to be added the optional "categorySelected" needs to have a non-nil value. And when I change view controllers, this value turns to nil:
"AddTaskViewController" :
if task != "" {
let mainVC = storyboard?.instantiateViewController(withIdentifier: "tasksTable") as! TodoListViewController
dateFormatter.dateFormat = "dd-MM-yyyy"
date = dateFormatter.string(from: datePicker.date)
mainVC.dataRecieved(data1: task, data2: date)
performSegue(withIdentifier: "unwindToTable", sender: self)
}
"dataRecieved Function on the other VC + Point of error" :
func dataRecieved (data1: String, data2 : String) {
let taskName = data1
let taskDueDate = data2
if taskName != "" {
if let currentCateogry = selectedCategory {
// This is the point of error
do {
try realm.write{
let newItem = Item()
newItem.title = taskName
newItem.dateDue = taskDueDate
newItem.dateCreated = Date()
currentCateogry.items.append(newItem)
print("finished appending")
}
} catch {
print("Error aappending new items into realm: \(error)")
}
tableView.reloadData()
}
} else {
tableView.reloadData()
}
}
The variable which turns to nil is the "selectedCategory". In order to show you how this variable works, let me introduce you to a small wire-frame layout of the application.
categoryViewController is the ROOT-View controller
Creating a category just includes a UIAlertView where you put the name
Selecting the category takes you to the ToDoList view controller.
A task has a parent category of "categorySelected" and is therefore appended in that portion of realm.
The selectedCategory variable is defined as such:
var selectedCategory : Category? {
didSet{
loadItems()
}
}
The loadItems() method:
func loadItems() {
todoItems = selectedCategory?.items.sorted(byKeyPath: "title", ascending: true)
tableView.reloadData()
}
Selected Category in the CategoryViewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToItems" {
let destinationVC = segue.destination as! TodoListViewController
if let indexPath = tableView.indexPathForSelectedRow{
destinationVC.selectedCategory = categories?[indexPath.row]
}
}
**Sorry for the missing } at the end.
So basically, when the person using the application clicks on a category cell, that value is transported through the view controllers before the segue is made. For that reason, the value stays inside the variable as long as the tableView controller is on the fore-ground.
When I actually go to the new view controller, this value is erased and so... despite everything working... when I press the "saveTaskButton", since the "selectedCategory" is nil... the appending never happens, and there is no task to be saved.
I am requesting for one of 2 solutions:
If I could somehow use a UIAlertView for a date input... so therefore I do not have to even change view controllers for the add task method.
Some way to restore the category value (which is of type category due to class defining systems)
The solutions I have tried thus far and the problems:
ViewDidAppear --> The function is called in the other view, when the value for the category is nil, therefore the viewDidAppear really serves no purpose
Somehow storing the value of the "selectedCategory" type category item --> Since I use the "didSet" method... If I force this value to be preserved to the next view controller I get the error selectedCategory is a "get" only dataType
I do not think that this is a very distinct problem, but I have only been learning swift for about 2-3 months and this is only the first time I have encountered such an issue. I hope you guys can help me.
Thanks!
(Also, please just comment if you need to see any other part of the code)
I'm trying to add a feature in my app, to add multiple members to one action at one time. The members are listed in a tableView and the user can select multiple rows at one time with the .allowsMultipleSelection = true function. I got the following code but this doesn't work. I think my idea would work but not in the way I have it in the code :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destination = segue.destination as? AddMultipleMemberTransactionViewController,
let selectedRows = multipleMemberTableView.indexPathsForSelectedRows else {
return
}
destination.members = members[selectedRows]
}
Does somebody out here know, how I can solve this problem, because there is an error :
Cannot subscript a value of type '[Member?]' with an index of type '[IndexPath]'
I have the same feature in the app but just for one member. There I in the let selectedRows line after the indexPathForSelectedRow a .row. Is there a similar function for indexPathsForSelectedRows ?
Or is this the wrong way to do it?
You need
destination.members = selectedRows.map{ members[$0.row] }
As the indexPathsForSelectedRows indicates, it returns an array of IndexPath. What you need to do is create an array of Member objects based on those path.
Assuming you have a "members" array that contain all the members the user can select from, and your table has only 1 section:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var selectedMembers: [Member] = []
guard let destination = segue.destination as? AddMultipleMemberTransactionViewController,
let selectedIndexes = multipleMemberTableView.indexPathsForSelectedRows else {
return
}
for selectedIndex in selectedIndexes {
let selectedMember = members[selectedIndex.row]
selectedMembers.append(selectedMember)
}
destination.members = selectedMembers
}
You can also use the array map() function to change the for loop into a single line operation:
let selectedMembers: [Member] = selectedRows.map{ members[$0.row] }
Both should effectively do the same.
I have three view controllers embedded in a navigation controller.
The first view controller is a list of tests, which use fetchedResultsController to populate the table.
The second view controller is a detail view controller, which allows adding a new test, editing it's detail, saving the new test or adding Questions to the test. It also includes a table of questions, which is also populated with a fetchedResultsController.
The problem I have is that when I have an existing Test which is saved, then when I go to add on questions, they correctly populate the table when I pop back to the TestDetailsVC, however, if I'm adding a new test, and then adding new questions to it, the test saves to core data, then in the QuestionDetailsVC questions also save to core data. However, when I pop back to the TestDetailsVC, the new questions don't populate the table. However, when I navigate back to the TestListVC and then go forward, the questions then populate the table correctly. I'm trying to get the questions to populate the table the first time I pop back to the table. Shouldn't the table re-fetche this data when the view loads, and the table work correctly when I navigate back since these objects are saved in core data? Why does it work correctly with a previously saved test object, but not work with a newly saved test object?
My code for saving the new test in the TestDetailsVC is:
//if we tap on a row, then we select that question to edit
//if a question is selected, we will pass that information over to
//the question editor view so it can be edited
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//make sure there is at least one question
//objs = means object selected
if let objs = controller.fetchedObjects, objs.count > 0 {
//if there is, keep track of the test which is selected
let question = objs[indexPath.row]
//pass along that test to the editor to be edited
//the sender is the selected test at that particular row
performSegue(withIdentifier: "EditQuestionSegue", sender: question)
}
}
//we need to get ready to do the segue before we call it
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
switch identifier {
case "EditQuestionSegue":
//find the question needing to be edited
//pass this along to the question editing view controller
if let destination = segue.destination as? QuestionDetailsVC {
if let question = sender as? Question {
destination.questionToEdit = question
//pass along the test that the question is
//related to also
if let test = testToEdit {
destination.testToEdit = test
}
}
}
case "AddNewQuestionSegue":
//if no test to edit got passed in, it means
//we're now editing a new test
//once we type in the test title, then we have to
//make sure the test has been saved if it's new test
//before we go on to edit questions
if testToEdit == nil {
saveTestBeforeAddingQuestion()
//make sure we set up as if new
//before we start adding questions
//these are called in the view did load
}
if let destination = segue.destination as? QuestionDetailsVC {
if let test = testToEdit {
destination.testToEdit = test
}
}
default:
print("no segue this time")
}
}
}
func saveTestBeforeAddingQuestion() {
var test: Test!
//if there's not a passed in value into the testToEdit core data
//object test entity, then we're going to edit as if new
if testToEdit == nil {
//then instantiate a new test object ready to be written to
test = Test(context: context)
} else {
test = testToEdit
}
if let title = titleTextField.text {
test.title = title
}
if let abrevTitle = abrevTitleTextField.text {
test.abrevTitle = abrevTitle
}
if let author = authorTextField {
test.author = author.text
}
if let publisher = publisherTextField {
test.publisher = publisher.text
}
ad.saveContext()
//since we have now saved a new test
//let's put that test into our testToEdit variable
//so we can pass it along to the next view controller
//during our segue
if let newTestCreated = test {
testToEdit = newTestCreated
}
}
My code for saving the new question is:
#IBAction func savePressed(_ sender: UIButton) {
var question: Question!
//if there's not a passed in value into the questionToEdit core data
//object test entity, then we're going to edit as if new
if questionToEdit == nil {
//then instantiate a new test object ready to be written to
question = Question(context: context)
} else {
question = questionToEdit
}
//make sure to relate the question added to the testToEdit test
question.test = self.testToEdit
//if there is something in the sentence text field
//then assign that value to the question.sentence attribute
//of the sentence entity in our core data context
if let sentence = questionSentenceTextField.text {
question.sentence = sentence
}
if let identifier = questionIdentifierTextField.text {
question.identifier = identifier
}
if let displayOrder = questionDisplayOrderTextField.text {
question.displayOrder = Int(displayOrder) as NSNumber?
}
// save the context
ad.saveContext()
_ = navigationController?.popViewController(animated: true)
}
I spent days trying to get the question table to load, but finally discovered that it wasn't loading back up because when I pop back to the view controller it doesn't automatically reload the table with the fetchedResultsController. So I had to place a call to refetch the data in the viewWillAppear method and then reload the tableView:
(TestDetailsVC)
override func viewWillAppear(_ animated: Bool) {
guard testToEdit != nil else {return}
attempFetch()
tableView.reloadData()
}
Before I begin, let me say that I have taken a look at a popular post on the matter: Passing Data between View Controllers
My project is on github https://github.com/model3volution/TipMe
I am inside of a UINavigationController, thus using a pushsegue.
I have verified that my IBAction methods are properly linked up and that segue.identifier corresponds to the segue's identifier in the storyboard.
If I take out the prepareForSegue: method then the segue occurs, but obviously without any data updating.
My specific error message is: Could not cast value of type 'TipMe.FacesViewController' (0x10de38) to 'UINavigationController' (0x1892e1c).
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
if segue.identifier == "toFacesVC" {
let navController:UINavigationController = segue.destinationViewController as! UINavigationController
let facesVC = navController.topViewController as! FacesViewController
facesVC.balanceLabel.text = "Balance before tip: $\(balanceDouble)"
}
}
Below is a screenshot with the code and error.
side notes: using Xcode 6.3, Swift 1.2
A couple of things:
1: change your prepareForSegue to
if segue.identifier == "toFacesVC" {
let facesVC = segue.destinationViewController as! FacesViewController
facesVC.text = "Balance before tip: $\(balanceDouble)"
}
2: add a string variable to your FacesViewController
var text:String!
3: change the FacesViewController viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
balanceLabel.text = text
}
The reasons for all the changes: the segue destinationViewController is the actual FacesViewController you transition to -> no need for the navigationController shenanigans. That alone will remove the "case error", but another will occur due to unwrapping a nil value because you try to access the balanceLabel which will not have been set yet. Therefore you need to create a string variable to hold the string you actually want to assign and then assign that text in the viewDidLoad - at the point where the UILabel is actually assigned.
Proof that it works:
4: If you want display two decimal places for the balance you might change the String creation to something like (following https://stackoverflow.com/a/24102844/2442804):
facesVC.text = String(format: "Balance before tip: $%.2f", balanceDouble)
resulting in:
My application is a UITabBarController application and when it first begins, it needs to make a call my Firebase database so that I can populate the UITableView within one of the tabs in the UITabBarController. However, I noticed that the first time I login and go to the TabBarController, the data does not show. I have to go from that tab to another tab, and then back to the original tab to have the data be displayed. However, I want it so that the data displays the first time around. I understand this is an error with the fact that Firebase asynchronously grabs data and that the view loads before all the data is processed but I just can't seem to get it to work as desired.
I tried to query for all the values we want first before we perform the segue, store them into an array, and then send that array to a predefined array in OffersView but that did not seem to work. Here is my attempt:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "OffersView"){
let barViewControllers = segue.destinationViewController as UITabBarController
let navigationOfferController = barViewControllers.viewControllers![0] as UINavigationController
let offersViewController = navigationOfferController.topViewController as OffersView
offersViewController.offers = offersQuery()
}
func offersQuery() -> [Offer]{
firebaseRef = Firebase(url:"https://OffersDB.firebaseio.com/offers")
//Read the data at our posts reference
firebaseRef.observeEventType(FEventType.ChildAdded, withBlock: { (snapshot) -> Void in
let restaurant = snapshot.value["restaurant"] as? String
let offer = Offer(restaurant: restaurant)
//Maintain array of offers
self.offers.append(offer)
}) { (error) -> Void in
println(error.description)
}
return offers
}
Any help would be much appreciated!
Edit: Im trying to use a completion handler everytime the childAdded call occurs and I am trying to do it like so but I can't seem to get it to work. I get an error saying: 'NSInternalInconsistencyException', reason: 'attempt to insert row 1 into section 0, but there are only 1 rows in section 0 after the update
setupOffers { (result) -> Void in
if(result == true){
var row = self.offers.count
self.tableView.beginUpdates()
var indexPaths = NSIndexPath(forRow: row, inSection: 0)
println(indexPaths)
self.tableView.insertRowsAtIndexPaths([indexPaths], withRowAnimation: UITableViewRowAnimation.Bottom)
}
}
You should segue normally, and inside the UITableViewController, perform the query. Once the query callback is called, you can go ahead and reload the table with -reloadData so it will populate the cells.