I have 4 categories in segmented control. When i press on one of them collection view should show products in particular category. But instead of reusing cells and show only new items, collection view shows old items and in the end adds new cells with new items.
Where is proper place to use reloadData(). Or maybe i'm missing something?
Here is my code
private lazy var segmentedControl: UISegmentedControl = {
var control = UISegmentedControl(items: ["All", "Men", "Women", "Jewelery", "Electro"])
control.selectedSegmentTintColor = .black
control.setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected)
control.tintColor = .white
control.selectedSegmentIndex = 0
control.addTarget(self, action: #selector(segmentChanged(_ :)), for: .valueChanged)
return control
}()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
networkManager.delegate = self
setupMainStackView()
setupCollectionView()
networkManager.loadProducts(category: .all)
performSearch()
}
func performSearch() {
if let category = NetworkManager.Category(rawValue: segmentedControl.selectedSegmentIndex) {
networkManager.loadProducts(category: category)
collectionView.reloadData()
}
// collectionView.reloadData()
}
#objc func segmentChanged(_ sender: UISegmentedControl) {
performSearch()
}
// MARK: - Data Source
extension MainViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return productResults.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MainViewCollectionViewCell.identifier, for: indexPath) as! MainViewCollectionViewCell
let listOfProduct = productResults[indexPath.row]
cell.configure(for: listOfProduct)
return cell
}
}
And here is how i search category
enum Category: Int {
case all = 0
case menSClothing = 1
case womenSClothing = 2
case electronics = 3
case jewelery = 4
var type: String {
switch self {
case .all: return ""
case .menSClothing: return "category/men's%20clothing"
case .womenSClothing: return "category/women's%20clothing"
case .electronics: return "category/electronics"
case .jewelery: return "category/jewelery"
}
}
}
private func fakeStoreURL(category: Category) -> URL {
let kind = category.type
let url = URL(string: "https://fakestoreapi.com/products/" + "\(kind)")
return url!
}
func loadProducts(category: Category) {
let url = fakeStoreURL(category: category)
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print(error)
return
}
guard let data = data else { return }
do {
var products = try JSONDecoder().decode([Product].self, from: data)
products = self.parse(data: data)
print(products)
DispatchQueue.main.async {
self.delegate?.didSendProductData(self, with: products)
}
} catch {
print(error)
}
}.resume()
}
Problem is here
networkManager.loadProducts(category: category)
collectionView.reloadData()
loadProducts is an asynchronous method , and reload of the collection occurs before the network data returns , so you need a completion
func loadProducts(category: Category,completion:#escaping([Product]? -> ())) {
let url = fakeStoreURL(category: category)
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print(error)
completion(nil)
return
}
guard let data = data else { return }
do {
var products = try JSONDecoder().decode([Product].self, from: data)
products = self.parse(data: data)
print(products)
DispatchQueue.main.async {
completion(products)
}
} catch {
print(error)
completion(nil)
}
}.resume()
}
Then
networkManager.loadProducts(category: category) { [weak self] products in
guard let products = products else { return }
self?.productResults = products
self?.collectionView.reloadData()
}
Related
Greetings I have the disadvantage that the cells of the tableview are duplicated in a random way, at certain times the event occurs, not always
i think i need to clean or reload data but im not sure, could you help please.
final class MovementsDatasource: NSObject, PaginatedDatasource {
typealias Values = MovementsSectionModel
private let repository: TransactionsRepositoryProtocol
private let disposeBag = DisposeBag()
private let fetching = ActivityIndicator()
private let id: String, cvl: String
private let rowsObserver = PublishSubject<[Values]>()
var values = [Values]() { didSet { self.rowsObserver.onNext(self.values) } }
var page = 0
var hasNextPage = false
init(repository: TransactionsRepositoryProtocol, id: String, cvl: String) {
self.id = id
self.cvl = cvl
self.repository = repository
}
func getActivityIndicator() -> ActivityIndicator { self.fetching }
func getValuesObserver() -> Observable<[Values]> { self.rowsObserver }
func getNextPage() {
guard self.hasNextPage else { return }
getMovements(for: self.id, cvl: self.cvl, page: self.page)
}
func getFirstPage() {
self.page = 0
self.hasNextPage = false
self.values = []
getMovements(for: self.id, cvl: self.cvl, page: self.page)
}
}
extension MovementsDatasource {
private func getMovements(for productID: String, cvl: String, page: Int) {
self.repository
.movements(userCVL: cvl, ibanAccountId: productID, page: page)
.trackActivity(self.fetching)
.subscribe(onNext: { [weak self] response in
guard let _self = self else { return }
_self.hasNextPage = response.hasNextPage
_self.page = _self.hasNextPage ? (_self.page + 1) : _self.page
_self.publish(response)
})
.disposed(by: self.disposeBag)
}
private func publish(_ response: MovementsResponse) {
guard let rawMovement = response.values else { return }
let newRows = self.transform(rawMovement)
var rows = self.values
if !rows.isEmpty { rows += newRows }
else { rows = newRows }
self.values = self.merge(rows)
}
internal func transform(_ movements: [Movement]) -> [MovementsSectionModel] {
movements
.map { $0.movementDate.displayTimestamp() }
.unique()
.map { title -> MovementsSectionModel in
let sorted = movements.filter { $0.movementDate.displayTimestamp() == title }
return MovementsSectionModel(header: title, items: sorted)
}
}
internal func merge(_ sections: [MovementsSectionModel]) -> [MovementsSectionModel] {
sections
.map { $0.header }
.unique()
.map { title -> MovementsSectionModel in
let merged = sections.reduce(into: [MovementCellViewModel]()) {
accumulator, section in
if section.header == title { accumulator += section.items }
}
return MovementsSectionModel(header: title, items: merged)
}
}
}
extension MovementsDatasource {
func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
self.values[section].items.count
}
func numberOfSections(in _: UITableView) -> Int { self.values.count }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard !self.values.isEmpty, let cell = tableView.dequeueReusableCell(withIdentifier: MovementCell.reuseID, for: indexPath) as? MovementCell
else {
return UITableViewCell(frame: .zero)
}
let section = indexPath.section
let index = indexPath.item
if !self.values.isEmpty {
cell.viewModel = self.values[section].items[index]
}
return cell
}
}
I'm trying to save data to the core data and then display it on another view controller. I have a table view with custom cell, which have a button. I've created a selector, so when we tap on the button in each of the cell, it should save all the data from the cell. Here is my parent view controller:
import UIKit
import SafariServices
import CoreData
class ViewController: UIViewController, UISearchBarDelegate {
#IBOutlet weak var pecodeTableView: UITableView!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var savedNews = [SavedNews]()
var newsTitle: String?
var newsAuthor: String?
var urlString: String?
var newsDate: String?
var isSaved: Bool = false
private var articles = [Article]()
private var viewModels = [NewsTableViewCellViewModel]()
private let searchVC = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
pecodeTableView.delegate = self
pecodeTableView.dataSource = self
pecodeTableView.register(UINib(nibName: S.CustomCell.customNewsCell, bundle: nil), forCellReuseIdentifier: S.CustomCell.customCellIdentifier)
fetchAllNews()
createSearchBar()
loadNews()
saveNews()
countNewsToCategory()
}
#IBAction func goToFavouritesNews(_ sender: UIButton) {
performSegue(withIdentifier: S.Segues.goToFav, sender: self)
}
private func fetchAllNews() {
APICaller.shared.getAllStories { [weak self] result in
switch result {
case .success(let articles):
self?.articles = articles
self?.viewModels = articles.compactMap({
NewsTableViewCellViewModel(author: $0.author ?? "Unknown", title: $0.title, subtitle: $0.description ?? "No description", imageURL: URL(string: $0.urlToImage ?? "")
)
})
DispatchQueue.main.async {
self?.pecodeTableView.reloadData()
}
case .failure(let error):
print(error)
}
}
}
private func createSearchBar() {
navigationItem.searchController = searchVC
searchVC.searchBar.delegate = self
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModels.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: S.CustomCell.customCellIdentifier, for: indexPath) as! CustomNewsCell
cell.configure(with: viewModels[indexPath.row])
cell.saveNewsBtn.tag = indexPath.row
cell.saveNewsBtn.addTarget(self, action: #selector(didTapCellButton(sender:)), for: .touchUpInside)
return cell
}
#objc func didTapCellButton(sender: UIButton) {
guard viewModels.indices.contains(sender.tag) else { return }
print("Done")// check element exist in tableview datasource
if !isSaved {
saveNews()
print("success")
}
//Configure selected button or update model
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let article = articles[indexPath.row]
guard let url = URL(string: article.url ?? "") else {
return
}
let vc = SFSafariViewController(url: url)
present(vc, animated: true)
}
//Search
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let text = searchBar.text, !text.isEmpty else {
return
}
APICaller.shared.Search(with: text) { [weak self] result in
switch result {
case .success(let articles):
self?.articles = articles
self?.viewModels = articles.compactMap({
NewsTableViewCellViewModel(author: $0.author ?? "Unknown", title: $0.title, subtitle: $0.description ?? "No description", imageURL: URL(string: $0.urlToImage ?? "")
)
})
DispatchQueue.main.async {
self?.pecodeTableView.reloadData()
self?.searchVC.dismiss(animated: true, completion: nil)
}
case .failure(let error):
print(error)
}
}
}
}
extension ViewController {
func loadNews() {
let request: NSFetchRequest<SavedNews> = SavedNews.fetchRequest()
do {
let savedNews = try context.fetch(request)
//Handle saved news
if savedNews.count > 0 {
isSaved = true
}
} catch {
print("Error fetching data from context \(error)")
}
}
func saveNews() {
//Initialize the context
let news = SavedNews(context: self.context)
//Putting data
news.title = newsTitle
news.author = newsAuthor
news.publishedAt = newsDate
news.url = urlString
do {
try context.save()
} catch {
print("Error when saving data \(error)")
}
}
func countNewsToCategory() {
//Initialize the context
let request: NSFetchRequest<SavedNews> = SavedNews.fetchRequest()
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
])
request.predicate = predicate
do {
savedNews = try context.fetch(request)
} catch {
print("Error fetching data from category \(error)")
}
}
}
I don't know where is the problem, I've created a correct data model, but data could not be saved. Here is my model:
import Foundation
struct APIResponse: Codable {
let articles: [Article]
}
struct Article: Codable {
let author: String?
let source: Source
let title: String
let description: String?
let url: String?
let urlToImage: String?
let publishedAt: String
}
struct Source: Codable {
let name: String
}
And also my model in Core Data:
My second view controller, to which I want display the data:
import UIKit
import CoreData
class FavouriteNewsViewController: UIViewController {
#IBOutlet weak var favTableView: UITableView!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var savedNews = [SavedNews]()
override func viewDidLoad() {
super.viewDidLoad()
favTableView.delegate = self
favTableView.delegate = self
loadSavedNews()
favTableView.register(UINib(nibName: S.FavouriteCell.favouriteCell, bundle: nil), forCellReuseIdentifier: S.FavouriteCell.favouriteCellIdentifier)
// Do any additional setup after loading the view.
}
}
extension FavouriteNewsViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return savedNews.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = favTableView.dequeueReusableCell(withIdentifier: S.FavouriteCell.favouriteCellIdentifier, for: indexPath) as! FavouritesCell
print(savedNews)
let article = savedNews[indexPath.row]
if let articleTitle = article.title {
cell.favTitle.text = articleTitle
}
if let articleAuthor = article.author {
cell.favAuthor.text = articleAuthor
}
if let articleDesc = article.desc {
cell.favDesc.text = article.desc
}
return cell
}
}
extension FavouriteNewsViewController {
func loadSavedNews() {
let request: NSFetchRequest<SavedNews> = SavedNews.fetchRequest()
do {
savedNews = try context.fetch(request)
} catch {
print("Error fetching data from context \(error)")
}
}
func deleteNews(at indexPath: IndexPath) {
// Delete From NSObject
context.delete(savedNews[indexPath.row])
// Delete From current News list
savedNews.remove(at: indexPath.row)
// Save deletion
do {
try context.save()
} catch {
print("Error when saving data \(error)")
}
}
}
you did not assign your properties that you are trying to save
newsTitle,newsAuthor,newsDate,urlString
seems these properties have nil value . make sure these properties have valid value before save .
I have a json file that has a list of media stations. I've presented them in four different sections based off of their "category" and "medium" properties. The issue I have is accessing these items in the didSelectRowAt method and passing them to a media player VC so they can be played in an interface that allows switching between stations with a forward and back button. I am able to easily pass the stations array to the media player VC but I can't get a meaningful indexPath or Int of some sort to be able to index into my stations array and add or subtract 1 to the position to change the station. I'm not sure how to proceed. One thing to note is that there is only 1 tv station in the stations array and its the first item,, if that makes a difference. I've included my code below and left out some of the UI code for readability.
import AVKit
import BlackLabsColor
import SDWebImage
import UIKit
private let reuseIdentifier = "Cell"
class MediaCollectionVC2: UICollectionViewController {
var stations = [Station]()
var myVC = MediaPlayerVC()
override func viewDidLoad() {
super.viewDidLoad()
fetchRadioStation()
collectionView.register(MediaCollectionViewCell.self, forCellWithReuseIdentifier: MediaCollectionViewCell.identifier)
collectionView.register(MediaCollectionSectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: MediaCollectionSectionReusableView.identifier)
collectionView.backgroundColor = .gray
self.title = "Live Media"
}
override func numberOfSections(in collectionView: UICollectionView) -> Int { return 4 }
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
switch section {
case 0:
var numOfItems = 0
for station in stations {
if station.medium == "TV" {
numOfItems += 1
}
}
return numOfItems
case 1:
var numOfItems = 0
for station in stations {
if station.category == "News" && station.medium == "Radio" {
numOfItems += 1
}
}
return numOfItems
case 2:
var numOfItems = 0
for station in stations {
if station.category == "Entertainment" && station.medium == "Radio" {
numOfItems += 1
}
}
return numOfItems
case 3:
var numOfItems = 0
for station in stations {
if station.category == "Religious" && station.medium == "Radio" {
numOfItems += 1
}
}
return numOfItems
default:
print("number of items in section problem")
}
return 10
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == UICollectionView.elementKindSectionHeader {
let sectionHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: MediaCollectionSectionReusableView.identifier, for: indexPath) as! MediaCollectionSectionReusableView
switch indexPath.section {
case 0:
sectionHeader.label.text = "TV"
return sectionHeader
case 1:
sectionHeader.label.text = "News Radio"
return sectionHeader
case 2:
sectionHeader.label.text = "Entertainment Radio"
return sectionHeader
case 3:
sectionHeader.label.text = "Religious Radio"
return sectionHeader
default:
sectionHeader.label.text = "Section Header Issue"
return sectionHeader
}
} else {
print("section header issue")
return UICollectionReusableView()
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MediaCollectionViewCell.identifier, for: indexPath) as! MediaCollectionViewCell
switch indexPath.section {
case 0:
let tvStations = stations.filter { $0.medium == "TV" }
let tvStation = tvStations[indexPath.row]
cell.imageView.image = UIImage(named: tvStation.name)
cell.titleLabel.text = tvStation.name
return cell
case 1:
let newsRadioStations = stations.filter { $0.category == "News" && $0.medium == "Radio" }
let newsRadioStation = newsRadioStations[indexPath.row]
cell.imageView.image = UIImage(named: "\(newsRadioStation.name).jpg")
cell.titleLabel.text = newsRadioStation.name
return cell
case 2:
let entertainmentRadioStations = stations.filter { $0.category == "Entertainment" && $0.medium == "Radio" }
let entertainmentRadioStation = entertainmentRadioStations[indexPath.row]
cell.imageView.image = UIImage(named: "\(entertainmentRadioStations.name).jpg")
cell.titleLabel.text = entertainmentRadioStation.name
return cell
case 3:
let religiousRadioStations = stations.filter { $0.category == "Religious" && $0.medium == "Radio" }
let religiousRadioStation = religiousRadioStations[indexPath.row]
cell.titleLabel.text = religiousRadioStation.name
return cell
default:
cell.imageView.image = UIImage(named: "tv")
cell.titleLabel.text = "PROBLEMO"
return cell
}
}
// ISSUES HERE
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
switch indexPath.section {
case 0:
let tvStations = stations.filter { $0.medium == "TV" }
let tvStation = tvStations[indexPath.row]
playVideo(streamURL: tvStation.streamURL)
// Works perfectly fine since I'm not presenting a view controller
case 1:
// Issues arise as I need to pass only radio stations to the player view controller.
let radioStations = stations.filter { $0.medium == "Radio" }
let position = indexPath.item
myVC.stations = radioStations
myVC.position = position
present(myVC, animated: true)
default:
print("nada")
}
}
func playVideo(streamURL: String) {
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback)
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
guard let url = URL(string: streamURL) else {
print("url issue")
return
}
let player = AVPlayer(url: url)
let playerController = AVPlayerViewController()
playerController.player = player
present(playerController, animated: true)
player.play()
} catch {
print("error: \(error)")
}
}
func fetchRadioStation() {
let baseURL = "https://jsonkeeper.com/b/"
guard let url = URL(string: baseURL) else {
print("station list URL invalid")
return
}
let session = URLSession(configuration: .default)
session.dataTask(with: url) { data, response, error in
if error != nil {
print(error ?? "error fetching stations")
return
}
if let safeData = data {
self.parseJSON(data: safeData)
}
}.resume()
}
func parseJSON(data: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(StationData.self, from: data)
let newsData = decodedData.stations
for item in newsData {
let name = item.name
let streamURL = item.streamURL
let desc = item.desc
let category = item.category
let medium = item.medium
let station = Station(name: name, streamURL: streamURL, desc: desc, category: category, medium: medium)
DispatchQueue.main.async {
self.stations.append(station)
self.collectionView.reloadData()
}
}
print("all stations loaded successfully")
} catch {
print("Error decoding: \(error)")
}
}
}
// Media Player VC
import AVFoundation
import BlackLabsColor
import SDWebImage
import UIKit
class MediaPlayerVC: UIViewController {
var backButton: UIButton!
var nextButton: UIButton!
public var station: Int = 0
public var stations = [Station]()
var player: AVPlayer?
var isPlaying: Bool = true
let playPauseButton = UIButton()
override func loadView() {
super.loadView()
configure()
}
override func viewDidLoad() {
super.viewDidLoad()
configure()
}
func configure() {
let station = stations[position]
let urlString = station.streamURL
do {
try AVAudioSession.sharedInstance().setCategory(.playback)
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
guard let url = URL(string: urlString) else {
print("url issue")
return
}
player = AVPlayer(url: url)
guard let player = player else {
print("player issue")
return
}
player.volume = 0.5
player.play()
} catch {
print("error: \(error)")
}
}
#objc func playPauseButtonTapped(_ button: UIButton) {
player?.play()
isPlaying.toggle()
if isPlaying == true {
playPauseButton.setBackgroundImage(UIImage(systemName: "pause"), for: .normal)
} else {
player?.pause()
playPauseButton.setBackgroundImage(UIImage(systemName: "play"), for: .normal)
}
}
#objc func nextButtonTapped(_ button: UIButton) {
if position < stations.count - 1 {
position = position + 1
player?.pause()
for subview in view.subviews {
subview.removeFromSuperview()
}
loadView()
}
}
#objc func backButtonTapped(_ button: UIButton) {
if position > 0 {
position = position - 1
player?.pause()
for subview in view.subviews {
subview.removeFromSuperview()
}
loadView()
}
}
}
How do you properly send data to a custom data NSObject class for a collection view? My variables are always returning as nil.
I have a splash screen in it's own view Controller. When all the data I want loaded has finished loading from firebase, I go to the main screen of the app via performSegue(withIdentifier:), here's the code for the code in question from the SplashScreenViewController:
func getDatabaseReference(){
let d = DispatchGroup()
d.enter()
let encodedURL = (postIDRefDic["post1"]! + "/postURL")
ref.child(encodedURL).observe(.value, with: { (snapshot) in
let newUrl = snapshot.value as! String
DemoSource.shared.url = newUrl
d.leave()
})
d.notify(queue: .main){
self.performSegue(withIdentifier: "showHome", sender: nil)
}
}
in the above code you can see that I'm seguing to my next view controller, HomeViewController, in the HomeViewController class I have a collection view which is helped by a custom class (the NSObject class) called DemoSource (also show above) that I'm using to assign the new data I just got in a variable within that class. This DemoSource class is a custom data class of type NSObject:
import UIKit
import Firebase
struct DataObj {
var image: UIImage?
var play_Url: URL?
var title = ""
var content = ""
}
class DemoSource: NSObject {
static let shared = DemoSource()
var demoData = [DataObj]()
var url = ""
override init() {
demoData += [
DataObj(image: #imageLiteral(resourceName: "ss-1") , play_Url: URL(string: url), title: "title ", content: "Description")
]
}
}
I use this class with my HomeViewController with the collection view:
import UIKit
import AVKit
import Firebase
import MMPlayerView
class HomeViewController: UIViewController {
var offsetObservation: NSKeyValueObservation?
lazy var mmPlayerLayer: MMPlayerLayer = {
let l = MMPlayerLayer()
l.cacheType = .memory(count: 5)
l.coverFitType = .fitToPlayerView
l.videoGravity = AVLayerVideoGravity.resizeAspect
l.replace(cover: CoverA.instantiateFromNib())
l.repeatWhenEnd = true
return l
}()
#IBOutlet weak var playerCollect: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// remove previous download fails file
MMPlayerDownloader.cleanTmpFile()
self.navigationController?.mmPlayerTransition.push.pass(setting: { (_) in
})
offsetObservation = playerCollect.observe(\.contentOffset, options: [.new]) { [weak self] (_, value) in
guard let self = self, self.presentedViewController == nil else {return}
NSObject.cancelPreviousPerformRequests(withTarget: self)
self.perform(#selector(self.startLoading), with: nil, afterDelay: 0.2)
}
playerCollect.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 200, right:0)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
self?.updateByContentOffset()
self?.startLoading()
}
mmPlayerLayer.getStatusBlock { [weak self] (status) in
switch status {
case .failed(let err):
let alert = UIAlertController(title: "err", message: err.description, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self?.present(alert, animated: true, completion: nil)
case .ready:
print("Ready to Play")
case .playing:
print("Playing")
case .pause:
print("Pause")
case .end:
print("End")
default: break
}
}
mmPlayerLayer.getOrientationChange { (status) in
print("Player OrientationChange \(status)")
}
}
deinit {
offsetObservation?.invalidate()
offsetObservation = nil
print("ViewController deinit")
}
#IBAction func profileButtonTap(_ sender: Any) {
let uid = (Auth.auth().currentUser?.uid)!
let Splash = SpalshScreenViewController()
Splash.GetProfilePicture(uid: uid)
Splash.GetUsername(uid: uid)
Splash.GetName(uid: uid)
Splash.GetClipsNumber(uid: uid)
Splash.GetFollowersNumber(uid: uid)
Splash.GetFollowingsNumber(uid: uid)
performSegue(withIdentifier: "showProfile", sender: nil)
}
}
extension HomeViewController: MMPlayerFromProtocol {
func backReplaceSuperView(original: UIView?) -> UIView? {
guard let path = self.findCurrentPath(),
let cell = self.findCurrentCell(path: path) as? PlayerCell else {
return original
}
return cell.imgView
}
// add layer to temp view and pass to another controller
var passPlayer: MMPlayerLayer {
return self.mmPlayerLayer
}
func transitionWillStart() {
}
// show cell.image
func transitionCompleted() {
self.updateByContentOffset()
self.startLoading()
}
}
extension HomeViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let m = min(UIScreen.main.bounds.size.width, UIScreen.main.bounds.size.height)
return CGSize(width: m, height: m*0.75)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
DispatchQueue.main.async { [unowned self] in
if self.presentedViewController != nil || self.mmPlayerLayer.isShrink == true {
//self.playerCollect.scrollToItem(at: indexPath, at: .centeredVertically, animated: true)
//self.updateDetail(at: indexPath)
} else {
self.presentDetail(at: indexPath)
}
}
}
fileprivate func updateByContentOffset() {
if mmPlayerLayer.isShrink {
return
}
if let path = findCurrentPath(),
self.presentedViewController == nil {
self.updateCell(at: path)
//Demo SubTitle
if path.row == 0, self.mmPlayerLayer.subtitleSetting.subtitleType == nil {
}
}
}
}
fileprivate func presentDetail(at indexPath: IndexPath) {
self.updateCell(at: indexPath)
mmPlayerLayer.resume()
if let vc = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "DetailViewController") as? DetailViewController {
vc.data = DemoSource.shared.demoData[indexPath.row]
self.present(vc, animated: true, completion: nil)
}
}
fileprivate func updateCell(at indexPath: IndexPath) {
if let cell = playerCollect.cellForItem(at: indexPath) as? PlayerCell, let playURL = cell.data?.play_Url {
// this thumb use when transition start and your video dosent start
mmPlayerLayer.thumbImageView.image = cell.imgView.image
// set video where to play
mmPlayerLayer.playView = cell.imgView
mmPlayerLayer.set(url: playURL)
}
}
#objc fileprivate func startLoading() {
self.updateByContentOffset()
if self.presentedViewController != nil {
return
}
// start loading video
mmPlayerLayer.resume()
}
private func findCurrentPath() -> IndexPath? {
let p = CGPoint(x: playerCollect.contentOffset.x + playerCollect.frame.width/2, y: playerCollect.frame.height/2)
return playerCollect.indexPathForItem(at: p)
}
private func findCurrentCell(path: IndexPath) -> UICollectionViewCell? {
return playerCollect?.cellForItem(at: path)
}
}
extension HomeViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return DemoSource.shared.demoData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PlayerCell", for: indexPath) as? PlayerCell {
cell.data = DemoSource.shared.demoData[indexPath.row]
return cell
}
return UICollectionViewCell()
}
}
the first time I instantiate the Demosource class is:
extension HomeViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return DemoSource.shared.demoData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PlayerCell", for: indexPath) as? PlayerCell {
cell.data = DemoSource.shared.demoData[indexPath.row]
return cell
}
return UICollectionViewCell()
}
}
When I run my app it crashes as apparently, the url in the data class is nil, even though I set it with the url in my splashscreenviewcontroller? This DemoSource class get's instantiated before the variable is even populated it seems, when I did some debugging with breakpoints.
So my question is, after all this explaining... why isn't the url variable in the DemoSource class still nil and why is the class getting executed before when the view that uses this class is only called AFTER the data has been fetched from Firebase?
You have implemented DemoSource as a singleton, which means that it gets instantiated the first time that you reference DemoSource.shared. This is in getDatabaseReference. When it is instantiated url has its initial value (""), so that is what gets added to the the demoData array.
You don't need an initialiser.
You do need a function to add data to the demoData array.
class DemoSource: NSObject {
static let shared = DemoSource()
var demoData = [DataObj]()
add(urlString: String) {
demoData.append(DataObj(image: #imageLiteral(resourceName: "ss-1") , play_Url: URL(string: urlString), title: "title ", content: "Description"))
}
}
Then, in your getDatabaseReference -
func getDatabaseReference(){
let d = DispatchGroup()
d.enter()
let encodedURL = (postIDRefDic["post1"]! + "/postURL")
ref.child(encodedURL).observe(.value, with: { (snapshot) in
if let newUrl = snapshot.value as? String {
DemoSource.shared.add(urlString: newUrl)
}
d.leave()
})
d.notify(queue: .main){
self.performSegue(withIdentifier: "showHome", sender: nil)
}
}
I have the websocket server that server push the data every two seconds based on my subscription. I need to update the row based on the in the tableview.currently I am using Starscream module for websocket implementation. how to update specific rows value to every two seconds
import UIKit
import Starscream
struct Stocks: Codable {
let changepercent: String
let changeprice: String
let close: String
let currentprice: String
let high: String
let id: Int
let low: String
let name: String
let `open`: String
let type: String
let instid: String
let exchange: String
}
class ViewController: UIViewController, WebSocketDelegate,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var stocktableView: UITableView!
var arrContacts = [Stocks]()
var socket: WebSocket!
override func viewDidLoad() {
super.viewDidLoad()
var request = URLRequest(url: URL(string: "ws://192.168.18.97:8080/sss/landingstream")!)
//var request = URLRequest(url: URL(string: "ws://192.168.18.97:8080/Setrega/landingstream")!)
request.timeoutInterval = 5
socket = WebSocket(request: request)
socket.delegate = self
socket.connect()
}
// MARK: Websocket Delegate Methods.
func websocketDidConnect(socket: WebSocketClient) {
print("websocket is connected")
}
func websocketDidDisconnect(socket: WebSocketClient, error: Error?) {
if let e = error as? WSError {
print("websocket is disconnected: \(e.message)")
} else if let e = error {
print("websocket is disconnected: \(e.localizedDescription)")
} else {
print("websocket disconnected")
}
}
func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {
print("Received text: \(text)")
let decoder = JSONDecoder()
do {
let iexEvent: Stocks = try decoder.decode(Stocks.self, from: text.data(using: .utf8)!)
DispatchQueue.main.async {
self.stocktableView.reloadData()
}
} catch {
print(error)
}
}
func websocketDidReceiveData(socket: WebSocketClient, data: Data) {
print("Received data: \(data.count)")
}
// MARK: Write Text Action
#IBAction func writeText(_ sender: UIBarButtonItem) {
socket.write(string: "{\"requestType\": \"INSTRUMENT_PRICE\",\"topic\": \"SBIN\"}")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.arrContacts.count;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "StocksCell", for: indexPath)
cell.textLabel?.text = arrContacts[indexPath.row].changeprice
return cell
}
// MARK: Disconnect Action
#IBAction func disconnect(_ sender: UIBarButtonItem) {
if socket.isConnected {
sender.title = "Connect"
socket.disconnect()
} else {
sender.title = "Disconnect"
socket.connect()
}
}
}
Instead of this:
DispatchQueue.main.async {
self.stocktableView.reloadData()
}
Try to find the rows that were changed with this function:
final func indexesOfStocks(stocks:[Stocks]) -> [Int] {
return stocks.reduce([]) { (currentResult, currentStocks) in
if let currentStockIndex = self.arrContacts.index(where: { currentStocks.id == $0.id }) {
return currentResult + [currentStockIndex]
}
return currentResult
}
}
Update property arrContacts:
final func updateArrContacts(indexesOfStocksValue:[Int], iexEvents:[Stocks]) {
for i in stride(from: 0, to: indexesOfStocksValue.count, by: 1) {
self.arrContacts[indexesOfStocksValue[i]] = iexEvents[i]
}
}
And reload rows for updates items only:
final func updateRows(stocksIndexes:[Int]) {
DispatchQueue.main.async {
self.stocktableView.performBatchUpdates({
let indexesToUpdate = stocksIndexes.reduce([], { (currentResult, currentStocksIndex) -> [IndexPath] in
if currentStocksIndex < self.stocktableView.numberOfRows(inSection: 0) {
return currentResult + [IndexPath.init(row: currentStocksIndex, section: 0)]
}
return currentResult
})
self.stocktableView.reloadRows(at: indexesToUpdate, with: UITableViewRowAnimation.automatic)
}) { (_) in
}
}
}
Now you can update rows with that code:
let indexesOfStocksValue = self.indexesOfStocks(stocks: iexEvents) // iexEvents is an array of Stocks
self.updateArrContacts(indexesOfStocksValue: indexesOfStocksValue, iexEvents: iexEvents)
self.updateRows(stocksIndexes: indexesOfStocksValue)
This solution is based on idea that after websocketDidReceiveMessage: only existing items in arrContacts should be updated. No new items will be added and no items will be removed.