Wrapping NSTableView with Swift UI: How to use Bindings? - ios

I am having a hard time managing the data that I want to pass to a
wrapped NSTableView View with SwiftUI. I tried using an NSViewControllerRepresentable but the problem is that my info is not getting updated as it should be.
I have a huge list (hundreds of thousands of items) of an ObservableObject:
class FileObject: ObservableObject, Identifiable {
var id : String
#Published var line : String
init(id : String, line : String) {
self.id = id
self.line = line
}
}
This is how I am calling my NSViewControllerRepresentable inside SwiftUI:
struct NSLinesList: View {
#Binding var lines: [FileObject]
var body: some View {
LinesListTableRepresentable(objects: self.$lines)
}
}
And this is the implementation of the NSViewController which contains a table view.
I have made the Coordinator to be the dataSource and the delegate of the tableView which is inside the NSViewController.
private let kColumnIDContentId = "number"
private let kColumnIdContent = "content"
struct LinesListTableRepresentable: NSViewControllerRepresentable {
typealias NSViewControllerType = LinesListViewController
#Binding var objects : [FileObject]
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
func makeNSViewController(context: Context) -> LinesListViewController {
let storyboard = NSStoryboard.init(name: .init("Main"), bundle: nil)
let controller = storyboard.instantiateController(withIdentifier: "LinesListViewController") as! LinesListViewController
return controller
}
func updateNSViewController(_ nsViewController: NSViewControllerType, context: Context) {
nsViewController.tableView.dataSource = context.coordinator
nsViewController.tableView.delegate = context.coordinator
log.info("update the table view with \(self.objects.count) items")
nsViewController.refresh()
}
}
extension LinesListTableRepresentable {
class Coordinator : NSObject, NSTableViewDelegate, NSTableViewDataSource {
private let parent: LinesListTableRepresentable
init(_ representable : LinesListTableRepresentable) {
parent = representable
super.init()
}
#objc public func numberOfRows(in tableView: NSTableView) -> Int {
log.info("current object count inside table view: \(self.parent.objects.count)")
return self.parent.objects.count
}
#objc func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
guard let columnId = tableColumn?.identifier else { return nil }
let currentLine = self.parent.objects[row]
switch columnId {
case .init(kColumnIDContentId):
let tableCellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "lineNumbercell"), owner: self) as! LogNumberTableCell
tableCellView.contentField.stringValue = "\(row)"
tableCellView.wantsLayer = true
return tableCellView
case .init(kColumnIdContent):
let tableCellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "lineContentCell"), owner: self) as! LogContentTableCell
tableCellView.contentField.stringValue = currentLine.line
default:
break
}
return nil
}
}
}
I am putting some objects inside the objects, and although the LinesListTableRepresentable is getting the objects updated, the Coordinator always has 0 items!
My console output is this one:
2020-08-02T15:56:21+0300 info: update the table view with 2275 items
2020-08-02T15:56:21+0300 info: current object count inside table view: 0
So it seems that the self.parent.objects.count is always zero.
Can someone help with this?

Related

Swift - How do I decode json from a REST API

