I have 2 ViewControllers:
VC1 populates its tableView based on CoreData attribute isPicked, which is bool and show only items with true state. VC2 is a second Modal (not Full Screen) View Controller which allow user to change the state of isPicked attribute: check and uncheck item (make it true or false). The idea is user picks needed items end dismiss the VC2 and the items should appear in VC1.
I have implemented NSFetchedResultsController to both VC1 and VC2. And as soon as I press on first item (i.e. change isPicked state to true) I receive the error from VC1:
[error] fault: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
Here is how I change a state of item in VC2 (true\false):
guard let currencyArray = fetchedResultsController.fetchedObjects else { return }
let cell = tableView.cellForRow(at: indexPath) as! PickCurrencyTableViewCell
for currency in currencyArray {
if currency.shortName == cell.shortName.text {
currency.isPicked = !currency.isPicked
coreDataManager.save()
}
}
}
Here is my VC1 NSFetchedResultsController implementation:
super.viewDidLoad()
setupFetchedResultsController()
}
func setupFetchedResultsController() {
let predicate = NSPredicate(format: "isForConverter == YES")
fetchedResultsController = createCurrencyFetchedResultsController(and: predicate)
fetchedResultsController.delegate = self
try? fetchedResultsController.performFetch()
}
func createCurrencyFetchedResultsController(with request: NSFetchRequest<Currency> = Currency.fetchRequest(), and predicate: NSPredicate? = nil, sortDescriptor: [NSSortDescriptor] = [NSSortDescriptor(key: "shortName", ascending: true)]) -> NSFetchedResultsController<Currency> {
request.predicate = predicate
request.sortDescriptors = sortDescriptor
return NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
}
Here is my VC1 NSFetchedResultsController delegate:
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
if let indexPath = indexPath, let newIndexPath = newIndexPath {
switch type {
case .update:
tableView.reloadRows(at: [indexPath], with: .none)
case .move:
tableView.moveRow(at: indexPath, to: newIndexPath)
case .delete:
tableView.deleteRows(at: [indexPath], with: .none)
case .insert:
tableView.insertRows(at: [indexPath], with: .none)
default:
tableView.reloadData()
}
}
}
}
And finally my VC1 Table View methods:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let currency = fetchedResultsController.sections![section]
return currency.numberOfObjects
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "converterCell", for: indexPath) as! ConverterTableViewCell
let currency = fetchedResultsController.object(at: indexPath)
cell.shortName.text = currency.shortName
cell.fullName.text = currency.fullName
return cell
}
When I reload the app, picked item shows itself in VC1 (which caused crash). But every change in VC2 crashes the app again with the same error. I don't have any methods to delete items in VC1 or so. I need that VC1 tableView just show or hide items according to isPicked state made from VC2.
What I missed in my code?
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I have a UITableView which populate it cells with a NSFetchedResultsController based on CoreData attribute isForConverter which is Bool and should be true to be displayed. isForConverter state sets in another ViewController.
When I want to delete some cells from the UITableView and after access cells which wasn't deleted I receive the error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'no object at index 5 in section at index 0'
There is a GIF with the problem: https://cln.sh/M1aI9Z
My code for deleting cells. I don't need to delete it from database, just change it isForConverter from true to false:
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let currency = fetchedResultsController.object(at: indexPath)
currency.isForConverter = false
coreDataManager.save()
}
}
NSFetchedResultsController Setup and delegates:
func setupFetchedResultsController() {
let predicate = NSPredicate(format: "isForConverter == YES")
fetchedResultsController = coreDataManager.createCurrencyFetchedResultsController(with: predicate)
fetchedResultsController.delegate = self
try? fetchedResultsController.performFetch()
}
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .update:
if let indexPath = indexPath {
tableView.reloadRows(at: [indexPath], with: .none)
}
case .move:
if let indexPath = indexPath, let newIndexPath = newIndexPath {
tableView.moveRow(at: indexPath, to: newIndexPath)
}
case .delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .none)
}
case .insert:
if let newIndexPath = newIndexPath {
tableView.insertRows(at: [newIndexPath], with: .none)
}
default:
tableView.reloadData()
}
}
}
I noticed that if I just add tableView.reloadData() to tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) then everything works good. But deletion animation is really fast and antsy. Also according to docs I should not use tableView.reloadData() with NSFetchedResultsController...
How to fix that behaviour?
I have a UITableViewController and a Prototype cell with a UITextField. When I change a textField.text in one of the cells I want it to be changed in all other cells which my tableView now have (for example multiply number by 2).
This is the behaviour I want to implement: https://cln.sh/xjw5Od (short video)
Should I trigger a certain textField delegate method or it should be made in another way?
My code for now:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
cell.numberTextField.delegate = self
}
//Delegate methods
func textFieldDidBeginEditing(_ textField: UITextField) {
textField.text = ""
textField.textColor = UIColor.systemBlue
}
func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {
textField.text = "0"
textField.textColor = UIColor.black
}
I need to turn on a tableView editing mode by clicking on one of its cells from swipe action "move":
Code for swipe action:
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let move = UIContextualAction(style: .normal, title: "Переместить") { (action, view, completionHandler) in
self.turnEditing()
completionHandler(true)
}
move.backgroundColor = UIColor(named: "CalmBlueColor")
let configuration = UISwipeActionsConfiguration(actions: [move])
return configuration
}
turnEditing() function:
func turnEditing() {
if tableView.isEditing {
tableView.isEditing = false
} else {
tableView.isEditing = true
}
}
When I press on the swipe actions it's just closes, without going to editing mode... Here is the GIF: https://cln.sh/ilEN9F
Is it possible to go into editing mode from a swipe action or only from a barButtonItem + IBAction?
Hi Everyone.
Can anybody give me a tip where to look for solving this wrong app icon display in Launchpad.
I created an .icns file with all needed icon sizes according to Apple guide but after manually implementing this file into the app I received that this new icon displays bigger than others in Launchpad.
How to fix that?
I have a converter which converts numbers. When user is typing a number from NumberPad keyboard into my cell textField I want it to be formatted and displayed in real time like:
1000 -> 1 000
10000 -> 10 000
1000000 -> 1 000 000
1000,77 -> 1 000,77
I know it should happen in textFieldDidChangeSelection method. Here is mine:
func textFieldDidChangeSelection(_ textField: UITextField) {
numberFromTextField = Double(textField.text!)
//This is for reload visible Rows in my tableView. Might not needed in my question's context.
let activeTextFieldIndexPath = IndexPath(row: textField.tag, section: 0)
var visibleIndexPaths = [IndexPath]()
for indexPath in tableView.indexPathsForVisibleRows! {
if indexPath != activeTextFieldIndexPath {
visibleIndexPaths.append(indexPath)
}
}
tableView.reloadRows(at: visibleIndexPaths, with: .none)
}
There I have a global variable numberFromTextField which I made because I need to use the Double version of textField.text in my separate calculation methods.
How can I implement above formatted behaviour and at the same time save numberFromTextField = Double(textField.text!) as I need it in my different calculations?
I have a custom textField's input view - it is a Numpad style keyboard. Numpad is using to add numbers and math symbols to a textField.
I can't figure out how can I change a math symbol in a string if user already add one and wants to change it on another straight away. Here is an example of what I need: https://i.stack.imgur.com/IVGId.gif
Here is the code I use:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//number formatter
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
formatter.locale = .current
formatter.roundingMode = .down
//all possible math operation symbols user can add
let symbolsSet = Set(["+","-","x","/"])
var amountOfSymbols = 0
let numberString = textField.text ?? ""
guard let range = Range(range, in: numberString) else { return false }
let updatedString = numberString.replacingCharacters(in: range, with: string)
let correctDecimalString = updatedString.replacingOccurrences(of: formatter.groupingSeparator, with: "")
let completeString = correctDecimalString.replacingOccurrences(of: formatter.decimalSeparator, with: ".")
//current math symbol user add
let symbol = symbolsSet.filter(completeString.contains).last ?? ""
//if user add math symbol to an empty string - do not insert
if string == symbol, numberString.count == 0 { return false }
//count how much math symbols string has. If more that one - do not insert, string can have only one
completeString.forEach { character in
if symbolsSet.contains(String(character)) {
amountOfSymbols += 1
}
}
if amountOfSymbols > 1 { return false }
//count how much decimals string has. If more that one - do not insert because it can have only one per number
let numbersArray = completeString.components(separatedBy: symbol)
for number in numbersArray {
let amountOfDecimalSigns = number.filter({$0 == "."}).count
if amountOfDecimalSigns > 1 { return false }
}
//create numbers from a string
guard let firstNumber = Double(String(numbersArray.first ?? "0")) else { return true }
guard let secondNumber = Double(String(numbersArray.last ?? "0")) else { return true }
//format numbers and turn them back to string
let firstFormattedNumber = formatter.string(for: firstNumber) ?? ""
let secondFormattedNumber = formatter.string(for: secondNumber) ?? ""
//assign formatted numbers to a textField
textField.text = completeString.contains(symbol) ? "\(firstFormattedNumber)\(symbol)\(secondFormattedNumber)" : "\(firstFormattedNumber)"
return string == formatter.decimalSeparator
}
The logic for me was to use textField.deleteBackwards() method to delete an old one and add a new math symbol after, but with above code it doesn't work: it deletes symbol, but a new one doesn't appear - I should press again so new symbol can appear.
What should I do to change a math symbol in a string?
Test project on GitHub
I have a tableView with a prototype cell with a textField. I need to figure out how to do next:
when a user press on one of the textFields in tableView - he activate it and types a number
editing is done, keyboard hides
if user press second time on the same textField again then the number he typed first time should be saved and be editable (like he typed number 2, then activate textField again and can edit like 23)
if user pressed on a different textField, I need to run different code (reset to 0).
I can determine indexPath of currently edit textField:
func textFieldDidChangeSelection(_ textField: UITextField) {
let tapLocation = textField.convert(textField.bounds.origin, to: tableView)
guard let pickedCurrencyIndexPath = tableView.indexPathForRow(at: tapLocation) else { return }
I thought about storing the row of picked TextField, then if user clicks on the same TF and it matches the saved one - run one code, if row is changed (i.e. user clicked on anther TF) - run different code.
I tried to create an array which holds pressed TF IndexPath, but it doesn't store the previous textField indexPath, each time I press on textField it prints only the current pressed without what I pressed before.
What can I do there?
I have a UIScrollView with a UILabel + UITableView as a subview. Here is how it looks in a Storyboard: https://cln.sh/k2QiFS
In my tableView I have implemented a drag and drop for its cells through edit mode. When I drag a cell and move it down to the bottom of the tableView, the scrollView doesn't scroll and not showing cells which is currently out of the visible area.
So I should drop the cell, scroll the UIScrollView manually and again drag the cell.
Here is the behaviour on GIF: https://cln.sh/j45g83
If I get rid out of scrollView, the basic tableView behaviour automatically scrolls to the up/bottom, but the reason I added the scrollView is to be able the UILabel be scrolled too.
Here is my code for moveRowAt():
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
var currencies = fetchedResultsController.fetchedObjects!
let currency = fetchedResultsController.object(at: sourceIndexPath)
currencies.remove(at: sourceIndexPath.row)
currencies.insert(currency, at: destinationIndexPath.row)
for (index, currency) in currencies.enumerated() {
currency.rowForCurrency = Int32(index)
}
coreDataManager.save()
}
How can I turn on a scroll for my UIScrollView?
I want to implement a popup notification into my app when data was being updated successfully or not. To do that I:
created a .xib file: Screenshot - https://cln.sh/HvEHiX
created a class where I load that NIB:
import UIKit
class PopupView: UIView {
static let instance = PopupView()
@IBOutlet weak var backgroundView: UIView!
@IBOutlet weak var popupView: UIVisualEffectView!
@IBOutlet weak var symbol: UIImageView!
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var descriptionLabel: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
Bundle.main.loadNibNamed("PopupView", owner: self)
popupView.layer.cornerRadius = 20
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func showPopup(title: String, message: String, symbol: UIImage, on viewController: UIViewController) {
self.titleLabel.text = title
self.descriptionLabel.text = message
self.symbol.image = symbol
guard let targetView = viewController.view else { return }
backgroundView.frame = targetView.bounds
targetView.addSubview(backgroundView)
}
In the above class I created a showPopup method where defined a backgroundView frame to be equal to ViewController bounds.
When I call that method in desired ViewController I receive the behaviour where my popupView shows itself and then went off the screen straight away (black area in the GIF): GIF - https://cln.sh/e4ukf4
Would you be so kind to help me understand and fix the reason why the popupView went off the screen and not just equal to a ViewController bounds.
I have a networking method which returns 33 results. I save it in a CoreData database. Networking method starts every time an App launches (in ViewDidLoad). After every launch my CoreData database turns to be filled in with the same 33 results. So every launch I have 33, 66, 99, etc entries in it with the same names and attributes.
In my Entity Currency I have a unique attribute id like shortName (EUR, USD, etc).
How can I check what I have in CoreData after every Networking and if that shortName already exists just update its another attribute currentValue? And if there is no such shortName then fully create and save there.
The goal is to have only 33 entity's in my CoreData with recent information from Networking.
Thank you for a help in advance!
I have a UILabel as a UITableView header. Plus I have a UISearchController above it.
I can't figure out how to reduce the space between:
UITableView header and a UISearchController
UITableView header and a first cell in the section.
Here is how it looks like in Simulator: https://cln.sh/22Rqcz
Here is my Storyboard hierarchy: https://cln.sh/7s2IcB
I'm trying to format numbers in a UITextField consists of math equation String: "number + number".
At the moment I can type just a single number, then convert it to Double -> format with NSNumberFormatter -> convert back to String -> assign to textField.text: https://i.stack.imgur.com/4qQza.gif
The code:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
formatter.locale = .current
formatter.roundingMode = .down
let numberString = textField.text ?? ""
guard let range = Range(range, in: numberString) else { return false }
let updatedString = numberString.replacingCharacters(in: range, with: string)
let correctDecimalString = updatedString.replacingOccurrences(of: formatter.decimalSeparator, with: ".")
let completeString = correctDecimalString.replacingOccurrences(of: formatter.groupingSeparator, with: "")
guard let value = Double(completeString) else { return false }
let formattedNumber = formatter.string(for: value)
textField.text = formattedNumber
return string == formatter.decimalSeparator
}
Now I want to add a calculation functionality and display a simple math equation in a textField as "number + number", but each number should be formatted as shown on above screenshot. Example (but without formatting): https://i.stack.imgur.com/W7Jet.gif
I can't properly implement that. The logic for me was: track the String each time new char inserts -> if it has math sign extract numbers -> convert them to Double -> format with NSNumberFormatter -> convert back to String -> construct a new String "number + number".
The code I tried:
if let firstString = completeString.split(separator: "+").first, let secondString = completeString.split(separator: "+").last {
guard let firstValue = Double(firstString) else { return false }
guard let secondValue = Double(secondString) else { return false }
let firstFormattedNumber = formatter.string(for: firstValue)
let secondFormattedNumber = formatter.string(for: secondValue)
textField.text = "\(firstFormattedNumber ?? "") + \(secondFormattedNumber ?? "")"
// another try
if completeString.contains("+") {
let stringArray = completeString.components(separatedBy: "+")
for character in stringArray {
print(character)
guard let value = Double(character) else { return false }
guard let formattedNumber = formatter.string(for: value) else { return false }
textField.text = "\(formattedNumber) + "
}
}
But it's not working properly. I tried to search but didn't find any similar questions.
Test project on GitHub
How can I format the numbers from such a string?
I have 1 UIViewController with a type of CustomTableView and 1 UITableViewController with a type of usual UITableView. Both conform to NSFetchedResultsControllerDelegate and implemented its delegate methods with the repeated code. For now it's in extensions.
Is it possible to move that code out to a separate swift file? I tried to move it to separate file with class NSFetchedResultsView, but when I copy that delegate methods to the new file, it doesn't know anything about tableView inside it's methods...
How can I separate that methods properly?
Delegate methods I want to separate:
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .update:
if let indexPath = indexPath {
tableView.reloadRows(at: [indexPath], with: .none)
}
case .move:
if let indexPath = indexPath, let newIndexPath = newIndexPath {
tableView.moveRow(at: indexPath, to: newIndexPath)
}
case .delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .none)
}
case .insert:
if let newIndexPath = newIndexPath {
tableView.insertRows(at: [newIndexPath], with: .none)
}
default:
tableView.reloadData()
}
}
I have a VC1 with a UITableView which populate it cells with a NSFetchedResultsController. I want VC1 tableView to place all new added cells to the bottom of tableView, therefore I created a CoreData attribute: itemNumberForVC1(type Int32). The cells can be added only from VC2. That's why I need to pass the numberOfObjects from VC1 method numberOfRowsInSection to VC2.
The numberOfObjects is in the following method of VC1:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let numberOfObjects = fetchedResultsController.sections?[section].numberOfObjects else {return 0}
return numberOfObjects
}
I need to use the numberOfObjects in my VC2 didSelectRowAt method:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let currency = fetchedResultsController.object(at: indexPath)
currency.isForConverter = !currency.isForConverter
//currency.itemNumberForVC1 = numberOfObjects - 1 <- calculation: there I should receive the actual
//number of objects from VC1 and assign the number to picked currency, and it should appear in VC1
//at the bottom of its TableView
coreDataManager.save()
}
I tried to use delegates but inside VC2 didSelectRowAt the result is always 0. The max result I've got is created a method in VC2, which takes numberOfObjects as a parameter, call it in VC1 and then I have that number inside the VC2 but only in borders of the method. I can't move it to VC2 didSelectRowAt (tried through a global variable).
How can I do that efficiently?