I am having trouble reloading the tableView. When my firebaseService.getAllPosts() method runs, it gives me the two posts in the database (as intended). I can tell this because when the didSet print runs, it gives me the correct count. However, because I know the tableview is set before the method gets run, I know I need to reload the tableview to update the count in my datasource. There-in lies the issue. I put the posts variable outside of my class on purpose so it could be accessed from every class. (If this is not a good practice, let me know.)
Where can I run tableView.reloadData() so my tableView datasource updates and gives me the correct could of posts? I've tried putting FeedController().tableView.reloadData() in the didSet and I've tried putting it in viewDidLoad() but neither of these worked. I've also tried adding a variable called _posts and setting it equal to posts and adding didSet to that with tableView.reloadData() inside the didSet but that doesn't work either.
class FeedController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let tableView = UITableView(frame: UIScreen.mainScreen().bounds, style: UITableViewStyle.Plain)
let cellId = "PhotoCell"
let textCellId = "TextCell"
let firebaseService = FirebaseService.sharedInstance
static let sharedFeedInstance = FeedController()
var posts = [Post]() {
didSet {
tableView.reloadData()
print(posts.count)
}
}
override func viewDidLoad() {
super.viewDidLoad()
firebaseService.getAllPosts()
tableView.dataSource = self
tableView.delegate = self
self.view.addSubview(tableView)
}
// MARK: - Table view data source
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
print("sections: \(posts.count)")
return posts.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let post:Post? = posts[indexPath.section]
if let _ = post?.imageContentName {
let photoFeedCell = tableView.dequeueReusableCellWithIdentifier(self.cellId, forIndexPath: indexPath) as? FeedTVCellWithPhoto
photoFeedCell?.post = post
return photoFeedCell!
}
let textFeedCell = tableView.dequeueReusableCellWithIdentifier(self.textCellId, forIndexPath: indexPath) as? FeedTVCellText
textFeedCell?.post = post
return textFeedCell!
}
}
Update 1: getAllPosts method from FirebaseService class
func getAllPosts() {
let postRef = ref.child("posts")
postRef.observeSingleEventOfType(.Value, withBlock: { snapshot in
// print(snapshot.value)
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshots {
if let postDictionary = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let post = Post(key: key, dictionary: postDictionary)
FeedController.sharedFeedInstance.posts.insert(post, atIndex: 0)
}
}
}
})
}
In essence, you are addressing the methods and properties of FeedController the wrong way. Every time that you use FeedController() in your code you should be using self.
The following two paragraphs are a refresher about class and instance methods and properties. Feel free to ignore them if it's something that's already in your radar:
If a method or property belongs to an instance (that is, it may vary from instance to instance) you call it with self.property or self.method(), although often you can drop the self. as long as it does not create ambiguity.
If a method or property belongs to the whole class (and therefore it is declared as static let, static var or static func) then you would call it with NameOfTheClass.property or NameOfTheClass.method(). In your case, you would call it FeedController.property or FeedController.method(). That is, without the trailing parentheses. In any case, class methods and properties should be used sparingly and would probably not be appropriate in this situation.
Having settled that, we have the question of how to reload the data. If you move posts inside FeedController (making the array an instance variable) and add tableView.reloadData() (without the self.) to the didSet() of posts, you should be good, notwithstanding other unrelated code smells that others have highlighted and you should try to fix, but those should not affect your ability to reload the data.
So off the bat:
"FeedController().tableView.reloadData()" is
instantiating another FeedController. That's not going to work. Where to put the reload call will depend on your requirements. For example if you have a lot of segues occurring where you go back and forth to this UIViewController and the data is being changed in other UIViewControllers, you can reload in an override of viewWillAppear() or the other lifecycle methods. You need to call that on the tableView for the instance of the UIViewController that is loaded into the UIWindow, not what you're doing now.
Any reason why you're doing a lot of this stuff programmatically rather
than through the storyboard? Either way is fine but I find storyboard
to be a nice separation. For example if you used a UITableViewController through storyboard you could have it set up the delegate and dataSource without doing it through code which just looks like ugly boilerplate. You can set these without using a UITableViewController as well, as long as you meet the protocols. Those things that can be handled in the storyboard should is what I usually go by.
Your class is extending UIViewController and implementing those two protocols, why not just extend a UITableViewController? Unless you're manually putting a UITableView on there and need to show something other than a UITableView on that UIViewController there's no reason I can think of to do what you're doing.
Something else to note is that the way you're doing the PhotoFeedCell and TextFeedCell is kind of weird and gives mixed signals. You're dequeuing a reusable cell and optional casting it and using optional chaining afterwards, both of which implies that you're accepting it may be nil, but then force unwrapping it right after.
Related
So I don't know if the title gets my problem right but here is the thing:
I have an enum for identifying several types of table view cells. Every type of cell has its own class. I have conformed every one of those to a protocol but with an associated type. I am now trying to create a way to have instances of those classes and use their protocol methods and properties arbitrarily. Here's an example:
enum CellType {
case a, b, c
func getCellClass() -> ProtocolWithAssociatedType.Type { //Error here
switch self {
case .a: return ClassA.self
case .b: return ClassB.self
case .c: return ClassC.self
}
}
This enum raises an error of Protocol 'CreateOrderTableViewCellProtocol' can only be used as a generic constraint because it has Self or associated type requirements on that line.
So this is the exact protocol I have except the name:
protocol ProtocolWithAssociatedType: UITableViewCell {
associatedtype Delegate
var delegate: Delegate? {get set}
func setupCell()
}
All the classes ClassA, ClassB and ClassC conforms to this. They all have their own delegate protocols which they cast with typealias e.g.:
protocol ClassADelegate: class {
...
}
class ClassA: UITableViewCell, ProtocolWithAssociatedType {
typealias Delegate = ClassADelegate
weak var delegate: ClassADelegate?
func setupCell() {}
...
}
extension ViewController: ClassADelegate {
...
}
All of these is to slim down the tableView(...cellForItemAt:...) and other similar methods since there are many cell classes in this project and it's beyond the point of being readable and it's really really hard to make any development on this particular view controller because of this.
I have an extension for UITableView for creating reusable cells for those which it's reusable id is the same as it's class' name like this:
func dequeueReusableCell<C>(_ cellType: C.Type, for indexPath: IndexPath) -> C where C: UITableViewCell {
let identifier = String(describing: cellType)
guard let cell = dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? C else {
fatalError("Failed to find cell with identifier \"\(identifier)\" of type \"\(C.self)\"")
}
return cell
}
So I am willing to use this like following:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellType = CellDataArray[indexPath.row].cellType
let cellClass = cellType.getCellClass()
let cell = tableView.dequeueReusableCell(cellClass, for: indexPath) \\ Here I have to have the cell conform to the protocol somehow
cell.delegate = self \\So here is the associated type which doesn't need to be read but is need to be written only
cell.setupCell() \\Obviously there are going to be some data passed to the cell instance right here
return cell
}
I have read so many questions and answers and I am trying every one of them to accomplish this yet I haven't been able to do so. I am trying to avoid massive functions in the view controller and make things as modifiable as possible. All those cells are acting like little view controllers themselves and they all have to communicate with the view controller. Also the cells are not static, like for different occasions there are different cells I have to show. Right now even adding one simple cell is a hell of a work, let alone creating a whole new occasion. But with this approach I am trying to make things... modular.
So is there any way I can do this without having a runtime crash here and there or creating a blackhole and bringing the universe to an end?
EDIT: I have tried the generic type way but unable to do so. Because of the way I want the func getCellClass() work, it is not possible to make the complier know what that generic type will be. Like following:
func getCellClass<C>() -> C.Type where C: ProtocolWithAssociatedValue {
...
}
So even I force cast the return values to the C.Type then I have problem where I call it simply because C is not known.
EDIT
I have removed the associated value from the protocol and did the following:
protocol ProtocolWithAssociatedType: UITableViewCell {
var _delegate: AnyObject? {get set}
func setupCell()
}
protocol ClassADelegate: class {
...
}
class ClassA: UITableViewCell, ProtocolWithAssociatedType {
weak var _delegate: AnyObject? { didSet { delegate = _delegate as? ClassADelegate } }
private weak var delegate: ClassADelegate?
func setupCell() {}
...
}
extension ViewController: ClassADelegate {
...
}
This way the protocol is usable as return type of that enum function and I can force cast the reusable cell to it since the protocol itself conforms to UITableViewCell. I have built without any problem yet can not test yet (test servers are down outside of working hours). When I test it and if it runs without any problem, I will post it as solution.
Seems you're trying to mix polymorphic behaviour (i.e. the array of cell types) with static behaviour (protocols with associated types), and this is more or less not possible with the current Swift design.
Protocols with associated types are determined at compile time, while the code in your tableView(_:cellForRowAt:) method is executed at runtime, and this makes it somewhat incompatible with protocols with associated types.
let cell = tableView.dequeueReusableCell(cellClass, for: indexPath)
In the above code you need to be able to tell the compiler which type do you expect for the cell, in order to inject the delegate, however that's a compile-time feature, and you need it at runtime, hence the incompatibility.
I'm afraid you'll have to stick to runtime error propagation/handling for this. Unless you are willing to change the delegate to match the whole set of possible delegates, and in this case the associated type is no longer needed, which enables the runtime features.
protocol MyCell: UITableViewCell {
var delegate: CellDelegate1 & CellDelegate2 ... & CellDelegateN { get set }
func setupCell(SomeCommonProtocolThatGetsCastedAtRuntime)
}
SOLUTION
So after a couple of days' work and trial-error I have finally came up with a solution. Thanks to #Cristik I have had an insight and changed my approach.
Firstly I have changed my way of handling the delegates. I had a different delegate for every cell. Now I have only one which all of those cells use.
So this way I was able to make my property like following:
protocol CellProtocol: UITableViewCell {
var delegate: CommonCellDelegate?
func setupCell(...)
}
For all the properties and some common methods of the view controller which the cells will use I have declared the delegate protocol like following:
protocol CommonCellDelegate: class {
var viewControllerProperty1: SomeClass1 { get set }
var viewControllerProperty2: SomeClass2 { get set }
...
var viewControllerPropertyN: SomeClassN { get set }
func someCommonFunction1(...) -> ...
func someCommonFunction2(...) -> ...
...
func someCommonFunctionN(...) -> ...
}
As my intention from the beginning being to keep the view controller nice and clean, I have put those uncommon methods which cells individually need down below those cells' class declarations as an extension to the delegate property:
class SomeCellClass: CellProtocol {
var delegate: CommonCellDelegate?
func setupCell(...) {
...
}
...
}
extension CommonCellDelegate {
func someMethodForTheCellAbove1(...) -> ... {
...
}
func someMethodForTheCellAbove2(...) -> ... {
...
}
.
.
.
}
As I didn't need the enum method to return different classes anymore, I have changed it a bit to return the reuse id of every cell type. So no need to return the class type there:
enum CellType {
case cellA, cellB, cellC, ...
func getCellClass() -> String { //Error here
switch self {
case .cellA: return String(describing: ClassA.self)
case .cellB: return String(describing: ClassB.self)
case .cellC: return String(describing: ClassC.self)
...
// Note: We use the the name of the cell classes as their reuse id's for simplicity.
}
}
And finally, I was able to create those cells in the tableView(_:cellForRowAt:) method quite easily and without creating a rip in the fabric of the space-time:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellType = CellDataArray[indexPath.row].cellType
let cellReuseId = String(describing: cellType.getCellId())
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath) as? CellProtocol else { fatalError("some fatal error") }
cell.delegate = self
cell.setupCell(...)
return cell
}
This way it's very very easy to make new implementations since all that needs to be done is to create the class, make the class conform to the CellProtocol, make a case for it in the enum, add the case to the array whereever or whenever it's needed, register the cell id and voilĂ ! It's there! And because all the needed properties needed are now known by the cell thanks to the delegate, they can easily use those like their own properties, only difference is that it's not self.something but delegate?.something.
I hope this effort of mine might be helpful to others. Cheers!
I am new to programming and currently trying to make a newsfeed like app. My goal is it to notice changes in the firebase database through my app and new posts in my table view. Currently I am able to show one post in my tableView. Its only changing though when you reopen the ViewController.
One of my problem is that I don't know how to use any other command then obserSingleEvent and I am pretty sure that one of my main mistakes.
So my questions are:
1. How do I make the tableView instantly reload when a change appears?
2. how do I display more then one post?
(3. how can I show the most recent posts first?)
class ViewController: BaseViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var newsfeedTableView: UITableView!
var ref: DatabaseReference!
var posts = [String]()
override func viewDidLoad() {
super.viewDidLoad()
addSlideMenuButton()
ref = Database.database().reference()
ref.child("posts").queryOrderedByKey().observeSingleEvent(of: .childAdded, with: { snapshot in
let newPost = snapshot.value as? String
self.posts.append(newPost!)
print("newPost: \(newPost)")
print("posts: \(self.posts)")
self.newsfeedTableView.reloadData()
print("reloadData")
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (posts.count)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "cell")
cell.textLabel?.text = posts[indexPath.row]
print("posts in cell: \(posts)")
return cell
}
}
You're observing with singleEvent which is wrong for the intended use.
ref.child("posts").observe( .childAdded, with: { snapshot in
let newPost = snapshot.value as? NSDictionary
let post_title:String = newPost!["post_title"] as? String ?? "error"
self.posts.append(post_title)
print("newPost: \(newPost)")
print("posts: \(self.posts)")
self.newsfeedTableView.reloadData()
print("reloadData")
})
Also, that sorting system won't work for a news app. you can't work with human-provided 1,2,3. could you update it to something like this from now on:
(1 - 2) A random-unique generated post ID
PS. It's impossible for Firebase to order your post list with post, post1, post2. So in this case queryOrderedByKey() is useless.
Making the posts 1,2,3 would work for the ordering system
1) You can implement a function that is constantly observing the data in Firebase for changes and update the array. But be careful with that, it will be consuming memory and also you will get a lot of network usage.
.observe // instead of .observeSingleEvent will do that
Again, it would be better if you add a refresher to the tableView so you fetch the data on demand instead of all the time.
2) Your array is fine. I'll move the call to firebase to another function instead of having it in viewDidLoad().
3) You need to store a timeStamp in firebase to get this functionality. Yes, you can queryOrdered but remember firebase is async. Data will be given as available so it would be better to sort the array depending on the creation timeStamp of each of the items.
With the inspiration coming from the idea that you can code anything, i tried my hand at a complicated CollectionView nested structure which goes as follows:
CustomCollectionViewController
--CustomCollectionViewCell
----CustomTableView
------CustomTableViewCell
--------CustomPickerView
In CustomCollectionViewController, the main data feed comes from property:
var cardFeed: [String: [Card]] = [:]
Card is my defined model and variable cardFeed is applied the usual way in UICollectionView Delegate & DataSource methods:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "card", for: indexPath) as! CustomCollectionViewCell
cell.contentView.clipsToBounds = true
cell.delegate = self
cell.card = self.cardFeed[string]![indexPath.row]
cell.cardIndex = indexPath.row
}
From the delegate methods, cardFeed above sets a main property in CustomCollectionViewCell used to update the interface:
var card: Card! {
didSet{
setupCard()
}
}
The property card is also the data feed for UITableView Delegate & Datasource.
Everything works perfectly, everything shows up as it should. Except for the fact that when a user picks values from the CustomPickerView, the main data feed, namely cardFeed defined in CustomCollectionViewController (shown above) must update!
My solution is this:
(1) Given that there are three components, define array that records changes in CustomPickerView selected rows and a call back method to pass down the variable:
var selectedRow: [Int] = [1, 0, 0] {
didSet {
if updateRow != nil {
updateRow!(self.selectedRow)
}
}
}
var updateRow: ( ([Int]) -> () )?
(2) In CustomCollectionViewCell define another call back with an extra argument, to keep track of what cell actually sent the selected row array :
var passSelectedRow: (([Int], Int) -> ())?
which is called in tableViews cellForRowAtIndexPath method:
cell.updateRow = { selectedRow in
self.passSelectedRow!(selectedRow, indexPath.row)
}
(3) finally update cardFeed in CustomCollectionViewController cellForItemAtIndexPath:
cell.passSelectedRow = { selectedRow, forIndex in
if self.cardFeed[string]![indexPath.row].chosenFood[forIndex].selectedRow != selectedRow {
self.cardFeed[string]![indexPath.row].chosenFood[forIndex].selectedRow = selectedRow
}
}
But here is the problem, if i now add a didSet to cardFeed, it will create an infinite loop because cellForRowAtIndexPath will be called indefinitely. If i get the CustomCollectionViewCell reference anywhere other than cellForItemAtIndexPath, self.collectionView?.reload() does not work! Is there a way i can update my variable cardFeed in CustomCollectionViewController from the selected rows in CustomPickerView?
When communicating between objects it is bad practice to make the child object have a strong reference to its owner, that is how you end up with retain cycles and bugs.
Let's take a look at the two most common ways of communicating between objects: delegation and notification.
with delegation:
Create a protocol for communicating what you want, in your example:
protocol PickerFoodSelectedDelegate : class {
func selected(row : Int, forIndex : Int)
}
Add weak var selectionDelegate : PickerFoodSelectedDelegate as a variable in the picker class
In the tableView class, during cellForItemAtIndexPath, you assign self to picker.selectionDelegate
You then create a similar structure for communicating between the table and the collection view.
The key part is that delegate references be declared as weak, to avoid retain cycles and bugs.
With notifications you can use NotificationCenter.default to post a notification with any object you want, in this case you would:
Subscribe to a notification name you choose in the table view.
Post a notification from the picker view when an option is chosen.
When the table receives the notification, extract the object.
Do the same from the table to the collection view.
Hope this helps!
Have a right view controller that slides in and out over the main view controller. This right view controller has a table in it to contain the passed information from the main.
I can access and pass the data to the controller from the main without issue but in the right view I need to then bind the data passed to it from the main.
The problem is that even though I try binding the data after the view comes into focus it gives nil on the tableView.reloadData().
RightViewController has 2 functions that are used by the main
func loadAlerts(){
self.tableView.reloadData()
}
func setAlerts(alerts: Alerts){
self.alerts = alerts
}
Alerts is just a custom object. It does contain values. self.alerts is a class variable.
MainViewController calls these 2 functions this way
self.rightViewController = self.storyboard?.instantiateViewController(withIdentifier: "RightViewController") as! RightViewController
Set the data after getting it from the api call
if let count = self.alerts?.Alerts.count {
if count == 0 {
return
}
//set on controller
rightViewController.setAlerts(alerts: self.alerts!)
}
This is defined at class level like
private var rightViewController: RightViewController!
Then I have a delegate defined for when the right controller is opened from a gesture and it calls like this
func rightDidOpen() {
rightViewController.loadAlerts()
}
This works fine for everything but the a tableView. Even by telling the tableView to load on the main thread like so
DispatchQueue.main.async{
self.tableView.reloadData()
}
Didn't change anything. At this point the alerts has values.
I don't mind refactoring the entire thing if need be so any ideas, thoughts or info of how I can get this to work is appreciated. If more info is needed just let me know.
--
Here the table delegate and source defined
class RightViewController : UIViewController, UITableViewDataSource, UITableViewDelegate
and from front end assigned to the uicontroller (its calle Alerts Scene). Forgot to mention that if I do the api call directly in the right controller it works fine but I'm trying to reduce api calls so am refactoring this.
Here are the methods. Sorry for the misunderstanding.
//MARK: Tableview delegates
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let count = alerts?.Alerts.count{
return count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let alertD = alerts?.Alerts[indexPath.row] {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "AlertTableViewCell") as! AlertTableViewCell
cell.name.text = alertD.Summary
cell.icon.image = Helpers.listImage24dp(id: alertD.TOA)
cell.selectionStyle = .none
cell.name.textColor = UIColor.blue
return cell
}
return UITableViewCell()
}
I am new to iOS/Swift development, and having a problem with making a dynamic swap of DataSource work for a UITableView - note I am not swapping the Delegate, just the DataSource.
I have read other similar questions/responses on Stack Overflow, and not found one that's relevant to my situation. Typically they're about setting the DataSource on "viewDidLoad" (e.g. this one, and this one), whereas my situation is about swapping the DataSource when the user presses a button. The problems in the referenced questions don't exist in my code.
Here's outline of my code. I have the buttonPress method connected to the TouchUpInside event in the storyboard:
class ViewController: UIViewController, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
...
#IBAction func buttonPress(sender: UIButton) {
...
self.tableView.dataSource = DummyDataSource()
self.tableView.delegate = self
self.tableView.reloadData()
}
...
}
...and here's my datasource class:
import UIKit
class DummyDataSource: NSObject, UITableViewDataSource {
let names = ["A", "B", "C"]
func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return names.count
}
func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier(simpleTableIdentifier) as UITableViewCell?
if ( cell == nil ) {
cell = UITableViewCell( style: UITableViewCellStyle.Default,
reuseIdentifier: simpleTableIdentifier)
}
cell!.textLabel?.text = names[indexPath.row]
return cell!
}
}
When I press the button, I can see that the pressButton method is being called correctly, but the data doesn't show up in the tableView (no errors - just no data). Any ideas please? Thank you.
UITableView's dataSource property is either unsafe_unretained or weak, depending on which version of iOS. Either way, as with any other delegate, it doesn't keep a strong reference.
So when you write a line like this:
self.tableView.dataSource = DummyDataSource()
Your newly instantiated DummyDataSource() property doesn't have any strong references pointing to it. It is therefore immediately released by ARC.
We need to keep a strong reference to the data source if we want it to stick around.
My recommendation would be to add a data source property to your view controller which can keep the strong reference. We will also use the didSet of this property to set the table view's data source property and reload its data.
var dataSource: UITableViewDataSource? {
didSet {
tableView?.dataSource = dataSource
tableView?.reloadData()
}
}
We use optional-chaining to protect against the data source being set before the view is loaded and the tableView property is populated. Otherwise, we will get a fatal error for trying to unwrap nil.
We shouldn't need to be setting the data source property on the table view anywhere else. And the only reason why we should need to called reloadData() anywhere else is if our data source itself can change the data it is representing. However, it is important that reloadData() is called in sync with resetting the dataSource to protect against some likely index-out-of-bound crashes.