I am trying to make a GET from a REST API in swift. When I use the print statement (print(clubs)) I see the expected response in the proper format. But in the VC is gives me an empty array.
Here is the code to talk to the API
extension ClubAPI {
public enum ClubError: Error {
case unknown(message: String)
}
func getClubs(completion: #escaping ((Result<[Club], ClubError>) -> Void)) {
let baseURL = self.configuration.baseURL
let endPoint = baseURL.appendingPathComponent("/club")
print(endPoint)
API.shared.httpClient.get(endPoint) { (result) in
switch result {
case .success(let response):
let clubs = (try? JSONDecoder().decode([Club].self, from: response.data)) ?? []
print(clubs)
completion(.success(clubs))
case .failure(let error):
completion(.failure(.unknown(message: error.localizedDescription)))
}
}
}
}
and here is the code in the VC
private class ClubViewModel {
#Published private(set) var clubs = [Club]()
#Published private(set) var error: String?
func refresh() {
ClubAPI.shared.getClubs { (result) in
switch result {
case .success(let club):
print("We have \(club.count)")
self.clubs = club
print("we have \(club.count)")
case .failure(let error):
self.error = error.localizedDescription
}
}
}
}
and here is the view controller code (Before the extension)
class ClubViewController: UIViewController {
private var clubs = [Club]()
private var subscriptions = Set<AnyCancellable>()
private lazy var dataSource = makeDataSource()
enum Section {
case main
}
private var errorMessage: String? {
didSet {
}
}
private let viewModel = ClubViewModel()
#IBOutlet private weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.subscriptions = [
self.viewModel.$clubs.assign(to: \.clubs, on: self),
self.viewModel.$error.assign(to: \.errorMessage, on: self)
]
applySnapshot(animatingDifferences: false)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.viewModel.refresh()
}
}
extension ClubViewController {
typealias DataSource = UITableViewDiffableDataSource<Section, Club>
typealias Snapshot = NSDiffableDataSourceSnapshot<Section, Club>
func applySnapshot(animatingDifferences: Bool = true) {
// Create a snapshot object.
var snapshot = Snapshot()
// Add the section
snapshot.appendSections([.main])
// Add the player array
snapshot.appendItems(clubs)
print(clubs.count)
// Tell the dataSource about the latest snapshot so it can update and animate.
dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
}
func makeDataSource() -> DataSource {
let dataSource = DataSource(tableView: tableView) { (tableView, indexPath, club) -> UITableViewCell? in
let cell = tableView.dequeueReusableCell(withIdentifier: "ClubCell", for: indexPath)
let club = self.clubs[indexPath.row]
print("The name is \(club.name)")
cell.textLabel?.text = club.name
return cell
}
return dataSource
}
}
You need to apply a new snapshot to your table view once you have fetched the clubs. Your current subscriber simply assigns a value to clubs and nothing more.
You can use a sink subscriber to assign the new clubs value and then call applySnapshot. You need to ensure that this happens on the main queue, so you can use receive(on:).
self.subscriptions = [
self.viewModel.$clubs.receive(on: RunLoop.main).sink { clubs in
self.clubs = clubs
self.applySnapshot()
},
self.viewModel.$error.assign(to: \.errorMessage, on: self)
]

How to separate values of second table view in relation of clicked row of first table view?

