Good day! I have a little problem. I want to seperate data from Realm DB by Bool value (true and false). If Bool data currencyStatusCode == true then show currencyName, currencyCode and balance at first tableview section "Active accounts", if Bool data currencyStatusCode == false then in to second section "Inactive accounts". As well as I add image of DB look. Will be very appreciate for your help.
var sections = ["Active accounts", "Inactive accounts"]
#IBOutlet weak var accountManagerTableView: UITableView!
let realm = try? Realm()
let accounts = try! Realm().objects(currencyAccounts.self).sorted(byKeyPath: "currencyID")
var accountManager: currencyAccounts?
var accountsRecord: Results<currencyAccounts> {
get {
return realm!.objects(currencyAccounts.self)
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section]
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return accounts.count
/* switch (section) {
case 0:
return accounts.count
break;
case 1:
return accounts.count
break;
default:
break;
}
return section
*/
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = accountManagerTableView.dequeueReusableCell(withIdentifier: "activeCurrencyCell", for: indexPath) as! accountManagerTableViewCell
let sortingtInTableView = realm?.objects(currencyAccounts.self).sorted(byKeyPath: "currencyID", ascending: true)
let currentUserBalances = sortingtInTableView![indexPath.row]
cell.currencyFullName.text = currentUserBalances.currencyName
cell.currencyTitle.text = currentUserBalances.currencyCode
cell.selectedAccountBalance.text = String(currentUserBalances.currencyBalance)
return cell
}
Realm DB data
You use filter again Realm object itself. Please refer documentation for more details.
Please find the code to filter active and inactive accounts:
let realm = try! Realm()
let activeAccounts = realm.objects(Dog.self).filter("currencyStatusCode = true").sorted(byKeyPath: "currencyID")
// Persist your data easily
try! realm.write {
realm.add(activeAccounts)
}
let realm = try! Realm()
let inactiveAccounts = realm.objects(Dog.self).filter("currencyStatusCode = false").sorted(byKeyPath: "currencyID")
// Persist your data easily
try! realm.write {
realm.add(inactiveAccounts)
}
Related
I'm trying to index my tableView data, ordering the names of the people listed by its name initials. I've seen lots of tutorials about doing so with genericall non-persistent data, but none with Core Data and making use of fetchedResultsController option (for iCloud auto-sync).
Now my code is as follows (for clarity purposes, I transcribe only question-related parts):
//The two arrays to populate from Core Data, used later to populate the table
var alumnosDictionaryArray = [String:[String]]()
var titulosSeccionArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
//Obtain data from Core Data
updateFetchedResultsController()
//Populate the arrays with this data
populateArrays()
func updateFetchedResultsController() {
guard let context = container?.viewContext else { return }
context.performAndWait {
let sortDescriptor = NSSortDescriptor(key: "nombre", ascending: true)
request.sortDescriptors = [sortDescriptor]
fetchedResultsController = NSFetchedResultsController<Alumno>(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
do {
try fetchedResultsController?.performFetch()
tableView.reloadData()
} catch {
print("Error obtaining data: \(error)")
}
}
}
func populateArrays() {
if let objects = fetchedResultsController?.fetchedObjects {
for alumno in objects {
let inicial = String(alumno.nombre?.prefix(1) ?? "")
if var values = alumnosDictionaryArray[inicial] {
values.append(alumno.nombre!)
alumnosDictionaryArray[inicial] = values
} else {
alumnosDictionaryArray[inicial] = [alumno.nombre!]
}
}
titulosSeccionArray = [String](alumnosDictionaryArray.keys)
titulosSeccionArray = titulosSeccionArray.sorted(by: {$0 < $1})
}
}
This part seems to work ok, as the arrays are filled correctly, as I have checked with printing statements.
Later, for table data:
override func numberOfSections(in tableView: UITableView) -> Int {
return titulosSeccionArray.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let inicial = titulosSeccionArray[section]
if let values = alumnosDictionaryArray[inicial] {
return values.count
}
return 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! AlumnoCell
if self.validateIndexPath(indexPath) {
let object = fetchedResultsController?.object(at: indexPath)
let inicial = titulosSeccionArray[indexPath.section]
if let values = alumnosDictionaryArray[inicial] {
cell.nombreField.text = values[indexPath.section]
cell.especialidadField.text = object?.especialidadRelacionada?.nombre
cell.cursoField.text = object?.cursoRelacionado?.nivel
cell.tutorField.text = object?.tutorRelacionado?.nombre
}
} else {
print("Error from indexPath")
}
return cell
}
//To validate indexPath
func validateIndexPath(_ indexPath: IndexPath) -> Bool {
if let sections = self.fetchedResultsController?.sections,
indexPath.section < sections.count {
if indexPath.row < sections[indexPath.section].numberOfObjects {
return true
}
}
return false
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return titulosSeccionArray[section]
}
I get a more-or-less complete table with the correct sections, title of sections (initials), and number of rows for each, but it seems that only firs row works, as the rest of data are empty and throw an error when selecting any: 'no section at index 1'. In fact, I get many "Error from indexPath" messages on the console before exception throwing...
Any suggestion? Thanks.
For Alumno entity add 1 more column of nombreFirstChar.
Then use something like this to save this variable in database:
alumno.nombreFirstChar = nombre.firstCharacter()
Here firstCharacter helper method will be like this:
func firstCharacter() -> String {
var firstCharacter = self.first ?? "#"
if (!(firstCharacter >= "a" && firstCharacter <= "z") && !(firstCharacter >= "A" && firstCharacter <= "Z") ) {
firstCharacter = "#"
}
return String(firstCharacter).capitalized
}
Now in your fetchedResultsController,replace this line
fetchedResultsController = NSFetchedResultsController<Alumno>(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
with
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "nombreFirstChar", ascending: true)]
fetchRequest.propertiesToGroupBy = ["nombreFirstChar"]
fetchedResultsController = NSFetchedResultsController<Alumno>(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nombreFirstChar, cacheName: nil)
Then in table view delegate apis, you can use fetchedResultsController like this:
func numberOfSections(in tableView: UITableView) -> Int {
return fetchedResultsController.sections?.count ?? 0
}
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
guard let fetchedResultsSections: [NSFetchedResultsSectionInfo] = channelFetchedResultsController.sections else {return nil}
return fetchedResultsSections!.map{String($0.name)}
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
guard var fetchedResultsSections: [NSFetchedResultsSectionInfo] = fetchedResultsController.sections else {return nil}
return fetchedResultsSections[section].name
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard var sections:[NSFetchedResultsSectionInfo] = fetchedResultsController.sections, let section = sections[section] else {
return 0
}
return section.numberOfObjects
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
cell.accessoryType = .disclosureIndicator
let alumno = fetchedResultsController.object(at: indexPath)
...
return cell
}
My entire table view is being written programmatically and the data is coming from JSON. I am trying to group the cells by the customer the code seems to be correct but no sections are showing up at all.
Here is the code:
Portfolios.swift
import UIKit
struct Portfolios: Codable {
let customer, serial, rma, model: String
let manufacturer: String
}
PortfolioController.swift
import UIKit
class PortfolioController: UITableViewController {
var portfolios = [Portfolios]()
var portfoliosDic = [String:[Portfolios]]()
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.prefersLargeTitles = true
view.backgroundColor = UIColor.blue
navigationItem.title = "Customer"
fetchJSON()
}
func fetchJSON(){
let urlString = "https://www.example.com/example/example.php"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, error) in
DispatchQueue.main.async {
if let error = error {
print("Failed to fetch data from url", error)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let res = try JSONDecoder().decode([Portfolios].self, from: data)
self.portfoliosDic = Dictionary(grouping: res, by: { $0.customer})
DispatchQueue.main.async {
self.tableView.reloadData()
}
self.portfolios = try decoder.decode([Portfolios].self, from: data)
self.tableView.reloadData()
} catch let jsonError {
print("Failed to decode json", jsonError)
}
}
}.resume()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return portfoliosDic.keys.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let keys = Array(portfoliosDic.keys)
let item = portfoliosDic[keys[section]]!
return item.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "cellId")
let keys = Array(portfoliosDic.keys)
let arr = portfoliosDic[keys[indexPath.section]]!
let customer = arr[indexPath.row]
let titleStr = [customer.serial, customer.manufacturer, customer.model].compactMap { $0 }.joined(separator: " - ")
//cell.textLabel?.text = titleStr
print(titleStr)
// Get references to labels of cell
cell.textLabel!.text = customer.serial
return cell
}
}
UPDATE:
Because it is a UIViewController Xcode told me to remove the override func
and I added #IBOutlet weak var tableView: UITableView!
(The end results is an empty table for some reason)
Using a UITableViewController instead:
import UIKit
class CustomerViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var sections = [Section]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cellId")
navigationController?.navigationBar.prefersLargeTitles = true
fetchJSON()
}
func fetchJSON(){
let urlString = "https://www.example.com/example/example.php"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, error) in
DispatchQueue.main.async {
if let error = error {
print("Failed to fetch data from url", error)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let res = try decoder.decode([Portfolios].self, from: data)
let grouped = Dictionary(grouping: res, by: { $0.customer })
let keys = grouped.keys.sorted()
self.sections = keys.map({Section(name: $0, items: grouped[$0]!)})
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print("Failed to decode json", error)
}
}
}.resume()
}
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let section = sections[section]
return section.items.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section].name
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath)
let section = sections[indexPath.section]
let item = section.items[indexPath.row]
let titleStr = "\(item.serial) - \(item.manufacturer) - \(item.model)"
cell.textLabel!.text = titleStr
return cell
}
}
First of all why do you decode the JSON twice?
No sections are displayed because the method titleForHeaderInSection is not implemented.
The code is not reliable anyway because the order of the sections is not guaranteed. I recommend to create another struct for the sections.
struct Section {
let name : String
let items : [Portfolios]
}
struct Portfolios: Decodable {
let customer, serial, rma, model: String
let manufacturer: String
}
Delete portfolios and portfoliosDic and declare the data source array
var sections = [Section]()
Group the JSON, sort the keys and map the dictionaries to Section instances
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let res = try decoder.decode([Portfolios].self, from: data)
let grouped = Dictionary(grouping: res, by: { $0.customer })
let keys = grouped.keys.sorted()
self.sections = keys.map({Section(name: $0, items: grouped[$0]!)})
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print("Failed to decode json", error)
}
The table view datasource and delegate methods are
override func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let section = sections[section]
return section.items.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section].name
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath)
let section = sections[indexPath.section]
let item = section.items[indexPath.row]
let titleStr = "\(item.serial) - \(item.manufacturer) - \(item.model)"
cell.textLabel!.text = titleStr
return cell
}
Note:
Always dequeue cells in cellForRowAt
I'm trying to build an app modeled off of iPhone's Contacts app. When I fetch records from the CNContactStore. I can specify CNContactSortOrder.userDefault.
let fetchRequest = CNContactFetchRequest(keysToFetch:
[CNContactFormatter.descriptorForRequiredKeys(for: .fullName)])
fetchRequest.sortOrder = CNContactSortOrder.userDefault
I would like to replicate this sort order when creating section indexes. I can create a selector for CNContact.familyName. I need something like .userDefault because it picks up other fields like nickname when familyName is nil and includes it in the sorted results correctly.
import UIKit
import Contacts
class SectionIndexesTableViewController: UITableViewController {
let collation = UILocalizedIndexedCollation.current()
var sections: [[CNContact]] = []
var sectionTitles: [String] = []
var contacts: [CNContact] = [] {
didSet {
sectionTitles.removeAll()
sections.removeAll()
// let selector: Selector = #selector(getter: CNContact.familyName)
let selector: Selector = #selector(getter: CNContact.comparator(forNameSortOrder: .userDefault))
var sectionsAll = Array(repeating: [], count: collation.sectionTitles.count)
let sortedContacts = collation.sortedArray(from: contacts, collationStringSelector: selector)
for contact in sortedContacts {
let sectionNumber = collation.section(for: contact, collationStringSelector: selector)
sectionsAll[sectionNumber].append(contact as! CNContact)
}
for index in 0 ..< sectionsAll.count {
if sectionsAll[index].count > 0 {
sectionTitles.append(collation.sectionTitles[index])
sections.append(sectionsAll[index] as! [CNContact])
}
}
self.tableView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
let fetchRequest = CNContactFetchRequest(keysToFetch:
[CNContactFormatter.descriptorForRequiredKeys(for: .fullName)])
fetchRequest.sortOrder = CNContactSortOrder.userDefault
let store = CNContactStore()
do {
try store.enumerateContacts(with: fetchRequest, usingBlock: { (contact, stop) -> Void in
self.contacts.append(contact)
})
}
catch let error as NSError {
print(error.localizedDescription)
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TVCellSectionIndexes", for: indexPath)
let contact = sections[indexPath.section][indexPath.row]
let formatter = CNContactFormatter()
cell.textLabel?.text = formatter.string(from: contact)
return cell
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionTitles[section]
}
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return collation.sectionIndexTitles
}
override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
return collation.section(forSectionIndexTitle: index)
}
}
The compile error I receive is: Argument of '#selector' does not refer to an '#objc' method, property, or initializer
How do I expose .userDefault to #objc?
Apple's Contacts App with added records
I´m new to coding in Swift 3.
I am trying to "replicate" the phone app from iPhone but I have some problems when displaying data in cells, they don´t appear (when apparently there´s some data in there, recovered from the Core Data class).
The Core Data class consists of a Contact with some attributes like "firstName", "lastName", "phoneNumber", etc. I made it in the X.xcdatamodeld. Those attributes
are set in another VC and saved in there.
What I want to display in the cells is the firstName of each contact sorted alphabetically in sections, like the phone app.
Here is what I have so far.
extension Contact {
var titleFirstLetter: String {
return String(firstName![firstName!.startIndex]).uppercased()
}
}
class MainTableViewController: UITableViewController {
var listOfContacts = [Contact]()
var sortedFirstLetters: [String] = []
var sections: [[Contact]] = [[]]
struct Storyboard {
static let cellIdentifier = "Cell"
static let showDetailIdentifier = "showDetail"
static let showInformationIdentifier = "showInformationVC"
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
let firstLetters = listOfContacts.map { $0.titleFirstLetter }
let uniqueFirstLetters = Array(Set(firstLetters))
sortedFirstLetters = uniqueFirstLetters.sorted()
sections = sortedFirstLetters.map { firstLetter in
return listOfContacts.filter { $0.titleFirstLetter == firstLetter }.sorted { $0.firstName! < $1.firstName! }
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
getData()
tableView.reloadData()
}
func getData() {
// 1. Create context
let context = CoreDataController.persistentContainer.viewContext
// 2. RecoverData from Database with fetchRequest
do {
try listOfContacts = context.fetch(Contact.fetchRequest())
} catch {
print("Error \(error.localizedDescription)")
}
}
// MARK: - Tableview data source
override func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let contact = sections[indexPath.section][indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: Storyboard.cellIdentifier, for: indexPath)
cell.textLabel?.text = contact.firstName
return cell
}
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return sortedFirstLetters
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sortedFirstLetters[section]
}
NOTE: CoreDataController is a class I made to be comfortable when managing the retrieving and saving into CoreData (what I did was to copy the generated code of CoreData from the AppDelegate.swift)
Hopefully you can help me to figure out why it doesn't work. Thanks in advance!
Should use NSSortDescriptor with your fetched query like:
let sectionSortDescriptor = NSSortDescriptor(key: "first_name", ascending: true)
let sortDescriptors = [sectionSortDescriptor]
fetchRequest.sortDescriptors = sortDescriptors
let fetchedPerson = try context.fetch(fetchRequest) as! [Contact]
It may solved your problem. let me know if you getting issue after this.
Change your function like this
func getData() {
// 1. Create context
let context = CoreDataController.persistentContainer.viewContext
// 2. RecoverData from Database with fetchRequest
do {
let fetchRequest = Contact.fetchRequest()
let sectionSortDescriptor = NSSortDescriptor(key: "first_name", ascending: true)
let sortDescriptors = [sectionSortDescriptor]
fetchRequest.sortDescriptors = sortDescriptors
let fetchedPerson = try context.fetch(fetchRequest) as! [Contact]
try listOfContacts = context.fetch(fetchRequest)
} catch {
print("Error \(error.localizedDescription)")
}
}
I have two classes that form a one to many relationship:
class FooA: Object {
dynamic var someAValue: String
let fooBList = List<FooB>()
}
class FooB: Object {
dynamic var someBValue: String
}
I add the object into Realm in this fashion:
let fooA = FooA()
fooA.someAValue = 'a value'
let fooB = FooB()
fooB.someBValue = 'a value'
fooA.fooBList.append(fooB)
let realm = try! Realm()
try! realm.write {
realm.add(fooA)
}
Everything goes well, the object is inserted into Realm. However, when I attempt to access the FooB object like this:
fooA.fooBList[0]
It goes BOOM and the app crashes! So, obviously, I did not insert the relationship object into Realm correctly and the relationship is corrupted. What did I miss in the Realm documentation? Must I create the Array first and add it to the parent object in another fashion?
To update on my question, this is the operation that wants to blow up:
FooApiClient.getFooAObjects()
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .default))
.observeOn(MainScheduler.instance)
.subscribe { [weak self] in
switch $0 {
case .next(let fooAObjects):
self?.fooAObjects = fooAObjects
case .completed:
self?.fooTableView.reloadData()
loadingView?.hide()
case .error(let error):
loadingView?.hide()
self?.handleError(error)
self?.showNoFooView()
}
}
.addDisposableTo(disposeBag)
The invocation: self?.fooTableView.reloadData() causes to the table view to attempt to display the foo objects retrieved and where it goes BOOM.
Try something like this. It may have some mistakes.
class SomeTableViewController: UITableViewController {
var fooA : FooA?
override func viewDidLoad() {
super.viewDidLoad()
let realm = try! Realm()
if let aFooA = realm.objects(FooA.self).first{
fooA = aFooA
self.reloadTableView()
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let _ = fooA{
return fooA!.fooBList.count
}
return 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SomeCell", for: indexPath)
// Configure the cell...
let fooB = fooA!.fooBList[indexPath.row]
cell.titleLabel.text = fooB.someBValue
return cell
}
}