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?
Post
Replies
Boosts
Views
Activity
I have a textField where user can enter a number which can has a decimals. I want to restrict the amount of decimal characters possible to enter till 4.
I'm trying to reach this behaviour with this code:
let split = completeString.components(separatedBy: ".")
if split.count == 2 {
print(split.last!)
if split.last!.count >= 4 {
return false
}
}
Full code for shouldChangeCharactersIn method:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let formatter = converterManager.setupNumberFormatter()
let textString = textField.text ?? ""
guard let range = Range(range, in: textString) else { return false }
let updatedString = textString.replacingCharacters(in: range, with: string)
let correctDecimalString = updatedString.replacingOccurrences(of: ",", with: ".")
let completeString = correctDecimalString.replacingOccurrences(of: formatter.groupingSeparator, with: "")
let split = completeString.components(separatedBy: ".")
if split.count == 2 {
print(split.last!)
if split.last!.count >= 4 {
return false
}
}
numberFromTextField = completeString.isEmpty ? 0 : Double(completeString)
guard completeString.count <= 12 else { return false }
guard !completeString.isEmpty else { return true }
textField.text = formatter.string(for: numberFromTextField)
return string == formatter.decimalSeparator
}
}
This code successfully split the string, give me the numbers user enters after decimal sign, but:
textField shows 3 character instead of 4
print shows that despite textField shows 3, when I press 4th digits in shows in the split array
when after 4th digit I continue to press numbers it changes last digit. I want if there is a 4 numbers entered already allow only delete not change the last number
GIF with behaviour: https://cln.sh/5vf1iq
How to fix that behaviour?
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 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?
I have a method which need an Array of Strings for further calculations. For now I set up this array through UserDefaults, because it should store small (3-characters string) amount of data:
func setRow(for currency: Currency, in currencies: [Currency]) {
// initialise array from UserDefaults
var currencyRowsArray = UserDefaults.standard.stringArray(forKey: "currencyRowsArray") ?? [String]()
// calculations with the array (add or remove string object to\from it
if currency.isForConverter {
currencyRowsArray.append(currency.shortName!)
} else {
guard let row = currencyRowsArray.firstIndex(of: currency.shortName!) else { return }
currencyRowsArray.remove(at: row)
currency.rowForConverter = 0
}
for (row, object) in currencyRowsArray.enumerated() {
for currency in currencies {
if object == currency.shortName {
currency.rowForConverter = Int32(row)
}
}
}
// save array back to UserDefaults after calculations
UserDefaults.standard.set(currencyRowsArray, forKey: "currencyRowsArray")
}
But since I have a CoreData implemented also I decided to store it in CoreData rather than UserDefaults. Here is how I tried to implement that:
1.I have entity Currency, so I created an attribute currencyRowsArray: Type - Transformable, Custom Class - [String], Transformer - NSSecureUnarchiveFromData, Optional - YES (because I don't need it to initialize every time a created a Currency object with other attributes)
2.In the setRow method:
let request: NSFetchRequest<Currency> = Currency.fetchRequest()
request.predicate = NSPredicate(format: "currencyRowsArray IN %@", currencyRowsArray)
3.Initialize the empty array to fill it later with fetched Data:
currencyRowsArray = [String]()
4.Fetch the array:
do {
let fetchResult = try context.fetch(request)
currencyRowsArray = fetchedResult as! [String]
} catch {
print(error)
}
5.After calculations save the changes:
do {
try context.save()
} catch {
print(error)
}
Full changed code of the setRow method:
func setRow(for currency: Currency, in currencies: [Currency]) {
var currencyRowsArray = [String] ()
let request: NSFetchRequest<Currency> = Currency.fetchRequest()
request.predicate = NSPredicate(format: "currencyRowsArray IN %@", currencyRowsArray)
do {
let fetchResult = try context.fetch(request)
currencyRowsArray = fetchResult as! [String]
} catch {
print(error)
}
if currency.isForConverter {
currencyRowsArray.append(currency.shortName!)
} else {
guard let row = currencyRowsArray.firstIndex(of: currency.shortName!) else { return }
currencyRowsArray.remove(at: row)
currency.rowForConverter = 0
}
for (row, object) in currencyRowsArray.enumerated() {
for currency in currencies {
if object == currency.shortName {
currency.rowForConverter = Int32(row)
}
}
}
do {
try context.save()
} catch {
print(error)
}
}
But every time the array loads as empty and when I make change, array doesn't add or remove items in it. What I did wrong?
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?
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 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 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 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 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?
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!
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?