i'm new to Swift and need your help.
In my app i have two view controllers. Both have a dynamic table view.
The first view controller and his table shows some categories (named Groups) and the second one shows some objects (named Single Groups) which belongs to these categories. The user can add and delete all content in both table views.
I'm saving the values from each table view in an array and using two entities in core data (one entity for each table view). In core data i save the ID of a category/object as UUID and the Title as a String. Also every UUID gets an index, so every value has its own place.
Now to my problem:
When I click on a row in first table view the next view controller (detailViewController) shows up. I can add some stuff and everything works fine.
But when I go back and click on an other row in first table view it shows the same things on detailViewController again.
So I think I have to separate the objects in second tableview for each row in first table view.
But i don't know how to do this.
What I thought about is to save the array from secondViewController in relation to the index of the rows value in first table view. The difficulty is that i separated all content from my app in different files. So Core Data has it own files as well as the storage with the arrays and the view controller itself.
App Structure
Because of this I don't know what code you need exactly. I hope thats enough:
Class Group
import Foundation
class Group {
private(set) var groupId : UUID
private(set) var groupTitle : String
init(groupTitle: String) {
self.groupId = UUID()
self.groupTitle = groupTitle
}
init(groupId: UUID, groupTitle: String) {
self.groupId = groupId
self.groupTitle = groupTitle
}
}
Class SingleGroup
import Foundation
class SingleGroup {
private(set) var singleGroupId : UUID
private(set) var singleGroupName : String
private(set) var singleGroupAmount : Double
private(set) var singleGroupTimeStamp : Int64
init(singleGroupName: String, singleGroupAmount: Double, singleGroupTimeStamp: Int64) {
self.singleGroupId = UUID()
self.singleGroupName = singleGroupName
self.singleGroupAmount = singleGroupAmount
self.singleGroupTimeStamp = singleGroupTimeStamp
}
init(singleGroupId: UUID, singleGroupName: String, singleGroupAmount: Double, singleGroupTimeStamp: Int64) {
self.singleGroupId = singleGroupId
self.singleGroupName = singleGroupName
self.singleGroupAmount = singleGroupAmount
self.singleGroupTimeStamp = singleGroupTimeStamp
}
}
Storage Groups
import CoreData
class Storage {
static let storage : Storage = Storage()
private var groupIndexToIdDict : [Int:UUID] = [:]
private var currentIndex : Int = 0
private(set) var managedObjectContext : NSManagedObjectContext
private var managedContextHasBeenSet: Bool = false
//need to init the ManageObjectContext, it will be overwritten when setManagedContext is called from the view controller
init() {
managedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.mainQueueConcurrencyType)
}
func setManagedContext(managedObjectContext: NSManagedObjectContext) {
self.managedObjectContext = managedObjectContext
self.managedContextHasBeenSet = true
let groups = CoreDataHelper.readGroupsFromCoreData(fromManagedObjectContext: self.managedObjectContext)
currentIndex = CoreDataHelper.count
for (index, group) in groups.enumerated() {
groupIndexToIdDict[index] = group.groupId
}
}
func addGroup(groupToBeAdded: Group) {
if managedContextHasBeenSet {
// add group UUID to the dictionary
groupIndexToIdDict[currentIndex] = groupToBeAdded.groupId
// call Core Data Helper to create the new group
CoreDataHelper.createGroupInCoreData(groupToBeCreated: groupToBeAdded, intoManagedObjectContext: self.managedObjectContext)
// increase index
currentIndex += 1
}
}
Storage SingleGroup
import CoreData
class SingleGroupStorage {
static let singleGroupStorage : SingleGroupStorage = SingleGroupStorage()
private var singleGroupIndexToIdDict : [Int:UUID] = [:]
private var currentIndex: Int = 0
private(set) var managedObjectContext : NSManagedObjectContext
private var managedContextHasBeenSet: Bool = false
// need to init the ManagedObjectCOntext, it will be overwritten when setManagedContext is called from the view controller
init() {
managedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.mainQueueConcurrencyType)
}
func setManagedContext(managedObjectContext: NSManagedObjectContext) {
self.managedObjectContext = managedObjectContext
self.managedContextHasBeenSet = true
let singleGroups = SingleGroupsCoreDataHelper.readSingleGroupsFromCoreData(fromManagedObjectContext: self.managedObjectContext)
currentIndex = SingleGroupsCoreDataHelper.countSingleGroup
for (index, singleGroup) in singleGroups.enumerated() {
singleGroupIndexToIdDict[index] = singleGroup.singleGroupId
}
}
func addSingleGroup(singleGroupToBeAdded: SingleGroup) {
if managedContextHasBeenSet {
// add singlegroup UUID to the dictionary
singleGroupIndexToIdDict[currentIndex] = singleGroupToBeAdded.singleGroupId
// call Core Data Helper to create the new single group
SingleGroupsCoreDataHelper.createSingleGroupInCoreData(singleGroupToBeCreated: singleGroupToBeAdded, intoManagedObjectContext: self.managedObjectContext)
// increase index
currentIndex += 1
}
}
Core Data Helper Groups
import Foundation
import CoreData
class CoreDataHelper {
private(set) static var count : Int = 0
static func createGroupInCoreData(groupToBeCreated: Group, intoManagedObjectContext: NSManagedObjectContext) {
// create an entity and new group record
let groupEntity = NSEntityDescription.entity(forEntityName: "Groups", in: intoManagedObjectContext)!
let newGroupToBeCreated = NSManagedObject(entity: groupEntity, insertInto: intoManagedObjectContext)
newGroupToBeCreated.setValue(groupToBeCreated.groupId, forKey: "groupId")
newGroupToBeCreated.setValue(groupToBeCreated.groupTitle, forKey: "groupTitle")
do {
try intoManagedObjectContext.save()
count += 1
} catch let error as NSError {
print("Could not save group. \(error), \(error.userInfo)")
}
}
static func readGroupFromCoreData(groupIdToBeRead: UUID, fromManagedObjectContext: NSManagedObjectContext) -> Group? {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Groups")
let groupIdPredicate = NSPredicate(format: "groupId = %#", groupIdToBeRead as CVarArg)
fetchRequest.predicate = groupIdPredicate
do {
let fetchedGroupsFromCoreData = try fromManagedObjectContext.fetch(fetchRequest)
let groupManagedObjectToBeRead = fetchedGroupsFromCoreData[0] as! NSManagedObject
return Group.init(
groupId: groupManagedObjectToBeRead.value(forKey: "groupId") as! UUID,
groupTitle: groupManagedObjectToBeRead.value(forKey: "groupTitle") as! String)
} catch let error as NSError {
// TODO error handling
print("Could not read group. \(error), \(error.userInfo)")
return nil
}
}
Core Data Helper Single Groups
import Foundation
import CoreData
class SingleGroupsCoreDataHelper {
private(set) static var countSingleGroup : Int = 0
static func createSingleGroupInCoreData(singleGroupToBeCreated: SingleGroup, intoManagedObjectContext: NSManagedObjectContext) {
// create an entity and new single group record
let singleGroupEntity = NSEntityDescription.entity(forEntityName: "SingleGroups", in: intoManagedObjectContext)!
let newSingleGroupToBeCreated = NSManagedObject(entity: singleGroupEntity, insertInto: intoManagedObjectContext)
newSingleGroupToBeCreated.setValue(singleGroupToBeCreated.singleGroupId, forKey: "singleGroupId")
newSingleGroupToBeCreated.setValue(singleGroupToBeCreated.singleGroupName, forKey: "singleGroupName")
newSingleGroupToBeCreated.setValue(singleGroupToBeCreated.singleGroupAmount, forKey: "singleGroupAmount")
newSingleGroupToBeCreated.setValue(singleGroupToBeCreated.singleGroupTimeStamp, forKey: "singleGroupTimeStamp")
do {
try intoManagedObjectContext.save()
countSingleGroup += 1
} catch let error as NSError {
print("Could not save group. \(error), \(error.userInfo)")
}
}
static func readSingleGroupFromCoreData(singleGroupIdToBeRead: UUID, fromManagedObjectContext: NSManagedObjectContext) -> SingleGroup? {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "SingleGroups")
let singleGroupIdPredicate = NSPredicate(format: "singleGroupId = %#", singleGroupIdToBeRead as CVarArg)
fetchRequest.predicate = singleGroupIdPredicate
do {
let fetchedSingleGroupsFromCoreData = try fromManagedObjectContext.fetch(fetchRequest)
let singleGroupManagedObjectToBeRead = fetchedSingleGroupsFromCoreData[0] as! NSManagedObject
return SingleGroup.init(
singleGroupId: singleGroupManagedObjectToBeRead.value(forKey: "singleGroupId") as! UUID,
singleGroupName: singleGroupManagedObjectToBeRead.value(forKey: "singleGroupName") as! String,
singleGroupAmount: singleGroupManagedObjectToBeRead.value(forKey: "singleGroupAmount") as! Double,
singleGroupTimeStamp: singleGroupManagedObjectToBeRead.value(forKey: "singleGroupTimeStamp") as! Int64)
} catch let error as NSError {
// TODO error handling
print("Could not read single group. \(error), \(error.userInfo)")
return nil
}
}
first Table View
override func numberOfSections(in tableView: UITableView) -> Int {
// return fetchedResultsController.sections?.count ?? 0
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// return objects.count
return Storage.storage.count()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "GroupsTableViewCell", for: indexPath) as! GroupsTableViewCell
if let object = Storage.storage.readGroup(at: indexPath.row) {
cell.groupTitleLabel!.text = object.groupTitle
}
return cell
}
Segue to detailViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetailSegue" {
if let indexPath = tableView.indexPathForSelectedRow {
let navTitle = Storage.storage.readGroup(at: indexPath.row)
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
controller.title = navTitle?.groupTitle
}
}
}
Second Table View
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//return objects.count
return SingleGroupStorage.singleGroupStorage.countSingleGroup()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SingleGroupsTableViewCell", for: indexPath) as! SingleGroupsTableViewCell
if let object = SingleGroupStorage.singleGroupStorage.readSingleGroup(at: indexPath.row) {
cell.singleGroupNameLabel!.text = object.singleGroupName
cell.singleGroupAmountLabel!.text = String(format: "%.2f", object.singleGroupAmount)
cell.singleGroupDateLabel!.text = DateHelper.convertDate(date: Date.init(seconds: object.singleGroupTimeStamp))
}
return cell
}
I had searched for a solution a few weeks now but could not find anything.
Hope you could understand what my problem is and have any solutions or tips how to solve it.
Update:
Groups - read(at:)
func readGroup(at: Int) -> Group? {
if managedContextHasBeenSet {
// check input index
if at < 0 || at > currentIndex - 1 {
// TODO error handling
print("Can not read Groups.")
return nil
}
// get group UUID from dictionary
let groupUUID = groupIndexToIdDict[at]
let groupReadFromCoreData: Group?
groupReadFromCoreData = CoreDataHelper.readGroupFromCoreData(groupIdToBeRead: groupUUID!, fromManagedObjectContext: self.managedObjectContext)
return groupReadFromCoreData
}
return nil
}
Same for Single Group
func readSingleGroup(at: Int) -> SingleGroup? {
if managedContextHasBeenSet {
// check input index
if at < 0 || at > currentIndex - 1 {
// TODO error handling
print("Can not read SingleGroups.")
return nil
}
// get single group UUID from dicitionary
let singleGroupUUID = singleGroupIndexToIdDict[at]
let singleGroupReadFromCoreData: SingleGroup?
singleGroupReadFromCoreData = SingleGroupsCoreDataHelper.readSingleGroupFromCoreData(singleGroupIdToBeRead: singleGroupUUID!, fromManagedObjectContext: self.managedObjectContext)
return singleGroupReadFromCoreData
}
return nil
}

