I am relatively new on firebase. I want to update existing data on my firebase database. I have UITableView and UITableViewCells on my Xcode project, when user touch (tap gesture), for example a outlet on cell view, I want to update Firebase Database but this could be any cell on tableview. How to find this cell which user touched on screen, on firebase database and update its messageVoteScore value. There are assigned keys but my cells do not know those keys,
I could not figure it out how to match them.(Database/Messages/{"sender": "email"},{"messageBody":"text.."},{"messageVoteScore":"100"}
#objc func voteUpTapped(sender: UITapGestureRecognizer) {
// Update score //
self.messageVoteScore.text = String(Int(self.messageVoteScore.text!)! + 1 )
//observeSingleEventOfType listens for a tap by the current user.
Database.database().reference().child("Messages").observe(.value){
(snapshot) in
if let snapshotValue = snapshot.value as? [String : String] {
print(snapshotValue)
}
}
}
Are you passing class objects to your cell? If so you could use a protocol/delegate for this. A UITableView Cell is a View and shouldn't function as a controller. your UITableViewController should be updating firebase.
you should start by adding something like this to the top of your Cell class:
protocol MyTableViewCellDelegate: class {
func thingDidDo(object: Object, text: String...) //object is the class object you are changing.
then add the delegate to your properties:
weak var delegate: MyTableViewCellDelegate?
then add the IBAction:
#IBAction func thingDidDo(_ sender: Any) {
delegate?.thingDidDo(object: object, text: textView.text...)
}
Now back to your viewController Class:
In your "cellForRowAt" write in the delegate:
cell.delegate = self
then add an extension
extension MyTableViewController: MyTableViewCellDelegate {
func thingDidDo(object: Object, text: String) {
Do whatever you want with object, text...
}
Not sure what kind of function you are trying to perform so I made this as generic as I could
Related
I have a collection view inside a table view. There are two plus minus buttons in collection view cell. Now i have to update a label on the plus minus buttons action which is outside of table view. Thanks in advance.
I have to update a Slot: (label) by clicking on plus minus button.
I tried something like this with the delegate protocol.
I declare a delegate in my collection view class.
protocol SlotsCollectionViewCellDelegate: NSObjectProtocol {
func didTapOnIncrement(Int: Int)
func didTapOnDecrement(Int: Int)
}
//after that,
var delegate: SlotsCollectionViewCellDelegate?
#IBAction func plusBtnAction(_ sender: Any) {
self.delegate?.didTapOnIncrement(Int: cartCount)
}
#IBAction func minusBtnAction(_ sender: Any) {
delegate?.didTapOnDecrement(cell: self)
}
And in my Main View Controller
extension MainViewController: SlotsCollectionViewCellDelegate {
func didTapOnIncrement(Int: Int) {
cartSlot_lbl.text = Int.description
}
func didTapOnDecrement(Int: Int) {
cartSlot_lbl.text = Int.description
}
}
If I understood correctly, each time you push + or - you want to update slot label. In my opinion the easiest and fastest way to achieve this it's using NotificationCenter.default.post
In your collection view cell on button action write:
NotificationCenter.default.post(name: Notification.Name("postAction"), object: numberToIncreaseOrDecrease)
In your MainViewController where you have the slot label, add this code in view did load:
NotificationCenter.default.addObserver(self, selector: #selector(updateSlotValue(_:)), name: NSNotification.Name("postAction"), object: nil)
And out from the view did load add this function:
#objc func updateSlotValue(_ notification: Notification) {
let value = notification.object as! Int
cartSlot_lbl.text.text = value
}
I think delegates are the right choice for that. If it didn't work, please explain why and show some code, you probably forgot to set a delegate reference.
Anyway, here's some more thoughts:
You could use a Reactive Pattern, so that you create a Relay to store your current values, manipulate them by providing input (times etc.) and subscribe to them from the Class where the "Spot:" Label is implemented. Whenever your model changes, your Spot Label will also be changed.
You could also implement something using Notifications. Basically speaking, the difference to using a reactive pattern is not that big, you simply have to care about the "notify" part yourself. Assuming you have something like a Singleton Pattern applied where you store your entire State (selected dates/times, slots etc.), you could do that like this:
extension Notification.Name {
static let modelDidChange = Notification.Name("modelDidChange")
}
// where your model lies
struct YourModel {
var slots: Int = 0
static var singletonInstance: YourModel = YourModel() {
// use the didSet block to react to changes made to the model
didSet {
// send a notification so all subscriber know something has changed
NotificationCenter.default.post(.modelDidChange)
}
}
}
class YourViewControllerWhereTheLabelIs: UIViewController {
// ...
var slotLabel: UILabel?
// ...
init() {
// wherever you initialize your viewcontroller,
// you could also do it in viewWillAppear
// subscribe to the notification to react to changes
NotificationCenter.default.addObserver(self, selector: #selector(modelDidChange), name: .modelDidChange, object: nil)
}
deinit {
// just don't forget to unsubscribe
NotificationCenter.default.removeObserver(self)
}
#objc func modelDidChange() {
// update your label here, this is called whenever YourModel.singletonInstance is changed
self.slotLabel?.text = YourModel.singletonInstance.slots
}
}
Hope that helps you or gives you an idea. If I can be of more help just let me know.
I needed to delegate a click action for my UIView class to my UIViewController class since Swift does not support multiple class inheritance. So i wanted it such that once a button is clicked on my subview, a function in my BrowserViewController class is called.
I am using a protocol to achieve this, but on the function does not triggered when the button is tapped. Please help me out.
View Controller
class BrowseViewController: UIViewController {
var categoryItem: CategoryItem! = CategoryItem() //Category Item
private func setupExplore() {
//assign delegate of category item to controller
self.categoryItem.delegate = self
}
}
// delegate function to be called
extension BrowseViewController: ExploreDelegate {
func categoryClicked(category: ProductCategory) {
print("clicked")
let categoryView = ProductByCategoryView()
categoryView.category = category
categoryView.modalPresentationStyle = .overCurrentContext
self.navigationController?.pushViewController(categoryView, animated: true)
}
}
Explore.swift (subview)
import UIKit
protocol ExploreDelegate: UIViewController {
func categoryClicked(category: ProductCategory)
}
class Explore: UIView {
var delegate: ExploreDelegate?
class CategoryItem: UIView {
var delegate: ExploreDelegate?
var category: ProductCategory? {
didSet {
self.configure()
}
}
var tapped: ((_ category: ProductCategory?) -> Void)?
func configure() {
self.layer.cornerRadius = 6
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.categoryTapped)))
self.layoutIfNeeded()
}
#objc func categoryTapped(_ sender: UIGestureRecognizer) {
delegate?.categoryClicked(category: ProductCategory.everything)
self.tapped?(self.category)
}
}
}
Simply add a print statement inside categoryTapped.
You will then know if it is actually being tapped.
A million things could go wrong, for example, you may have forget to set the UIView to allow intertaction.
After checking that. Next add another print statement inside categoryTapped which shows you whether or not the delegate variable is null.
You'll quickly discover the problem using simple print statements.
print("I got to here!")
It's that easy.
And what about
if delegate == nil { print("it is nil!! oh no!" }
else { print("phew. it is NOT nil.") }
Debugging is really that easy at this level.
Next add a print statement inside setupExplore()
func setupExplore() {
print("setup explore was called")
....
See what happens.
I don't see any piece of code which sets the delegate.
First of all, define delegate as a property inside CategoryItem class, Then you must set the current instance of BrowseViewController to the delegate variable of CategoryItem. Now you can expect your method being called.
There are a few things that could cause the delegate method to not be triggered in this code:
Ensure that isUserInteractionEnabled = true on your CategoryItem. This is probably best done in either the configure() function in the CategoryItem or in the setupExplore() function of the BrowseViewController.
Make sure that the setupExplore() function on the BrowseViewController is being called, and that the category is being set on the CategoryItem to trigger the configure function. Otherwise, either the delegate or the gesture recognizer might not being set.
Side Note - weak vs strong delegate
On a side note, it is usually best practice to make your delegate properties weak var rather that having them be a strong reference, as this makes them prone to strong retain cycles.
Therefore, you might want to consider making the var delegate: ExploreDelegate? on your CategoryItem into weak var delegate: ExploreDelegate?. For more information on this problem, view this post.
this should be straight forward but i m finding hard time in understanding.
i have a class
class Field:NSObject {
var name:String?
var answer:String?
.................
}
Now i also have a viewcontroller in which there is a tableview. I am using Field array as datasource for this tableview. i am passing relavent object from Field array to my custom cell class (see below) which have a UItextfield .
cell.field = self.fields[indexPath.row]
What i want is that when text inside UItextfield is changed this should update our main Field array. Also custom cell class should have fresh copy of Field instance. I hope i have made it clear, please ask if you didn't understand anything. Much obliged.
here is cell class code
class SpeechCell: UITableViewCell, UITextFieldDelegate {
public var field:Field?{
didSet{
self.lblField.text = self.field?.name
self.txtAnswer.placeholder = self.field?.name
}
}
func textFieldDidEndEditing(_ textField: UITextField, reason: UITextFieldDidEndEditingReason) {
self.field?.answer = textField.text!
self.commandDelegate?.didUpdateField(field: self.field!, textField: textField)
}
override func prepareForReuse() {
self.txtAnswer.text = self.field?.answer
}
here is code for tableview
func didUpdateField(field: Field, textField: UITextField) {
self.fields[textField.tag].answer = textField.text!
}
You should use custom delegate method to notify the view controller about the changes in the textfield. In the custom delegate method you should update the fields array. You should change the field in the cell too before calling the custom delegate method.
You should call this delegate method in the textFieldDidChangeText.
So I have TableViewCell's that are being populated from 2 Dicitionaries in my view controller.
var categories : [Int : [String : Any]]!
var assignments : [Int: [String : Any]]!
I have a UiTextField in my cell that the user is supposed to be able to edit. I then want to be able to change the values of certain keys in that dictionary-based off what the user changes and re-display the table with those changes. My main problem is that I don't know how I will be able to access theese variables from within my cell. I have a method in my view controller that takes the row that the text field is in, along with the value of the textField, and updates the dictionaries. What I need is to be able to instantiate the view controller that the cell is in but I need the original instance that already has values loaded into the categories and assignments Dictionaries. If you have any other ideas on how I could accomplish this please post.
You can use delegate for sharing cell-data to your VC:
protocol YourCellDelegate() {
func pickedString(str: String)
}
class YourCell: UITableViewCell {
var delegate: YourCellDelegate! = nil
#IBOutlet weak var textField: UITextField!
.....//some set-method, where you can handle a text
func textHandle() {
guard let del = delegate else { return }
del.pickedString(textField.text)
}
.....
}
And usage in your VC: When you create cell, set its delegate self:
...
cell.delegate = self
...
and sure you VC supported your Delegate Protocol:
class YourVC: UIViewController, YourCellDelegate {
}
And now, you MUST implement protocol method:
class YourVC: UIViewController, YourCellDelegate {
....
func pickedString(str: String) {
}
....
}
All times, when you use textHandle() in your cell, pickedString(str: String) activates in yourVC with string from textField.
Enjoy!
I am working with swift in Xcode 7. I am totally new to Swift, Xcode, and Firebase. I would like to have three UITableViewControllers in my iOS app. The first two TableView controllers will need dynamic content and the third TableView controller will need static content. I would like for the second and third TableView controllers to display data based on what is pressed on the previous TableView controller. All of the data will come from my Firebase. I have no idea where to start. Please point me in the right direction! Thank you!
This question is broad, in that it asks how to do three different tasks.
I think you'll be better off getting answers if you only ask for one thing at a time.
I can help you with populating a UITableViewController with Firebase.
class MyTableViewController: UITableViewController {
// your firebase reference as a property
var ref: Firebase!
// your data source, you can replace this with your own model if you wish
var items = [FDataSnapshot]()
override func viewDidLoad() {
super.viewDidLoad()
// initialize the ref in viewDidLoad
ref = Firebase(url: "<my-firebase-app>/items")
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// listen for update with the .Value event
ref.observeEventType(.Value) { (snapshot: FDataSnapshot!) in
var newItems = [FDataSnapshot]()
// loop through the children and append them to the new array
for item in snapshot.children {
newItems.append(item as! FDataSnapshot)
}
// replace the old array
self.items = newItems
// reload the UITableView
self.tableView.reloadData()
}
}
}
This technique uses the .Value event, you can also use .ChildAdded, but then you have to keep track of .ChildChanged, '.ChildRemoved', and .ChildMoved, which can get pretty complicated.
The FirebaseUI library for iOS handles this pretty easily.
dataSource = FirebaseTableViewDataSource(ref: self.firebaseRef, cellReuseIdentifier: "<YOUR-REUSE-IDENTIFIER>", view: self.tableView)
dataSource.populateCellWithBlock { (cell: UITableViewCell, obj: NSObject) -> Void in
let snap = obj as! FDataSnapshot
// Populate cell as you see fit, like as below
cell.textLabel?.text = snap.key as String
}
I do it slightly different when I have a UITableViewController, especially for those that can push to another detail view / or show a modal view over the top.
Having the setObserver in ViewDidAppear works well. However, I didnt like the fact that when I looked into a cells detail view and subsequently popped that view, I was fetching from Firebase and reloading the table again, despite the possibility of no changes being made.
This way the observer is added in viewDidLoad, and is only removed when itself is popped from the Nav Controller stack. The tableview is not reloaded unnecessarily when the viewAppears.
var myRef:FIRDatabaseReference = "" // your reference
override func viewDidLoad() {
super.viewDidLoad()
setObserver()
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
// only called when popped from the Nav Controller stack
// if I push to another detail view my observer will remain active
if isBeingDismissed() || isMovingFromParentViewController() {
myRef.removeAllObservers()
}
}
func setObserver() {
myRef.observeEventType(.Value, withBlock: { snapshot in
var newThings = [MyThing]()
for s in snapshot.children {
let ss = s as! FIRDataSnapshot
let new = MyThing(snap: ss) // convert my snapshot into my type
newThings.append(new)
}
self.things = newThings.sort{ $0.name < $1.name) } // some sort
self.tableView.reloadData()
})
}
I also use .ChildChanged .ChildDeleted and .ChildAdded in UITableViews. They work really well and allow you use UITableView animations. Its a little more code, but nothing too difficult.
You can use .ChildChanged to get the initial data one item at a time, then it will monitor for changes after that.
If you want all your data at once in the initial load you will need .Value, I suggest you use observeSingleEventOfType for your first load of the tableview. Just note that if you also have .ChildAdded observer you will also get an initial set of data when that observer is added - so you need to deal with those items (i.e. don't add them to your data set) otherwise your items will appear twice on the initial load.