I'm struggle with following challenge. I created table view with custom cell that contains switch. I wanna only one switch can be on i.e, for instance after launch I switched on 3rd switched and then I switched on 7th switch and thus the 3rd one is switched off and so on. I use rx + protocols for cell and don't understand all the way how to determine which switch was toggled. Previously I was going to use filter or map to look up in dataSource array which switch is on and somehow handle this, but now I messed up with it. I'm not sure it's possible without using table view delegate methods. Thanks a lot, hope someone could explain where I am wrong.
//My cell looks like this:
// CellViewModel implementation
import Foundation
import RxSwift
protocol ViewModelProtocol {
var bag:DisposeBag {get set}
func dispose()
}
class ViewModel:ViewModelProtocol {
var bag = DisposeBag()
func dispose() {
self.bag = DisposeBag()
}
}
protocol CellViewModelProtocol:ViewModelProtocol {
var isSwitchOn:BehaviorSubject<Bool> {get set}
}
class CellVM:ViewModel, CellViewModelProtocol {
var isSwitchOn: BehaviorSubject<BooleanLiteralType> = BehaviorSubject(value: false)
let internalBag = DisposeBag()
override init() {
}
}
//My Cell implementation
import UIKit
import RxSwift
import RxCocoa
class Cell:UITableViewCell {
static let identifier = "cell"
#IBOutlet weak var stateSwitch:UISwitch!
var vm:CellViewModelProtocol? {
didSet {
oldValue?.dispose()
self.bindUI()
}
}
var currentTag:Int?
var bag = DisposeBag()
override func awakeFromNib() {
super.awakeFromNib()
self.bindUI()
}
override func prepareForReuse() {
super.prepareForReuse()
self.bag = DisposeBag()
}
private func bindUI() {
guard let vm = self.vm else { return }
self.stateSwitch.rx.controlEvent(.valueChanged).withLatestFrom(self.stateSwitch.rx.value).observeOn(MainScheduler.asyncInstance).bind(to: vm.isSwitchOn).disposed(by: vm.bag)
}
}
//TableViewController implementation
import UIKit
import RxSwift
import RxCocoa
class TableViewController: UITableViewController {
private var dataSource:[CellViewModelProtocol] = []
var vm = TableViewControllerVM()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.estimatedRowHeight = 70
self.tableView.rowHeight = UITableView.automaticDimension
self.bindUI()
}
private func bindUI() {
vm.dataSource.observeOn(MainScheduler.asyncInstance).bind { [weak self] (dataSource) in
self?.dataSource = dataSource
self?.tableView.reloadData()
}.disposed(by: vm.bag)
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataSource.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier, for: indexPath) as! Cell
if cell.vm == nil {
cell.vm = CellVM()
}
return cell
}
}
class TableViewControllerVM:ViewModel {
var dataSource:BehaviorSubject<[CellViewModelProtocol]> = BehaviorSubject(value: [])
let internalBag = DisposeBag()
override init() {
super.init()
dataSource.onNext(createDataSourceOf(size: 7))
self.handleState()
}
private func createDataSourceOf(size:Int) -> [CellViewModelProtocol] {
var arr:[CellViewModelProtocol] = []
for _ in 0..<size {
let cell = CellVM()
arr.append(cell)
}
return arr
}
private func handleState() {
}
}
Maybe this code will help you:
extension TableViewController {
// called from viewDidLoad
func bind() {
let cells = (0..<7).map { _ in UUID() } // each cell needs an ID
let active = ReplaySubject<UUID>.create(bufferSize: 1) // tracks which is the currently active cell by ID
Observable.just(cells) // wrap the array in an Observable
.bind(to: tableView.rx.items(cellIdentifier: "Cell", cellType: Cell.self)) { _, element, cell in
// this subscription causes the inactive cells to turn off
active
.map { $0 == element }
.bind(to: cell.toggleSwitch.rx.isOn)
.disposed(by: cell.disposeBag)
// this subscription watches for when a cell is set to on.
cell.toggleSwitch.rx.isOn
.filter { $0 }
.map { _ in element }
.bind(to: active)
.disposed(by: cell.disposeBag)
}
.disposed(by: disposeBag)
}
}
Have a similar UI,so tested locally and it works.But not very neat code.
ProfileCellViewModel
struct ProfileCellViewModel {
// IMPORTANT!!!
var bibindRelay: BehaviorRelay<Bool>?
}
ProfileCell
final class ProfileCell: TableViewCell {
#IBOutlet weak var topLabel: Label!
#IBOutlet weak var centerLabel: Label!
#IBOutlet weak var bottomLabel: Label!
#IBOutlet weak var onSwitch: Switch!
public var vm: ProfileCellViewModel? {
didSet {
// IMPORTANT!!!
if let behaviorRelay = vm?.bibindRelay {
(onSwitch.rx.controlProperty(editingEvents: .valueChanged,
getter: { $0.isOn }) { $0.isOn = $1 } <-> behaviorRelay)
.disposed(by: self.rx.reuseBag)
}
}
}
}
ProfileViewModel
final class ProfileViewModel: ViewModel, ViewModelType {
struct Input {
let loadUserProfileStarted: BehaviorRelay<Void>
}
struct Output {
let userItems: BehaviorRelay<[ProfileCellViewModel]>
let chatRelay: BehaviorRelay<Bool>
let callRelay: BehaviorRelay<Bool>
}
let input = Input(loadUserProfileStarted: BehaviorRelay<Void>(value: ()))
let output = Output(userItems: BehaviorRelay<[ProfileCellViewModel]>(value: []),
chatRelay: BehaviorRelay<Bool>(value: false),
callRelay: BehaviorRelay<Bool>(value:false))
override init() {
super.init()
// IMPORTANT!!!
Observable.combineLatest(output.chatRelay,output.callRelay).pairwise().map { (arg0) -> Int in
let (pre, curr) = arg0
let preFlag = [pre.0,pre.1].filter { $0 == true }.count == 1
let currFlag = [curr.0,curr.1].filter { $0 == true }.count == 2
if preFlag && currFlag {
return [pre.0,pre.1].firstIndex(of: true) ?? 0
}
return -1
}.filter {$0 >= 0}.subscribe(onNext: { (value) in
[self.output.chatRelay,self.output.callRelay][value].accept(false)
}).disposed(by: disposeBag)
}
private func createProfileCellItems(user: User) -> [ProfileCellViewModel] {
// IMPORTANT!!!
let chatCellViewModel = ProfileCellViewModel(topText: nil,
centerText: R.string.i18n.chat(),
bottomText: nil,
switchStatus: true,
bibindRelay: output.chatRelay)
// IMPORTANT!!!
let callCellViewModel = ProfileCellViewModel(topText: nil,
centerText: R.string.i18n.call(),
bottomText: nil,
switchStatus: true,
bibindRelay: output.callRelay)
return [roleCellViewModel,
teamCellViewModel,
statusCellViewModel,
sinceCellViewModel,
chatCellViewModel,
callCellViewModel]
}
}
I mark the codes you should pay attention to with // IMPORTANT!!!
Related
I made a test application following the example of Google with github, but with a few changes (less keys used in the firestore and less filters).
The problem is this, the app crashing when I added new keys in the firestore, but the app works with two keys previously added.
Crashes and shows error on fatalError("error"). I can not understand why with two keys the application works, but if i begin to use the third key (hall) then the app crashes.
What could be the problem?
It's my code:
class ViewControllerTwo: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
private var sweets: [Sweet] = []
private var document: [DocumentSnapshot] = []
fileprivate var query: Query? {
didSet {
if let listener = listener {
listener.remove()
}
}
}
private var listener: FIRListenerRegistration?
fileprivate func observeQuery() {
guard let query = query else { return }
stopObserving()
listener = query.addSnapshotListener { [unowned self] (snapshot, error) in
guard let snapshot = snapshot else {
print("Error fetching snapshot results: \(error!)")
return
}
let models = snapshot.documents.map { (document) -> Sweet in
if let model = Sweet(dictionary: document.data()) {
return model
} else {
fatalError("error")
}
}
self.sweets = models
self.document = snapshot.documents
self.tableView.reloadData()
}
}
#IBAction func filterButton(_ sender: Any) {
present(filters.navigationController, animated: true, completion: nil)
}
lazy private var filters: (navigationController: UINavigationController, filtersController: FilterViewController) = {
return FilterViewController.fromStoryboard(delegate: self)
}()
fileprivate func stopObserving() {
listener?.remove()
}
fileprivate func baseQuery() -> Query {
return Firestore.firestore().collection("sweets").limit(to: 50)
}
override func viewDidLoad() {
super.viewDidLoad()
query = baseQuery()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
observeQuery()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
stopObserving()
}
deinit {
listener?.remove()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sweets.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ViewControllerCell
let sweet = sweets[indexPath.row]
cell.studioNameLabel.text = sweet.name
cell.studioAddressLabel.text = sweet.content
cell.hallNameLabel.text = sweet.hall
return cell
}
}
extension ViewControllerTwo: FiltersViewControllerDelegate {
func query(withCategory title: String?) -> Query {
var filtered = baseQuery()
if let title = title, !title.isEmpty {
filtered = filtered.whereField("title", isEqualTo: title)
}
return filtered
}
func controller(_ controller: FilterViewController, didSelectCategory title: String?) {
let filtered = query(withCategory: title)
self.query = filtered
observeQuery()
}
}
class ViewControllerCell: UITableViewCell {
#IBOutlet weak var studioNameLabel: UILabel!
#IBOutlet weak var studioAddressLabel: UILabel!
#IBOutlet weak var hallNameLabel: UILabel!
}
And my struct:
protocol DocumentSerializable {
init?(dictionary:[String:Any])
}
struct Sweet {
var name:String
var content:String
var hall:String
var dictionary:[String:Any] {
return [
"name": name,
"content" : content,
"hall" : hall
]
}
}
extension Sweet : DocumentSerializable {
static let title = [
"one",
"two",
"three",
"four"
]
init?(dictionary: [String : Any]) {
guard let name = dictionary["name"] as? String,
let content = dictionary["content"] as? String,
let hall = dictionary["hall"] as? String else { return nil }
self.init(name: name, content: content, hall: hall)
}
}
My project in google drive
google drive
google service info.plist
You just need to reinstall app once you add any new key to you existing structure.
So you should decide before structure implementation that what keys you will need. Or you can reinstall app if you add new key in future.
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.
I am using library SJSegmentedViewController for my project, github link to pod
Problem:
I have main view controller (FilterVC) on which I have a button "APPLY", on its action I want to access an array stored in another viewcontroller (FilterSkillVC), I am doing this using delegation, but still what I get is an empty instance
UPDATED
MY FilterVC code
import UIKit
import SJSegmentedScrollView
protocol FilterVCDelegate {
func btnApply()
}
class FilterVC: UIViewController {
var selectedSegment: SJSegmentTab?
var segmentedVC : SJSegmentedViewController?
var vcDelegate : FilterVCDelegate?
#IBOutlet weak var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
segmentViewInitialization()
}
#IBAction func btnApply(_ sender: Any) {
vcDelegate?.btnApply()
}
}
extension FilterVC {
var titles: [String] {
return [SegmentTitles.skillSet.rawValue,
SegmentTitles.cuisines.rawValue,
SegmentTitles.others.rawValue ]
}
var tabs: [String] {
return [StoryboardId.skillSet.rawValue,
StoryboardId.skillSet.rawValue,
StoryboardId.others.rawValue ]
}
func segmentViewInitialization() {
segmentedVC = CSSegment.setupTabs(storyboard: self.storyboard, tabs: tabs, titles: titles) as? SJSegmentedViewController
segmentedVC?.delegate = self
segmentedVC?.selectedSegmentViewHeight = 2.0
segmentedVC?.segmentTitleColor = .white
segmentedVC?.selectedSegmentViewColor = AppColor.secondary.value
segmentedVC?.segmentBackgroundColor = AppColor.primary.value
segmentedVC?.segmentViewHeight = 64.0
segmentedVC?.segmentShadow = SJShadow.light()
segmentedVC?.segmentTitleFont = AppFont.avenirMedium.size(14.0)
containerView.addSubview((segmentedVC?.view)!)
segmentedVC?.view.frame = containerView.bounds
}
}
extension FilterVC: SJSegmentedViewControllerDelegate {
func didMoveToPage(_ controller: UIViewController, segment: SJSegmentTab?, index: Int) {
if index != tabs.count-1 {
let filterVC = controller as! FilterSkillVC
filterVC.updateCurrentHeader(currentTab:SegmentTitles(rawValue: titles[index])!)
}
if selectedSegment != nil {
selectedSegment?.titleColor(.white)
}
if (segmentedVC?.segments.count)! > 0 {
selectedSegment = segmentedVC?.segments[index]
selectedSegment?.titleColor(AppColor.secondary.value)
}
}
}
My Skill VC code
import UIKit
class FilterSkillVC: UIViewController {
#IBOutlet var tblView: UITableView!
var instance = FilterVC()
lazy var arraySkills = [JSTags]()
lazy var arrayCuisines = [JSTags]()
var arrayID = [String]()
var currentHeader: SegmentTitles = .skillSet
override func viewDidLoad() {
super.viewDidLoad()
apiSkillCall()
apiCuisineCall()
registerCell(cellId: .filterListCell, forTableView: tblView)
tblView.tableFooterView = UIView()
// let instance = FilterVC()
instance.vcDelegate = self
}
func updateCurrentHeader(currentTab : SegmentTitles){
currentHeader = currentTab
tblView.reloadData()
}
//MARK: ----- Custom Methods
func countForHeader() -> NSInteger {
switch currentHeader {
case .skillSet:
return arraySkills.count
case .cuisines:
return arrayCuisines.count
default:
return 0
}
}
func titleForHeader(_ index: NSInteger) -> (name: String?, obj: AnyObject?) {
switch currentHeader {
case .skillSet:
return (name: arraySkills[index].name, obj: arraySkills[index])
case .cuisines:
return (name: arrayCuisines[index].name, obj: arrayCuisines[index])
default:
return (name: nil, obj: nil)
}
}
//MARK: ----- Handle Response Methods
func handleSkillsResponse(response: Response) {
switch response{
case .success(let response):
if let skills = response as? [JSTags] {
self.arraySkills = skills
}
case .failure(let str):
Alerts.shared.show(alert: .oops, message: /str, type: .error)
case .dataNotExist(let str):
Alerts.shared.show(alert: .oops, message: str, type: .info)
}
tblView.reloadData()
}
func handleCuisineResponse(response: Response) {
switch response{
case .success(let response):
if let cuisines = response as? [JSTags] {
self.arrayCuisines = cuisines
tblView.reloadData()
}
case .failure(let str):
Alerts.shared.show(alert: .oops, message: /str, type: .error)
case .dataNotExist(let str):
Alerts.shared.show(alert: .oops, message: str, type: .info)
}
}
//MARK: API Methods
func apiSkillCall() {
APIManager.shared.request(with: ProfileEndPoint.fetchSkills()) { (response) in
self.handleSkillsResponse(response: response)
}
}
func apiCuisineCall() {
APIManager.shared.request(with: ProfileEndPoint.fetchCuisines()) { (response) in
self.handleCuisineResponse(response: response)
}
}
}
extension FilterSkillVC : UITableViewDelegate, UITableViewDataSource, FilterListCellDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return countForHeader()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.filterListCell.rawValue) as! FilterListCell
let filter = titleForHeader(indexPath.row)
cell.lblFilterLabel.text = filter.name
//Mark: Cell delegate
cell.delegate = self
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 70
}
//Mark: FilterCellDelegate Method
func buttonTapped(cell: FilterListCell) {
if let indexPath = self.tblView.indexPath(for: cell) {
print("Button tapped on row \(indexPath.row)")
if currentHeader == .skillSet {
arraySkills[indexPath.row].isSelected = !arraySkills[indexPath.row].isSelected
}
else {
arrayCuisines[indexPath.row].isSelected = !arrayCuisines[indexPath.row].isSelected
}
}
}
}
extension FilterSkillVC : FilterVCDelegate {
func btnApply() {
for object in arraySkills {
if object.isSelected {
arrayID.append((object.id) ?? "")
}
}
for object in arrayCuisines {
if object.isSelected {
arrayID.append((object.id) ?? "")
}
}
}
}
You are losing the reference to the instance as soon as the viewDidLoad method is completed.
Make instance a global Variable.
Like so :
import UIKit
class FilterSkillVC: UIViewController {
#IBOutlet var tblView: UITableView!
var instance = FilterVC() //New line added here.
lazy var arraySkills = [JSTags]()
lazy var arrayCuisines = [JSTags]()
var arrayID = [String]()
var currentHeader: SegmentTitles = .skillSet
override func viewDidLoad() {
super.viewDidLoad()
apiSkillCall()
apiCuisineCall()
registerCell(cellId: .filterListCell, forTableView: tblView)
tblView.tableFooterView = UIView()
//let instance = FilterVC() //Commented this.
instance.vcDelegate = self
}
More updates :
In the didMoveToPage method, you are getting a reference to a FilterVC (from a storyboard ??), now this instance of FilterVC is different from the instance of filterVC we created.
Please add this change and try :
func didMoveToPage(_ controller: UIViewController, segment: SJSegmentTab?, index: Int) {
if index != tabs.count-1 {
let filterVC = controller as! FilterSkillVC
filterVC.updateCurrentHeader(currentTab:SegmentTitles(rawValue: titles[index])!)
self.vcDelegate = filterVC // <== Updated this line.
}
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(placeLabel.text)
dbRef!.child("comment").child(key).setValue(commentTextField.text)
dbRef!.child("rating").child(key).setValue(ratingControl.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
I am currently using a table view to display a menu, in which people can click on the add or subtract buttons to increase/decrease their order.
This table view has approximately 30 items and so you have to scroll down to get to all the menu items. The problem is, when you scroll down, the table view cells above the scroll (that are now hidden) lose the data that they have just contained.
For example, if you have a menu item that you have ordered 2 of an item, that 2 on the label has now turned back to 0. This is very object oriented so I am not sure why this is happening.
My table view class:
#IBOutlet weak var appetizerTableView: UITableView!
var appetizerList = [OrderModel]()
let ref = FIRDatabase.database().reference()
override func viewDidLoad() {
appetizerTableView.delegate = self
appetizerTableView.dataSource = self
ref.child("Broadway").child("AppetizerDishes").observeSingleEventOfType(.Value, withBlock: { snapshot in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
print("WILL: \(snap)")
if let postDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let post = OrderModel(postkey: key, postData: postDict)
self.appetizerList.append(post)
}
}
}
self.appetizerTableView.reloadData()
})
}
var data=[OrderModel]()
func addButtonAction(addedList:[String:Float]) {
print("WILLCOHEN:\(addedList)")
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return appetizerList.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let post = appetizerList[indexPath.row]
if let cell = appetizerTableView.dequeueReusableCellWithIdentifier("OrderCell") as? OrderCell{
cell.configureCell(post)
cell.delegate = self
return cell
} else {
return OrderCell()
}
}
}
My cell model class:
class OrderModel {
private var _dishName: String!
private var _dishDescription: String!
private var _numberOfOrders: Int!
private var _postKey: String!
private var _dishPrice: Float!
var dishName: String {
return _dishName
}
var dishDescription: String {
return _dishDescription
}
var numberOfOrders: Int {
get {
return _numberOfOrders
}
set (newVal) {
_numberOfOrders = newVal
}
}
var postKey: String {
return _postKey
}
var dishPrice: Float {
return _dishPrice
}
init(dishName: String, dishDescription: String, numberOfOrders: Int) {
self._dishName = dishName
self._dishDescription = dishDescription
}
init(postkey: String, postData: Dictionary<String, AnyObject>) {
self._postKey = postkey
if let dishName = postData["dishName"] as? String {
self._dishName = dishName
}
if let dishDescription = postData["dishDescription"] as? String {
self._dishDescription = dishDescription
}
if let numberOfOrders = postData["anumberOfOrders"] as? Int {
self._numberOfOrders = numberOfOrders
}
if let dishPrice = postData["dishPrice"] as? Float32 {
self._dishPrice = dishPrice
}
}
}
My cell class:
protocol ClassNameDelegate:class {
func addButtonAction(addedList:[String:Float])
}
var addedList: [String:Float] = [:]
class OrderCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
}
var post: OrderModel!
var link: Link!
#IBOutlet weak var dishName: UILabel!
#IBOutlet weak var dishDescriptionAndPrice: UILabel!
#IBOutlet weak var numberOfOrders: UILabel!
#IBOutlet weak var addOrderBtn: UIButton!
#IBOutlet weak var subtractOderBtn: UIButton!
weak var delegate: ClassNameDelegate?
#IBAction func addButtonPressed(sender: AnyObject) {
if post.numberOfOrders == 9 {
numberOfOrders.text = "9"
} else {
if addedList[post.dishName] != nil {
addedList[post.dishName] = post.dishPrice
} else {
addedList["\(post.dishName) \(Int(numberOfOrders.text!)! + 1)"] = post.dishPrice
}
post.numberOfOrders = post.numberOfOrders - 1
numberOfOrders.text = "\(post.numberOfOrders)"
}
if delegate != nil {
delegate?.addButtonAction(addedList)
}
}
#IBAction func subtractButtonPressed(sender: AnyObject) {
if post.numberOfOrders == 0 {
numberOfOrders.text = "0"
} else {
post.numberOfOrders = post.numberOfOrders + 1
numberOfOrders.text = "\(post.numberOfOrders)"
}
}
func getOrders() -> Dictionary<String, Float> {
return addedList
}
func configureCell(post: OrderModel) {
self.post = post
self.dishName.text = post.dishName
self.dishDescriptionAndPrice.text = post.dishDescription
self.numberOfOrders.text = "0"
}
}
I should mention that I am pulling my table view data from Firebase.
Thank you in advance for any help or suggestions, it is very much appreciated.
You are calling configureCell(post:) on every cell, and in the configureCell(post:) function you set the text value of the numbersOfOrders label to "0". You should probably set the text to a value in the OrderModel/post argument.
Also, you should be sure to always call configureCell(post:) (you are not calling it if you have to manually create an OrderCell), so your tableView(tableView:cellForRowAtIndexPath:) function should look like this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let post = appetizerList[indexPath.row]
let cell = appetizerTableView.dequeueReusableCellWithIdentifier("OrderCell") as? OrderCell ?? OrderCell()
cell.configureCell(post)
cell.delegate = self
return cell
}