NSPopoverTouchBarItems in NSScrollView (NSTouchBar)

Is there a way to add an array of NSPopoverTouchBarItems into a NSScrollView?
Currently, my view hierarchy resembles the below list.
NSTouchBar
NSCustomTouchBarItem
NSScrollView
NSStackView
Array of NSButtons
The above hierarchy outputs the following screenshot.
In sum, the end goal is to replace the array of NSButtons with NSPopoverTouchBarItems.
I believe what you need is the use of NSScrubber to be able to scroll or have fixed position of multiple buttons including NSPopoverTouchBarItem
https://developer.apple.com/documentation/appkit/nsscrubber
Check out this repository for more information and sample codes that might help you:
https://github.com/loretoparisi/touchbar
import Cocoa
fileprivate extension NSTouchBar.CustomizationIdentifier {
static let popoverBar = NSTouchBar.CustomizationIdentifier("com.TouchBarCatalog.popoverBar")
}
fileprivate extension NSTouchBarItem.Identifier {
static let scrubberPopover = NSTouchBarItem.Identifier("com.TouchBarCatalog.TouchBarItem.scrubberPopover")
}
class PopoverScrubber: NSScrubber {
var presentingItem: NSPopoverTouchBarItem?
}
class PopoverScrubberViewController: NSViewController {
// MARK: NSTouchBar
override func makeTouchBar() -> NSTouchBar? {
let touchBar = NSTouchBar()
touchBar.delegate = self
touchBar.customizationIdentifier = .popoverBar
touchBar.defaultItemIdentifiers = [.scrubberPopover]
touchBar.customizationAllowedItemIdentifiers = [.scrubberPopover]
return touchBar
}
}
// MARK: NSTouchBarDelegate
extension PopoverScrubberViewController: NSTouchBarDelegate {
func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
guard identifier == NSTouchBarItem.Identifier.scrubberPopover else { return nil }
let popoverItem = NSPopoverTouchBarItem(identifier: identifier)
popoverItem.collapsedRepresentationLabel = "Scrubber Popover"
popoverItem.customizationLabel = "Scrubber Popover"
let scrubber = PopoverScrubber()
scrubber.register(NSScrubberTextItemView.self, forItemIdentifier: NSUserInterfaceItemIdentifier(rawValue: "TextScrubberItemIdentifier"))
scrubber.mode = .free
scrubber.selectionBackgroundStyle = .roundedBackground
scrubber.delegate = self
scrubber.dataSource = self
scrubber.presentingItem = popoverItem
popoverItem.collapsedRepresentation = scrubber
popoverItem.popoverTouchBar = PopoverTouchBarSample(presentingItem: popoverItem)
return popoverItem
}
}
// MARK: NSScrubber Data Source and delegate
extension PopoverScrubberViewController: NSScrubberDataSource, NSScrubberDelegate {
func numberOfItems(for scrubber: NSScrubber) -> Int {
return 20
}
func scrubber(_ scrubber: NSScrubber, viewForItemAt index: Int) -> NSScrubberItemView {
let itemView = scrubber.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "TextScrubberItemIdentifier"), owner: nil) as! NSScrubberTextItemView
itemView.textField.stringValue = String(index)
return itemView
}
func scrubber(_ scrubber: NSScrubber, didSelectItemAt index: Int) {
print("\(#function) at index \(index)")
if let popoverScrubber = scrubber as? PopoverScrubber,
let popoverItem = popoverScrubber.presentingItem {
popoverItem.showPopover(nil)
}
}
}

