UITableView: scroll to load more Youtube items - ios

I'm fetching videos from Youtube channel, I'm able to display 10 videos when APP is starting.
I would like to use the following trick (using pagetoken ; Retrieve all videos from youtube playlist using youtube v3 API) to display more videos.
The idea is to scroll down to fetch the next videos, by using willDisplayCell method.
ViewController:
override func viewDidLoad() {
super.viewDidLoad()
self.model.delegate = self
model.getFeedVideo()
self.tableView.dataSource = self
self.tableView.delegate = self
}
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if !loadingData && indexPath.row == 10 - 1 {
model.getFeedVideo()
print("loading more ...")
}
}
How to use pageToken from ViewController whereas I get the token
from VideoModel class?
How to automatically increment the "10" for
indexPath attribute? Is it possible to get the current number of displayed
cell?
VideoModel:
protocol VideoModelDelegate {
func dataReady()
}
class VideoModel: NSObject {
func getFeedVideo() {
var nextToken:String
nextToken = "XXXXXX"
// Fetch the videos dynamically via the Youtube Data API
Alamofire.request(.GET, "https://www.googleapis.com/youtube/v3/playlistItems", parameters: ["part":"snippet", "playlistId":PLAYLIST_ID, "key":API_KEY, "maxResults":10, "pageToken":nextToken], encoding: ParameterEncoding.URL, headers: nil).responseJSON { (response) -> Void in
if let JSON = response.result.value {
nextToken = JSON["nextPageToken"] as! String
print(nextToken)
var arrayOfVideos = [Video]()
for video in JSON["items"] as! NSArray {
// Creating video object...
// Removed to make it clearer
}
self.videoArray = arrayOfVideos
if self.delegate != nil {
self.delegate?.dataReady()
}
}
}
}
Do I need to add a return value or should I create another method to get the pageToken? Please, suggest.

I will give you a general idea on how to accomplish it.
First: Check if user has scrolled to the last element with if !loadingData && indexPath.row == self.videos.count - 1
Second(edit):
//VideoModel.swift
if self.delegate != nil {
//Pass fetched videos and token to the delegate method.
self.delegate?.dataReady(self.videoArray, nextToken)
}
Now, few changes in delegate method:
//ViewController.swift
func dataReady(Array->Videos,String->Token) {
//Define a variable globally and assign received token for later use
tokenGlobal = Token
//add received array of videos to the current array of videos
[self.videos addObjectsFromArray:Videos];
//Here are two options to populate new videos,
//First, Reload whole table view, not a recommended approach, since it will take much more time to load
self.tableView.reloadData()
//Second, add cells to the existing tableView, instead of loading whole table it will added new cells according to the newly added videos into the array.
}
Now when user reaches the end of tableView, pass the token we have saved in global variable like this:
if !loadingData && indexPath.row == self.videos - 1 {
model.getFeedVideo(tokenGlobal)
print("loading more ...")
}
Now, use this tokenGlobal to fetch more videos using 'getFeedVideo' Method.
Look for SO to find out how to add new cells to table view. But for testing you can proceed with reloadData.
P.S: I don't know the Swift syntax. :)

Related

Am I implementing the tableviewdatasource correctly to get downloaded data to show?

I am developing a small app to connect to my site, download data via a PHP web service, and display it in a table view. To get started I was following a tutorial over on Medium by Jose Ortiz Costa (Article on Medium).
I tweaked his project and got it running to verify the Web service was working and able to get the data. Once I got that working, I started a new project and tried to pull in some of the code that I needed to do the networking and tried to get it to display in a tableview in the same scene instead of a popup scene like Jose's project.
This is where I am running into some issues, as I'm still rather new to the swift programming language (started a Udemy course and have been picking things up from that) getting it to display in the table view. I can see that the request is still being sent/received, but I cannot get it to appear in the table view (either using my custom XIB or a programmatically created cell). I thought I understood how the code was broken down, and even tried to convert it from a UITableViewController to a UITableviewDataSource via an extension of the Viewcontroller.
At this point, I'm pretty stumped and will continue to inspect the code and tweak what I think might be the root cause. Any pointers on how to fix would be really appreciated!
Main Storyboard Screenshot
Struct for decoding my data / Lead class:
import Foundation
struct Lead: Decodable {
var id: Int
var name: String
var program: String
var stage: String
var lastAction: String
}
class LeadModel {
weak var delegate: Downloadable?
let networkModel = Network()
func downloadLeads(parameters: [String: Any], url: String) {
let request = networkModel.request(parameters: parameters, url: url)
networkModel.response(request: request) { (data) in
let model = try! JSONDecoder().decode([Lead]?.self, from: data) as [Lead]?
self.delegate?.didReceiveData(data: model! as [Lead])
}
}
}
ViewController:
import UIKit
class LeadViewController: UIViewController {
// Buttons
#IBOutlet weak var newButton: UIButton!
#IBOutlet weak var firstContactButton: UIButton!
#IBOutlet weak var secondContactButton: UIButton!
#IBOutlet weak var leadTable: UITableView!
let model = LeadModel()
var models: [Lead]?
override func viewDidLoad() {
super.viewDidLoad()
//Make Buttons rounded
newButton.layer.cornerRadius = 10.0
firstContactButton.layer.cornerRadius = 10.0
secondContactButton.layer.cornerRadius = 10.0
//Delegate
model.delegate = self
}
//Send request to web service based off Buttons Name
#IBAction func findLeads(_ sender: UIButton) {
let new = sender.titleLabel?.text
let param = ["stage": new!]
print ("findLead hit")
model.downloadLeads(parameters: param, url: URLServices.leads)
}
}
extension LeadViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
print ("number of sections hit")
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
guard let _ = self.models else {
return 0
}
print ("tableView 1 hit")
return self.models!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Create an object from LeadCell
let cell = tableView.dequeueReusableCell(withIdentifier: "leadID", for: indexPath) as! LeadCell
// Lead selection
cell.leadName.text = self.models![indexPath.row].name
cell.actionName.text = self.models![indexPath.row].lastAction
cell.stageName.text = self.models![indexPath.row].stage
cell.progName.text = self.models![indexPath.row].program
print ("tableView 2 hit")
// Return the configured cell
return cell
}
}
extension LeadViewController: Downloadable {
func didReceiveData(data: Any) {
//Assign the data and refresh the table's data
DispatchQueue.main.async {
self.models = data as? [Lead]
self.leadTable.reloadData()
print ("LeadViewController Downloadable Hit")
}
}
}
EDIT
So with a little searching around (okay...A LOT of searching around), I finally found a piece that said I had to set the class as the datasource.
leadTable.dataSource = self
So that ended up working (well after I added a prototype cell with the identifier used in my code). I have a custom XIB that isn't working right now and that's my next tackle point.
You load the data, but don't use it. First, add the following statement to the end of the viewDidLoad method
model.delegate = self
Then add the following LeadViewController extension
extension LeadViewController: Downloadable {
func dicReceiveData(data: [Lead]) {
DispatchQueue.main.async {
self.models = data
self.tableView.reloadData()
}
}
}
And a couple of suggestions:
It is not a good practice to use the button title as a network request parameter:
let new = sender.titleLabel?.text
let param = ["stage": new!]
It is better to separate UI and logic. You can use the tag attribute for buttons (you can configure it in the storyboard or programmatically) to check what button is tapped.
You also have several unnecessary type casts in the LeadModel class. You can change
let model = try! JSONDecoder().decode([Lead]?.self, from: data) as [Lead]?
self.delegate?.didReceiveData(data: model! as [Lead])
to
do {
let model = try JSONDecoder().decode([Lead].self, from: data)
self.delegate?.didReceiveData(data: model)
}
catch {}

