When I scroll almost to the end of the current table view, it loads more data, and the problem is after reloadData() it almost instantly moves to another cell. For example, if I stopped scrolling on the 12th cell, tableView moves to the 15th. The same with 22 and 25 etc. I don't want my tableView to jump over cells. How can I repair it?
How I check whether its time to load more data:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "previewCell") as! postsCell
let photosForFirstPost = photoURLs[indexPath.row + 1]
//print(photoURLs[0])
if photosForFirstPost?.count != 0 && photosForFirstPost != nil {
let url = photosForFirstPost![0]
// cell.imagePreview.kf.setImage(with: url)
let resource = ImageResource(downloadURL: photosForFirstPost![0], cacheKey: String(describing: photosForFirstPost![0]))
// print(ImageCache.default.isImageCached(forKey: String(describing: photosForFirstPost![0])))
cell.imagePreview.kf.setImage(with: resource)
} else {
cell.imagePreview.image = UIImage(named: "harry")
}
cell.separatorLine.backgroundColor = .blue
cell.themeName.text = "Theme name"
cell.readTime.text = "3 mins read"
cell.textPreview.text = realPosts[indexPath.row].text
if postsTableView.indexPathsForVisibleRows?.first?.row == realPosts.count - 3 {
var arrayOfIndexes = [IndexPath]()
for i in 0...9 {
arrayOfIndexes.append(IndexPath(row: realPosts.count + i, section: 0))
}
requestTenPosts(indexPath: arrayOfIndexes)
}
return cell
}
How I request data at the launch:
func requestForPosts() {
guard let requestURL = URL(string: "https://api.vk.com/method/wall.get?owner_id=\(groupId)&count=\(howMuchPosts)&access_token=\(serviceKey)&v=\(versionOfMethod)&offset=\(offset)") else { return }
do {
self.posts = [try Welcome(fromURL: requestURL)]
realPosts = self.posts[0].response.items
searchPhotos(arrayOfItems: self.realPosts)
offset += howMuchPosts
} catch {
print(error)
}
}
How I request for more data:
func requestTenPosts(indexPath: [IndexPath]) {
guard let requestURL = URL(string: "https://api.vk.com/method/wall.get?owner_id=\(groupId)&count=\(10)&access_token=\(serviceKey)&v=\(versionOfMethod)&offset=\(offset)") else { return }
DispatchQueue.global().async {
do {
self.offset += 10
for howMuchKeysToADD in (self.offset...self.offset + 10) {
self.textOfAPost.updateValue("", forKey: howMuchKeysToADD)
self.photoURLs.updateValue([], forKey: howMuchKeysToADD)
}
var forAMoment = try Welcome(fromURL: requestURL)
var arrayForAMoment: [Item] = []
for i in 0...9 {
self.realPosts.append(forAMoment.response.items[i])
arrayForAMoment.append(forAMoment.response.items[i])
}
print(arrayForAMoment)
print("searchPhotos is called")
self.searchPhotos(arrayOfItems: arrayForAMoment)
DispatchQueue.main.async {
self.postsTableView.reloadData()
}
} catch {
print(error)
}
if postsTableView.indexPathsForVisibleRows?.first?.row == realPosts.count - 3 {
var arrayOfIndexes = [IndexPath]()
for i in 0...9 {
arrayOfIndexes.append(IndexPath(row: realPosts.count + i, section: 0))
}
requestTenPosts(indexPath: arrayOfIndexes)
}else{return}
or use simple else{} and don't forgot to give cell.tag = [indexpath.row]
Instead of reloading the data, can you try inserting the rows at the bottom. Since you already have access to all the index paths, it should not be difficult and table view should not move.
DispatchQueue.main.async {
//self.postsTableView.reloadData()
self.postsTableView.beginUpdates()
self.postsTableView.insertRows(at: indexPath, with: .left)
self.postsTableView.endUpdates()
}
Related
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()
}
we need to pass count in JSON parameter like this
var currentPageNumberVM: Int = 0
"count": currentPageNumber
and in service call I'm getting JSON data like below here JSON data is coming and data is showing in collectionview but pagination is not working
func serviceCall(){
self.currentPageNumberVM+=10
let param = ["jsonrpc": "2.0",
"params": ["type" : type, "count": currentPageNumberVM]] as [String : Any]
APIReqeustManager.sharedInstance.serviceCall(param: param, vc: self, url: getUrl(of: .productByFeature), header: header) {(responseData) in
if responseData.error != nil{
self.view.makeToast(NSLocalizedString("Something went wrong!", comment: ""))
}else{
self.viewmoreDB = ViewMoreBase(dictionary: responseData.dict as NSDictionary? ?? NSDictionary())
self.productsData = self.viewmoreDB?.result?.products
self.collectionView.reloadData()
}
}
}
I'm adding values to collectionview like below
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return productsData?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HorizontalSliderCollectionCell", for: indexPath) as! HorizontalSliderCollectionCell
let indexData = productsData?[indexPath.item]
cell.lblDescrip.text = indexData?.product_by_language?.des
cell.lblTitle.text = indexData?.product_by_language?.title
return cell
}
for pagenation i am trying like below: but nothing works
var isLoading = false
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offsetY = scrollView.contentOffset.y
let contentHeight = scrollView.contentSize.height
if (offsetY > contentHeight - scrollView.frame.height * 4) && !isLoading {
loadMoreData()
}
}
func loadMoreData() {
if !self.isLoading {
self.isLoading = true
DispatchQueue.global().async {
// Fake background loading task for 2 seconds
sleep(2)
// Download more data here
DispatchQueue.main.async {
self.collectionView.reloadData()
self.isLoading = false
}
}
}
}
How to add pagination to collectionview? .. I mean after loading 10 cells..below need to show activityindicator.. and load another 10 cells
how to do this, please do help me
Could you try this? but first, note that you need to pass the counter as a parameter in your call service function:
This code will be added in your cellForItemAt :
let lastPost = postsArray.count - 1
if lastPost == indexPath.row {
if limit < 100{
limit += 10
callAPI()
}
while callAPI function is:
func callAPI () {
PostService.getPosts(limit: limit) { (postsArray, error) in
if error == nil {
guard let postsArray = postsArray else {return}
self.postsArray = postsArray
DispatchQueue.main.async {
self.postsCollectionView.reloadData()
}
}
}
}
}
Of course, you will change the naming dependent on your project.
Hope it helps
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()
}
}
}
In my application I had make call Json function and table view delegate and datasource methods in view did load but here without loading data from web service it is calling table view methods and inside it is crashing due to having no data from model can anyone help me how to resolve this and this is happening sometimes and sometimes it working properly ?
here is my view did load
let guestAddressURL = "http://magento.selldesk.io/index.php/rest/V1/guest-carts/\(guestkey!)/billing-address"
self.guestShippingaddressURL(guestAddressApi: guestAddressURL)
self.tableDetails.delegate = self
self.tableDetails.dataSource = self
self.tableDetails.tableFooterView?.isHidden = true
self.tableDetails.separatorInset = UIEdgeInsets.zero
self.tableDetails.rowHeight = UITableViewAutomaticDimension
self.tableDetails.estimatedRowHeight = 50
self.title = "Checkout"
here is my Json function
func guestShippingaddressURL(guestAddressApi: String) {
print(guestAddressApi)
let url = URL(string: guestAddressApi)
var request = URLRequest(url: url! as URL)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if error != nil { print(error!); return }
do {
if let jsonObj = try JSONSerialization.jsonObject(with: data!) as? [String:Any] {
let obj = jsonObj["street"] as! [String]
for item in obj {
self.street = item
}
print(obj)
print(self.street)
self.guestShippingAddressModel = GuestAddress.init(dict: jsonObj)
if self.street?.isEmpty == false {
self.addressSelected = true
self.selected = false
}
DispatchQueue.main.async {
self.tableDetails.reloadData()
}
}
} catch {
print(error)
}
}
task.resume()
}
func numberOfSections(in tableView: UITableView) -> Int {
if self.street?.isEmpty == false{
return 3
}
else {
if ((addressSelected == true || checkIsPaymentRadioSelect == true) && selected == false) {
return 3
}else {
return 2
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
if ((addressSelected == true || checkIsPaymentRadioSelect == true) && selected == false) {
if (section == 0)
{
return 1
}
else if (section == 1)
{
return 1
}
else
{
return 1
}
}
else
{
if (section == 0)
{
return 1
}
else
{
return 1
}
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if ((addressSelected == true || checkIsPaymentRadioSelect == true) && selected == false){
if (indexPath.section == 0) {
return UITableViewAutomaticDimension
}
else if (indexPath.section == 1) {
return 62
}
else {
if height == 0 {
return CGFloat(heightStart)
}
else{
return CGFloat(self.height)
}
}
}
else{
if (indexPath.section == 0){
if self.street?.isEmpty == true{
return 50
}else {
return UITableViewAutomaticDimension
}
}
else if (indexPath.section == 1){
return 62
}
else {
return 0
}
}
}
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int){
let header = view as! UITableViewHeaderFooterView
header.textLabel?.textColor = UIColor.gray
header.textLabel?.textAlignment = NSTextAlignment.center
header.textLabel?.font = UIFont(name: "Futura", size: 17)
}
The tableview will start loading whether your JSON call has finished or not. This happens automatically after viewDidLoad finishes .. it doesn't wait for the reloadData() call in your completion handler.
So you need to setup your numberOfSections to return 0 until the data has been loaded. This way, your table will be empty (and cellForRow will not even be called) until the completion handler puts the data in place and calls reloadData(). At which time, your numberOfSections will return non-zero and your data will be displayed.
You should initialize all your variables used inside table view methods with some default values.
Like: var street: String! = "" , var addressSelected: Bool! = false etc.
Because even before the API is called, some of these values are nil or not set.
For cell.nameLabel.text = "\((dict?.firstName)!) \, you can return 0 in numberOfRows method.
guard let _ = dict {
return 0
}
You can do it by set your Delegate and DataSource in web Service call Back.
DispatchQueue.main.async {
self.tableDetails.delegate = self
self.tableDetails.dataSource = self
self.tableDetails.reloadData()
}
hope it works.
I am having array in which selected name will be stored and passed to before view controller and when ever i need to go previous view controller then the previously selected check mark needs to be selected but here it is enabling the last selected element only the problem is if i select three then it is not selecting three it is check marking only the last element but i need the three selected can anyone help me how to make the check mark to be selected for three elements ?
protocol ArrayToPass: class {
func selectedArrayToPass(selectedStrings: [String])
}
class FilterSelectionViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
var productName = [String]()
var productprice = [String]()
var imageArray = [String]()
var idArray = [Int]()
let urlString = "http://www.json-generator.com/api/json/get/bOYOrkIOSq?indent=2"
var values = [String]()
var selected: Bool?
var delegate: ArrayToPass?
var nameSelection: Bool?
var namesArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.downloadJsonWithURL()
tableDetails.separatorInset = UIEdgeInsets.zero
activityIndicator.startAnimating()
tableDetails.isHidden = true
tableDetails.dataSource = self
tableDetails.delegate = self
let rightBarButton = UIBarButtonItem(title: "Apply", style: UIBarButtonItemStyle.plain, target: self, action: #selector(applyBarButtonActionTapped(_:)))
self.navigationItem.rightBarButtonItem = rightBarButton
tableDetails.estimatedRowHeight = UITableViewAutomaticDimension
tableDetails.rowHeight = 60
// Do any additional setup after loading the view.
}
func applyBarButtonActionTapped(_ sender:UIBarButtonItem!){
self.delegate?.selectedArrayToPass(selectedStrings: values)
navigationController?.popViewController(animated: true)
}
func downloadJsonWithURL() {
let url = NSURL(string: urlString)
URLSession.shared.dataTask(with: (url as URL?)!, completionHandler: {(data, response, error) -> Void in
if let jsonObj = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSArray {
for item in jsonObj! {
if let itemDict = item as? NSDictionary{
if let name = itemDict.value(forKey: "name") {
self.productName.append(name as! String)
}
if let price = itemDict.value(forKey: "value") {
self.productprice.append(price as! String)
}
if let image = itemDict.value(forKey: "img") {
self.imageArray.append(image as! String)
}
if let id = itemDict.value(forKey: "id") {
self.idArray.append(id as! Int)
}
}
}
OperationQueue.main.addOperation({
self.tableDetails.reloadData()
})
}
}).resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return productName.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "filterSelectionCell", for: indexPath) as! FilterSelectionCell
activityIndicator.stopAnimating()
activityIndicator.hidesWhenStopped = true
tableDetails.isHidden = false
cell.brandProductName.text = productName[indexPath.row]
if nameSelection == true{
if namesArray.count != 0 {
print(namesArray)
for name in namesArray{
if productName[indexPath.row].contains(name){
print(productName[indexPath.row])
cell.accessoryType = .checkmark
}
else {
cell.accessoryType = .none
}
}
}
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
selected = false
if let cell = tableView.cellForRow(at: indexPath as IndexPath) {
if cell.accessoryType == .checkmark{
cell.accessoryType = .none
print("\(productName[indexPath.row])")
values = values.filter{$0 != "\(productName[indexPath.row])"}
selected = true
}
else{
cell.accessoryType = .checkmark
}
}
if selected == true{
print(values)
}
else{
getAllTextFromTableView()
}
print(values)
}
func getAllTextFromTableView() {
guard let indexPaths = self.tableDetails.indexPathsForSelectedRows else { // if no selected cells just return
return
}
for indexPath in indexPaths {
values.append(productName[indexPath.row])
}
}
here is the image for this
Basically do not manipulate the view (the cell). Use a data model.
struct Product {
let name : String
let value : String
let img : String
let id : Int
var selected = false
init(dict : [String:Any]) {
self.name = dict["name"] as? String ?? ""
self.value = dict["value"] as? String ?? ""
self.img = dict["img"] as? String ?? ""
self.id = dict["id"] as? Int ?? 0
}
}
And never use multiple arrays as data source . That's a very bad habit.
Declare the data source array as
var products = [Product]()
Parse the JSON data and do a (better) error handling
func downloadJsonWithURL() {
let url = URL(string: urlString)!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil { print(error!); return }
do {
if let jsonObj = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] {
self.products = jsonObj.map{ Product(dict: $0) }
DispatchQueue.main.async {
self.tableDetails.reloadData()
}
}
} catch {
print(error)
}
}
task.resume()
}
in cellForRow... assign the name to the label and set the checkmark depending on selected
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "filterSelectionCell", for: indexPath)
let product = products[indexPath.row]
cell.textLabel!.text = product.name
cell.accessoryType = product.selected ? .checkmark : .none
return cell
}
In didSelect... toggle selected and reload the row
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selected = products[indexPath.row].selected
products[indexPath.row].selected = !selected
tableView.reloadRows(at: [indexPath], with: .none)
}
To get all selected items is very easy, too.
let selectedItems = products.filter{ $0.selected }
or get only the names
let selectedNames = products.filter{ $0.selected }.map{ $0.name }
There is no need at all to get any information from the view. The controller gets the information always from the model and uses tableview data source and delegate to update the view.
If you want to pass data to another view controller pass Product instances. They contain all relevant information.