How to disable automatic scrolling to top

How can I disable auto scroll to the top of table view when I append new data to data source of it.
The problem is visible in the following gif.
Edit: Added ViewController, ViewModel and MessageEntity.
Used frameworks are: RxSwift, RxDataSources for reactive datasource of table view.
ViewController:
class RabbitMqVC: BaseViewController {
struct Cells {
static let message = ReusableCell<MessageCell>(nibName: "MessageCell")
static let messageTheir = ReusableCell<MessageCellTheir>(nibName: "MessageCellTheir")
}
#IBOutlet
weak var tableView: UITableView!{
didSet{
rabbitMqViewModel.sections
.drive(tableView.rx.items(dataSource: dataSource))
.addDisposableTo(disposeBag)
}
}
private let dataSource = RxTableViewSectionedAnimatedDataSource<RabbitMqViewModel.MessageSections>()
private let rabbitMqViewModel : rabbitMqViewModel
init(rabbitMqViewModel: rabbitMqViewModel) {
self.rabbitMqViewModel = rabbitMqViewModel
super.init(nibName: "RabbitMqVC", bundle: nil)
dataSource.configureCell = { _, tableView, indexPath, item in
let randomNumber = 1.random(to: 2)
let cell = randomNumber == 1 ? tableView.dequeue(Cells.message, for: indexPath) : tableView.dequeue(Cells.messageTheir, for: indexPath)
cell.message = item
return cell
}
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(Cells.message)
tableView.register(Cells.messageTheir)
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 80
}
}
ViewModel:
class RabbitMqViewModel: ViewModel {
enum MessageSections: AnimatableSectionModelType {
typealias Item = MessageEntity
typealias Identity = Int
case messages(messages: [MessageEntity])
var items: [Item] {
switch self {
case .messages(messages:let messages):
return messages
}
}
var identity: Int {
return 1
}
init(original: MessageSections, items: [Item]) {
switch original {
case .messages:
self = .messages(messages: items)
}
}
}
// input
let didLoad = PublishSubject<Void>()
//output
let sections: Driver<[MessageSections]>
init(service: RabbitMqService,){
let messages: Observable<[MessageEntity]> = didLoad
.flatMapLatest { _ -> Observable<[MessageEntity]> in
return service.listenMessages()
}
.share()
self.sections = messages
.map { (messages) -> [RabbitMqViewModel.MessageSections] in
var sections: [MessageSections] = []
sections.append(.messages(messages: messages))
return sections
}
.asDriver(onErrorJustReturn: [])
}
}
MessageEntity:
struct MessageEntity {
let id: String
let conversationId: String
let messageText: String
let sent: Date
let isSentByClient: Bool
let senderName: String
let commodityClientId : Int?
}
extension MessageEntity: IdentifiableType, Equatable {
typealias Identity = Int
public var identity: Identity {
return id.hashValue
}
public static func ==(lhs: MessageEntity, rhs: MessageEntity) -> Bool {
return lhs.id == rhs.id
}
}
estimatedRowHeight = 1
Fixed it.