How to ensure the order of list shown in UITableView when getting data for cell from UIDocument

My app fetches data via FileManager for each cell in UITableView. The cells need data from a file which requires open UIDocument objects. However, it seems like code inside open completion handler get executed non predictably, so the cells don't get displayed in the order I wish.
How do I solve this problem? I appreciate if anyone gives any clue.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
fetchCellsData()
}
func fetchCellsData() {
do {
let files = getUrlsOfFiles() //files in a certain order
for file in files {
let document = MyDocument(fileURL: file) //MyDocument subclassing UIDocument
//open to get data for cell
document.open { (success) in
if !success {
print("File not open at %#", file)
return
}
let data = populateCellData(document.fileInfo)
self.cellsData.append(data)
self.tableView.reloadData()
/// tried below also
// DispatchQueue.main.async {
// self.cellsData.append(data)
// self.tableView.reloadData()
// }
}
document.close(completionHandler: nil)
}
} catch {
print(error)
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! MyCell
let fileInfo = cellsData[indexPath.row].fileInfo
//assign fileInfo into MyCell property
return cell
}
I expect cells get rendered in the order of 'files', however, it seems like the order is a bit unpredictable presuming that it's due to the fact that 'cellForRowAt' gets called before it knows about the full set of 'cellsData'.
From Apple documentation on UIdocument . Apple doc:
open(completionHandler:)
Opens a document asynchronously.
Which means that even if you trigger document.open in the right order, nothing guarantees that the completionHandler sequence will be in the same order, this is why the order is unpredictible.
However, you know that they will eventually all get done.
What you could do is :
1 - place all your datas from opened document into another list
2 - order this list in accordance to your need
3 - place this list into cellsData (which I assume is bound to your tableViesDatasource)
var allDatas: [/*your data type*/] = []
...
do {
// reset allDatas
allDatas = []
let files = getUrlsOfFiles()
for file in files {
let document = MyDocument(fileURL: file)
document.open { (success) in
if !success {
print("File not open at %#", file)
return
}
let data = populateCellData(document.fileInfo)
self.allDatas.append(data) // "buffer" list
self.tableView.reloadData()
}
document.close(completionHandler: nil)
}
} catch { ...
Then you change cellsData to a computed property like so :
var cellsData: [/*your data type*/] {
get {
return allDatas.order( /* order in accordance to your needs */ )
}
}
this way, each time a new data is added, the list is orderer before being redisplayed.
Discussion
this is the easier solution regarding the state of your code, however this may not be the overall prefered solution. For instance in your code, you reload your tableview each time you add a new value, knowing that there will be more data added after, which is not optimised.
I suggest you to read on Dispatch Group, this is a way to wait until all asynchronous operation your triggered are finished before executing certain actions (such as reloading your tableview in this case) (Readings: Raywenderlich tuts)

How to cancel async image loading for uitableviewcells on new search

I start a search on my server for profiles of users. As the user types usernames, different suggested profiles appear in a cell in a UITableView. Each profile has an image associated with it. The image is loaded asynchronously once the user types, via the cellForItem(at:) function. When the user types to search it cancels my search request to the server, however it also needs to cancel the asynchronous requests. There may be more than one request happening at a time - how should I keep track of them?
My code to download an image is like this:
model.downloadImage(
withURL: urlString,
completion: { [weak self] error, image in
guard let strongSelf = self else { return }
if error == nil, let theImage = image {
// Store the image in to our cache
strongSelf.model.imageCache[urlString] = theImage
// Update the cell with our image
if let cell = strongSelf.tableView.cellForRow(at: indexPath) as? MyTableCell {
cell.profileImage.image = theImage
}
} else {
print("Error downloading image: \(error?.localizedDescription)")
}
}
)
Note that I can add request = model.downloadImage(... if I wanted to, but request would only hold a pointer to the last time that the downloadImage() function was called.
For such purpose I recommend using Kingfisher. It's powerful 3rd party library for downloading and caching images. With kingfisher you can achieve what you want by simply calling:
// In table view delegate
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
//...
cell.imageView.kf.cancelDownloadTask()
}
You can use GCD, with iOS 8 & macOS 10.10 DispatchWorkItem was introduced, which provide this exact functionality (a task it can be canceled) in a very easy to use API.
This is an example:
class SearchViewController: UIViewController, UISearchBarDelegate {
// We keep track of the pending work item as a property
private var pendingRequestWorkItem: DispatchWorkItem?
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
// Cancel the currently pending item
pendingRequestWorkItem?.cancel()
// Wrap our request in a work item
let requestWorkItem = DispatchWorkItem { [weak self] in
self?.model.downloadImage(...
}
// Save the new work item and execute it after 250 ms
pendingRequestWorkItem = requestWorkItem
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(250),
execute: requestWorkItem)
}
}
In your case, you need to know through the delegate (example: TextField) when the user enters information.

image and label in interface builder overlap my data in the TableView cell

I am a beginner in iOS development, and I want to make an instagram clone app, and I have a problem when making the news feed of the instagram clone app.
So I am using Firebase to store the image and the database. after posting the image (uploading the data to Firebase), I want to populate the table view using the uploaded data from my firebase.
But when I run the app, the dummy image and label from my storyboard overlaps the downloaded data that I put in the table view. the data that I download will eventually show after I scroll down.
Here is the gif when I run the app:
http://g.recordit.co/iGIybD9Pur.gif
There are 3 users that show in the .gif
username (the dummy from the storyboard)
JokowiRI
MegawatiRI
After asynchronously downloading the image from Firebase (after the loading indicator is dismissed), I expect MegawatiRI will show on the top of the table, but the dummy will show up first, but after I scroll down and back to the top, MegawatiRI will eventually shows up.
I believe that MegawatiRI is successfully downloaded, but I don't know why the dummy image seems overlaping the actual data. I don't want the dummy to show when my app running.
Here is the screenshot of the prototype cell:
And here is the simplified codes of the table view controller:
class NewsFeedTableViewController: UITableViewController {
var currentUser : User!
var media = [Media]()
override func viewDidLoad() {
super.viewDidLoad()
tabBarController?.delegate = self
// to set the dynamic height of table view
tableView.estimatedRowHeight = StoryBoard.mediaCellDefaultHeight
tableView.rowHeight = UITableViewAutomaticDimension
// to erase the separator in the table view
tableView.separatorColor = UIColor.clear
}
override func viewWillAppear(_ animated: Bool) {
// check wheter the user has already logged in or not
Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
RealTimeDatabaseReference.users(uid: user.uid).reference().observeSingleEvent(of: .value, with: { (snapshot) in
if let userDict = snapshot.value as? [String:Any] {
self.currentUser = User(dictionary: userDict)
}
})
} else {
// user not logged in
self.performSegue(withIdentifier: StoryBoard.showWelcomeScreen, sender: nil)
}
}
tableView.reloadData()
fetchMedia()
}
func fetchMedia() {
SVProgressHUD.show()
Media.observeNewMedia { (mediaData) in
if !self.media.contains(mediaData) {
self.media.insert(mediaData, at: 0)
self.tableView.reloadData()
SVProgressHUD.dismiss()
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: StoryBoard.mediaCell, for: indexPath) as! MediaTableViewCell
cell.currentUser = currentUser
cell.media = media[indexPath.section]
// to remove table view highlight style
cell.selectionStyle = .none
return cell
}
}
And here is the simplified code of the table view cell:
class MediaTableViewCell: UITableViewCell {
var currentUser: User!
var media: Media! {
didSet {
if currentUser != nil {
updateUI()
}
}
}
var cache = SAMCache.shared()
func updateUI () {
// check, if the image has already been downloaded and cached then just used the image, otherwise download from firebase storage
self.mediaImageView.image = nil
let cacheKey = "\(self.media.mediaUID))-postImage"
if let image = cache?.object(forKey: cacheKey) as? UIImage {
mediaImageView.image = image
} else {
media.downloadMediaImage { [weak self] (image, error) in
if error != nil {
print(error!)
}
if let image = image {
self?.mediaImageView.image = image
self?.cache?.setObject(image, forKey: cacheKey)
}
}
}
So what makes the dummy image overlaps my downloaded data?
Answer
The dummy images appear because your table view controller starts rendering cells before your current user is properly set on the tableViewController.
Thus, on the first call to cellForRowAtIndexPath, you probably have a nil currentUser in your controller, which gets passed to the cell. Hence the didSet property observer in your cell class does not call updateUI():
didSet {
if currentUser != nil {
updateUI()
}
}
Later, you reload the data and the current user has now been set, so things start to work as expected.
This line from your updateUI() should hide your dummy image. However, updateUI is not always being called as explained above:
self.mediaImageView.image = nil
I don't really see a reason why updateUI needs the current user to be not nil. So you could just eliminate the nil test in your didSet observer, and always call updateUI:
var media: Media! {
didSet {
updateUI()
}
Alternatively, you could rearrange your table view controller to actually wait for the current user to be set before loading the data source. The login-related code in your viewWillAppear has nested completion handers to set the current user. Those are likely executed asynchronously .. so you either have to wait for them to finish or deal with current user being nil.
Auth.auth etc {
// completes asynchronously, setting currentUser
}
// Unless you do something to wait, the rest starts IMMEDIATELY
// currentUser is not set yet
tableView.reloadData()
fetchMedia()
Other Notes
(1) I think it would be good form to reload the cell (using reloadRows) when the image downloads and has been inserted into your shared cache. You can refer to the answers in this question to see how an asynch task initiated from a cell can contact the tableViewController using NotificationCenter or delegation.
(2) I suspect that your image download tasks currently are running in the main thread, which is probably not what you intended. When you fix that, you will need to switch back to the main thread to either update the image (as you are doing now) or reload the row (as I recommend above).
Update your UI in main thread.
if let image = image {
DispatchQueue.main.async {
self?.mediaImageView.image = image
}
self?.cache?.setObject(image, forKey: cacheKey)
}

Swift iOS -How To Reload TableView Outside Of Firebase Observer .childAdded to Filter Out Duplicate Values?

I have a tabBar controller with 2 tabs: tabA which contains ClassA and tabB which contains ClassB. I send data to Firebase Database in tabA/ClassA and I observe the Database in tabB/ClassB where I retrieve it and add it to a tableView. Inside the tableView's cell I show the number of sneakers that are currently inside the database.
I know the difference between .observeSingleEvent( .value) vs .observe( .childAdded). I need live updates because while the data is getting sent in tabA, if I switch to tabB, I want to to see the new data get added to the tableView once tabA/ClassA is finished.
In ClassB I have my observer in viewWillAppear. I put it inside a pullDataFromFirebase() function and every time the view appears the function runs. I also have Notification observer that listens for the data to be sent in tabA/ClassA so that it will update the tableView. The notification event runs pullDataFromFirebase() again
In ClassA, inside the callback of the call to Firebase I have the Notification post to run the pullDataFromFirebase() function in ClassB.
The issue I'm running into is if I'm in tabB while the new data is updating, when it completes, the cell that displays the data has a count and the count is thrown off. I debugged it and the the sneakerModels array that holds the data is sometimes duplicating and triplicating the newly added data.
For example if I am in Class B and there are 2 pairs of sneakers in the database, the pullDataFromFirebase() func will run, and the tableView cell will show "You have 2 pairs of sneakers"
What was happening was if I switched to tabA/ClassA, then added 1 pair of sneakers, while it's updating I switched to tabB/ClassB, the cell would still say "You have 2 pairs of sneakers" but then once it updated the cell would say "You have 5 pairs of sneakers" and 5 cells would appear? If I switched tabs and came back it would correctly show "You have 3 pairs of sneakers" and the correct amount of cells.
That's where the Notification came in. Once I added that if I went through the same process and started with 2 sneakers the cell would say "You have 2 pairs of sneakers", I go to tabA, add another pair, switch back to tabB and still see "You have 2 pairs of sneakers". Once the data was sent the cell would briefly show "You have 5 pairs of sneakers" and show 5 cells, then it would correctly update to "You have 3 pairs of sneakers" and the correct amount of cells (I didn't have to switch tabs).
The Notification seemed to work but there was that brief incorrect moment.
I did some research and the most I could find were some posts that said I need to use a semaphore but apparently from several ppl who left comments below they said semaphores aren't meant to be used asynchronously. I had to update my question to exclude the semaphore reference.
Right now I'm running tableView.reloadData() in the completion handler of pullDataFromFirebase().
How do I reload the tableView outside of the observer once it's finished to prevent the duplicate values?
Model:
class SneakerModel{
var sneakerName:String?
}
tabB/ClassB:
ClassB: UIViewController, UITableViewDataSource, UITableViewDelegate{
var sneakerModels[SneakerModel]
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(pullDataFromFirebase), name: NSNotification.Name(rawValue: "pullFirebaseData"), object: nil)
}
override func viewWillAppear(_ animated: Bool){
super.viewWillAppear(animated)
pullDataFromFirebase()
}
func pullDataFromFirebase(){
sneakerRef?.observe( .childAdded, with: {
(snapshot) in
if let dict = snapshot.value as? [String:Any]{
let sneakerName = dict["sneakerName"] as? String
let sneakerModel = SneakerModel()
sneakerModel.sneakerName = sneakerName
self.sneakerModels.append(sneakerModel)
//firebase runs on main queue
self.tableView.reloadData()
}
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sneakerModels.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SneakerCell", for: indexPath) as! SneakerCell
let name = sneakerModels[indePath.row]
//I do something else with the sneakerName and how pairs of each I have
cell.sneakerCount = "You have \(sneakerModels.count) pairs of sneakers"
return cell
}
}
}
tabA/ClassA:
ClassA : UIViewController{
#IBAction fileprivate func postTapped(_ sender: UIButton) {
dict = [String:Any]()
dict.updateValue("Adidas", forKey: "sneakerName")
sneakerRef.?.updateChildValues(dict, withCompletionBlock: {
(error, ref) in
//1. show alert everything was successful
//2. post notification to ClassB to update tableView
NotificationCenter.default.post(name: Notification.Name(rawValue: "pullFirebaseData"), object: nil)
}
}
}
In other parts of my app I use a filterDuplicates method that I added as an extension to an Array to filter out duplicate elements. I got it from filter array duplicates:
extension Array {
func filterDuplicates(_ includeElement: #escaping (_ lhs:Element, _ rhs:Element) -> Bool) -> [Element]{
var results = [Element]()
forEach { (element) in
let existingElements = results.filter {
return includeElement(element, $0)
}
if existingElements.count == 0 {
results.append(element)
}
}
return results
}
}
I couldn't find anything particular on SO to my situation so I used the filterDuplicates method which was very convenient.
In my original code I have a date property that I should've added to the question. Any way I'm adding it here and that date property is what I need to use inside the filterDuplicates method to solve my problem:
Model:
class SneakerModel{
var sneakerName:String?
var dateInSecs: NSNumber?
}
Inside tabA/ClassA there is no need to use the Notification inside the Firebase callback however add the dateInSecs to the dict.
tabA/ClassA:
ClassA : UIViewController{
#IBAction fileprivate func postTapped(_ sender: UIButton) {
//you must add this or whichever date formatter your using
let dateInSecs:NSNumber? = Date().timeIntervalSince1970 as NSNumber?
dict = [String:Any]()
dict.updateValue("Adidas", forKey: "sneakerName")
dict.updateValue(dateInSecs!, forKey: "dateInSecs")//you must add this
sneakerRef.?.updateChildValues(dict, withCompletionBlock: {
(error, ref) in
// 1. show alert everything was successful
// 2. no need to use the Notification so I removed it
}
}
}
And in tabB/ClassB inside the completion handler of the Firebase observer in the pullDataFromFirebase() function I used the filterDuplicates method to filter out the duplicate elements that were showing up.
tabB/ClassB:
func pullDataFromFirebase(){
sneakerRef?.observe( .childAdded, with: {
(snapshot) in
if let dict = snapshot.value as? [String:Any]{
let sneakerName = dict["sneakerName"] as? String
let sneakerModel = SneakerModel()
sneakerModel.sneakerName = sneakerName
self.sneakerModels.append(sneakerModel)
// use the filterDuplicates method here
self.sneakerModels = self.sneakerModels.filterDuplicates{$0.dateInSecs == $1.dateInSecs}
self.tableView.reloadData()
}
})
}
Basically the filterDuplicates method loops through the sneakerModels array comparing each element to the dateInSecs and when it finds them it excludes the copies. I then reinitialize the sneakerModels with the results and everything is well.
Also take note that there isn't any need for the Notification observer inside ClassB's viewDidLoad so I removed it.

Resources