Read data from firebase and populate TableViewCell

Hello I have a tableviewcell where i can populate it with custom data from my pc, but i can't use my firebase data on the cell that i have made. I want to fill my cell with String and Int, not only Strings. My code is:
PlacesTableViewController Class
import UIKit
import FirebaseDatabase
class PlacesTableViewController: UITableViewController {
//MARK: Properties
#IBOutlet weak var placesTableView: UITableView!
//database reference
var dbRef:FIRDatabaseReference?
var places = [Places]()
var myList:[String] = []
//handler
var handle:FIRDatabaseHandle?
override func viewDidLoad() {
super.viewDidLoad()
dbRef = FIRDatabase.database().reference()
// Loads data to cell.
loadData()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return places.count
//return myList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "PlacesTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? PlacesTableViewCell else {
fatalError("The dequeued cell is not an instance of PlacesTableView Cell.")
}
let place = places[indexPath.row]
cell.placeLabel.text = place.name
cell.ratingControl.rating = place.rating
//cell.placeLabel.text = myList[indexPath.row]
//cell.ratingControl.rating = myRatings[indexPath.row]
return cell
}
//MARK: Private Methods
private func loadData() {
handle = dbRef?.child("placeLabel").observe(.childAdded, with: { (snapshot) in
if let item = snapshot.value as? String
{
self.myList.append(item)
self.placesTableView.reloadData()
print (item)
}
})
/* handle = dbRef?.child("rating").observe(.childAdded, with: { (snapshot) in
if let item = snapshot.value as? String
{
self.myList.append(item)
self.placesTableView.reloadData()
}
})*/
/*guard let place1 = Places(name: "Veranda", rating: 4) else {
fatalError("Unable to instantiate place1")
}
places += [place1]*/
}
}
Places Class
import UIKit
class Places {
//MARK: Properties
var name: String
var rating: Int
//MARK:Types
struct PropertyKey {
static let name = "name"
static let rating = "rating"
}
//MARK: Initialization
init?(name: String, rating: Int) {
// Initialize stored properties.
self.name = name
self.rating = rating
// Initialization should fail if there is no name or if the rating is negative.
// The name must not be empty
guard !name.isEmpty else {
return nil
}
// The rating must be between 0 and 5 inclusively
guard (rating >= 0) && (rating <= 5) else {
return nil
}
}
}
PlacesTableViewCell Class
import UIKit
import FirebaseDatabase
class PlacesTableViewCell: UITableViewCell, UITableViewDelegate {
//MARK: Properties
#IBOutlet weak var placeLabel: UILabel!
#IBOutlet weak var ratingControl: RatingControl!
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
}
}
Firebase Database
Assuming your database layout should instead look like this (see comments above):
...
placeLabel
|
-- XXY: "Veranda"
-- YYY: "Dio Con Dio"
rating
|
-- XXX: 4
-- YYY: 1
...
then try this:
private func loadData() {
dbRef!.child("placeLabel").observe(.childAdded) {
(snapshot) in
let label = snapshot.value as! String
self.updatePlace(snapshot.key, label: label)
}
dbRef!.child("rating").observe(.childAdded) {
(snapshot) in
let rating = snapshot.value as! Int
self.updatePlace(snapshot.key, rating: rating)
}
}
private var loadedLabels = [String: String]()
private var loadedRatings = [String: Int]()
private func updatePlace(_ key: String, label: String? = nil, rating: Int? = nil) {
if let label = label {
loadedLabels[key] = label
}
if let rating = rating {
loadedRatings[key] = rating
}
guard let label = loadedLabels[key], let rating = loadedRatings[key] else {
return
}
if let place = Places(name: label, rating: rating) {
places.append(place)
placesTableView.reloadData()
}
}
By the way, you can temporarily hack your database — using Firebase (nice!) web console — if you want to quickly validate the above solution.
Writing to Database. Try the following code to write the nodes in your database (i.e., this code reuses the same key across all place properties):
let key = dbRef!.child("placeLabel").childByAutoId().key
dbRef!.child("placeLabel").child(key).setValue(placeLab‌​el.text)
dbRef!.child("comment").child(key).setValue(commentText‌​Field.text)
dbRef!.child("rating").child(key).setValue(ratingContro‌​l.rating)
Hacking the Database. To edit the database manually, try:
open http://console.firebase.google.com
select your app
open database option
add a new node with the right key
delete the old node

Resources