UITableView cells not populating with CloudKit data - ios

I have an app that saves an array of 5 randomly generated colors via CloudKit. They are saved under Field Type of String (list). I'm able to save and retrieve the colors successfully.
In my app, I want to display each record with the array of colors on a different row. Currently, the correct number of rows show when data is retrieved, but only the first row has the array of colors (screenshots below). When I pull down on the table, it refreshes the rows and will show a different color array that was saved.
import UIKit
import CloudKit
class FavoritesController: UIViewController, UITableViewDataSource {
let paletteController = PaletteController()
let favoritesTableView = UITableView()
let reuseIdentifier = "favoritesCell"
let privateDatabase = CKContainer.default().privateCloudDatabase
var retrieveFavoritePalette: [CKRecord] = []
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
queryDatabase()
}
func setupTableView() {
favoritesTableView.dataSource = self
favoritesTableView.delegate = self
favoritesTableView.register(UITableViewCell.self, forCellReuseIdentifier: reuseIdentifier)
let heightOfCells: CGFloat = 100
favoritesTableView.rowHeight = heightOfCells
view.addSubview(favoritesTableView)
favoritesTableView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor)
}
func queryDatabase() {
let query = CKQuery(recordType: "Favorite", predicate: NSPredicate(value: true))
privateDatabase.perform(query, inZoneWith: nil) { (records, error) in
if error == nil {
print("Record retrieved")
for record in records! {
self.retrieveFavoritePalette.append(record)
}
} else {
print("Record not retrieved: \(String(describing: error))")
}
let sortedRecords = records?.sorted(by: { $0.creationDate! > $1.creationDate! })
self.retrieveFavoritePalette = sortedRecords ?? []
DispatchQueue.main.async {
self.favoritesTableView.reloadData()
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return retrieveFavoritePalette.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath)
let paletteRecord: CKRecord = retrieveFavoritePalette[indexPath.row]
var individualColorView: [UIView] = []
// print(paletteRecord.value(forKey: "FavoritePalette"))
let line = paletteRecord.value(forKey: "FavoritePalette") as? [String] ?? []
//Creates the individual boxes where the color goes
for i in 0..<5 {
let xAxis = i * 20
let individualView = UIView(frame: CGRect(x: xAxis, y: 0, width: 20, height: 80))
individualColorView.append(individualView)
}
for j in 0..<line.count {
let allColorsView = individualColorView[j]
print(individualColorView[j])
allColorsView.backgroundColor = UIColor(hexString: line[j])
tableView.addSubview(allColorsView)
}
cell.selectionStyle = .none
return cell
}
}
I tried putting let paletteRecord... to tableView.addSubview(allColorsView) into a TableViewCell class, but I got stuck when I couldn't figure out how to have the code compile without indexPath.row in let paletteRecord: CKRecord = retrieveFavoritePalette[indexPath.row].
Output of print(paletteRecord.value(forKey: "FavoritePalette")) is Optional(<__NSArrayM 0x6000039f98f0>( BBBB88, CCC68D, EEDD99, EEC290, EEAA88 ))
This is what it currently looks like. I need each row to display the string array that was saved to CloudKit.
Data that's saved to CloudKit
Any help is appreciated!

I think your issue is on this line:
tableView.addSubview(allColorsView)
You are adding your color boxes to the tableView, but you probably meant to add them to the cell itself like this:
cell.addSubview(allColorsView)

Related

How to show/hide label in a different view when UISwitch isOn in swift?

I have a UISwitch component in my CreateSomethingViewController. This component is on a xib file.
In my SomethingTableViewCell, I have a label called existsLabel.
When I create my something, I can select as Existent (if I turn my UISwitch component on) or not (if Switch is off).
If my existsLabel was in my CreateSomethingViewController, I would do something like this:
#IBAction func changeSomethingExistence(_ sender: UISwitch) {
let isExistent = sender.isOn
existsLabel.isHidden = false
if isExistent {
existsLabel.isHidden = true
}
}
How can I do this (show my existsLabel on my SomethingTableViewCell) when my UISwitch isOn? Using swift.
I think, you already knew the index or position of your updated objects. So We can reload only visible cells row after updating on particular objects to the index position of your cell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? YourTableViewCell
cell?.yourSwitch.isOn = yourList[indexPath.row].switchIsOne
cell?.yourSwitch.tag = indexPath.row
cell?.yourSwitch.addTarget(self, action: #selector(changeSomethingExistence), for:UIControl.Event.valueChanged)
cell?.existsLabel.isHidden = !yourList[indexPath.row].switchIsOne
return cell!
}
Here is your Switch update actions:
#objc func changeSomethingExistence(mySwitch: UISwitch) {
yourList[mySwitch.tag].switchIsOne = mySwitch.isOn
self.updateCell(indexRow: mySwitch.tag)
}
Call this function from anywhere with your selected index and update the same.
func updateCell(indexRow: Int) {
let updatedIndexPath = IndexPath(row: indexRow, section: 0)
self.tableView.reloadRows(at: [updatedIndexPath], with: .automatic)
}
Here's an example. Instead of hiding and showing a view, I set the background color of the cells. The basic ideas are the same.
Essentially you need an object to store the value that the switch controls. In this case I store that data in the same object that I used as the UITableViewDataSource. When the switch is flipped, you tell that object to change the value. It will broadcast the change to all the cells that are currently listening for the change.
There are lots of ways you could observe the change. You could use the Target Action pattern, you could broadcast the change using the NSNotificationCenter. You could use key/value observers, etc. In this case the object holding the value has an #Published property and the cells subscribe to that property.
One critical thing to do is implement prepareForReuse. When a cell is scrolled off the view, it is put in a reuse queue. Rather than create a new cell the system might hand you one out of the reuse buffer. If it does that, you want to be sure the cell is listening to the right source of information for things that change dynamically.
You should be able to copy/paste this code into an iOS Playground:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
import Combine
class CustomCell : UITableViewCell {
var cancelBackgrounds : AnyCancellable?
override func prepareForReuse() {
cancelBackgrounds?.cancel()
cancelBackgrounds = nil
// ALWAYS call super... this can cause hard to identify bugs
super.prepareForReuse()
}
func observeFancyBackground(dataSource: TableData) {
// Set up to observe when the fanch Background value changes
// If this cell was listening to someone else, stop listening to them
// and start listeneing to the new guy.
// This may not be necessary - its a safety check.
cancelBackgrounds?.cancel()
cancelBackgrounds = nil
// Start listening to the new information source
cancelBackgrounds = dataSource.$showFancyBackgrounds.sink(receiveValue: {
isOn in
self.setBackground(isOn)
})
}
private func setBackground(_ showFancy: Bool) {
if showFancy {
self.backgroundConfiguration?.backgroundColor = UIColor.yellow
} else {
self.backgroundConfiguration?.backgroundColor = UIColor.white
}
}
}
class TableData : NSObject, UITableViewDataSource {
let tableData = (1...1000).map { "\(Int($0))" }
#Published var showFancyBackgrounds = false
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.textLabel?.text = tableData[indexPath.row]
cell.observeFancyBackground(dataSource: self)
return cell
}
}
class MyViewController : UIViewController {
let switchView = UISwitch()
let tableView = UITableView(frame: CGRect(x: 0, y: 200, width: 320, height: 100), style: .plain)
let tableData = TableData()
// This is the action called when the switch is toggled.
#objc func switchFlipped(sender: UISwitch) {
tableData.showFancyBackgrounds = sender.isOn
}
// This just sets things up to be pretty.
override func loadView() {
let view = UIView()
switchView.translatesAutoresizingMaskIntoConstraints = false
switchView.addTarget(self, action: #selector(switchFlipped), for: .valueChanged)
tableView.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
tableView.dataSource = tableData
view.addSubview(switchView)
view.addSubview(tableView)
self.view = view
let viewIDs = ["switch" : switchView,
"table" : tableView]
let constraints = [
NSLayoutConstraint.constraints(
withVisualFormat: "V:|-8-[switch]-[table]-|",
options: [],
metrics: nil,
views: viewIDs),
NSLayoutConstraint.constraints(
withVisualFormat: "|-[switch]-|",
options: [],
metrics: nil,
views: viewIDs),
NSLayoutConstraint.constraints(
withVisualFormat: "|-0-[table]-0-|",
options: [],
metrics: nil,
views: viewIDs),
].flatMap { $0 }
view.addConstraints(constraints)
}
}
let myViewController = MyViewController()
PlaygroundPage.current.liveView = myViewController
You can do this by reloading the tableView when the switch is changed.
var isExistent: Bool
#IBAction func changeSomethingExistence(_ sender: UISwitch) {
isExistent = sender.isOn
//reload the table
tableView.reloadData()
}
In your UITableViewDataSource you can check which cell.label need to be hidden or not and accordingly hide/show the label of those cells
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//decide which cells needs to be hide/show based on the indexPath and switchValue
//then you can call cell.existsLabel.isHidden = isExistent
}

How can I improve the performace of my UITableView

I have a UITableView which loads images from a firebase database. Each cell in the table contains three pictures. The firestore query loads three documents at a time, while the table view paginates when the users scroll to the bottom. The issue I am having is that as I scroll the table view stutters every time it reaches a new cell. Each cell takes up a little more than the full screen. Here is an example of what I am trying to describe: https://imgur.com/a/xRB6gZg
Here is the code that is producing these issues:
func paginate(){
postQuery = postQuery.start(afterDocument: documents.last!)
self.loadPosts()
}
//queries Firestore and loads into postArray
func loadPosts() {
if let blockedArray = userDefaults.array(forKey: blockKey) as? [String]{
blockedUsers = blockedArray
}
postQuery.getDocuments{ [weak self](querySnapshot, error) in
self!.q.async{
if let err = error {
print(err)
}else{
var postsTemp = self?.postArray
for doc in querySnapshot!.documents{
self?.documents += [doc]
let post = self!.createPost(doc)
if(!self!.postArray.contains(post) && !self!.blockedUsers.contains(post.uid)){
postsTemp?.append(post)
}
DispatchQueue.main.async {
self!.postArray = postsTemp!
self!.tableView.reloadData()
self!.isNewDataLoading = false
}
}
self!.loadedFirst = true
}
}
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if postArray.count == 0{
return 1
}else{
return postArray.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var post: PostStruct
var peopleUserIsFollowing: [String] = []
let cell = tableView.dequeueReusableCell(withIdentifier: K.cellIdentifier, for: indexPath) as! PostCell
cell.delegate = self
if postArray.count == 0 {
let instructions = cell.textLabel
instructions?.text = "Press the camera to start Piking!"
instructions?.textAlignment = .center
clearPosts(cell)
}else {
post = postArray[indexPath.row]
if let leftPostArray = userDefaults.array(forKey: fbLeftKey) as? [String]{
votedLeftPosts = leftPostArray
}
if let rightPostArray = userDefaults.array(forKey: fbRightKey) as? [String]{
votedRightPosts = rightPostArray
}
let firstReference = storageRef.child(post.firstImageUrl)
let secondReference = storageRef.child(post.secondImageUrl)
//For FriendsTableView query
let db = Firestore.firestore()
let followingReference = db.collection("following")
.document(currentUser!)
.collection("UserIsFollowing")
followingReference.getDocuments(){(querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
peopleUserIsFollowing.append(document.documentID)
}
}
}
//Fill in labels and imageViews
cell.timer = createTimer(post, cell)
cell.firstImageView.sd_setImage(with: firstReference)
cell.secondImageView.sd_setImage(with: secondReference)
cell.leftTitle.text = post.firstTitle
cell.rightTitle.text = post.secondTitle
cell.postDescription.text = post.postDescription + "\(indexPath)"
if post.userPic == "" {
userPic =
"https://firebasestorage.googleapis.com/v0/b/pikit-7e40e4.appspot.com/o/Default%20Profile%20Pic.png?alt=media&token=2bc88382-2ad3-4eb8-8163-dcddf391c666"
} else{
userPic = post.userPic
}
let url = URL(string: userPic)
let data = try? Data(contentsOf: url!)
cell.profilePic.image = UIImage(data: data!)
let votesCollection = db.collection("votes").document(post.postID)
getCount(ref: votesCollection, cell: cell)
if(post.uid != currentUser){
cell.userName.text = post.poster
}else{
cell.userName.text = "Me"
cell.tapLeft.isEnabled = false
cell.tapRight.isEnabled = false
}
cell.textLabel?.text = ""
if(post.poster == Auth.auth().currentUser!.uid || post.endDate - Int(NSDate().timeIntervalSince1970) <= 0){
cell.tapRight.isEnabled = false
cell.tapLeft.isEnabled = false
cell.firstImageView.layer.borderWidth = 0
cell.secondImageView.layer.borderWidth = 0
}
else if(votedRightPosts.contains(post.firstImageUrl)){
cell.secondImageView.layer.borderColor = UIColor.green.cgColor
cell.secondImageView.layer.borderWidth = 4
cell.firstImageView.layer.borderWidth = 0
cell.tapRight.isEnabled = false
cell.tapLeft.isEnabled = true
}
else if (votedLeftPosts.contains(post.firstImageUrl)){
cell.firstImageView.layer.borderColor = UIColor.green.cgColor
cell.firstImageView.layer.borderWidth = 4
cell.secondImageView.layer.borderWidth = 0
cell.tapLeft.isEnabled = false
cell.tapRight.isEnabled = true
}
}
return cell
}
override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let postCell = tableView.dequeueReusableCell(withIdentifier: K.cellIdentifier, for: indexPath) as! PostCell
clearPosts(postCell)
postCell.timer?.invalidate()
postCell.timer = nil
}
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
//Bottom Refresh
if scrollView == tableView{
if ((scrollView.contentOffset.y + scrollView.frame.size.height) >= scrollView.contentSize.height)
{
if !isNewDataLoading{
isNewDataLoading = true
paginate()
}
}
}
}
I have tried adjusting what didEndDisplaying does, such as clearing cells/ not clearing cells, but that had no effect. I have also tried changing around where paginate is called but this seems to be the best way. I am not sure where I went wrong. I have also noticed in the Xcode debugger that the memory usage of the app steadily rises as the table view is scrolled up and down, but never seems to go down.
In general, you have two options to fix this problem. That's a lot of code to parse through, so I can't give you a code sample, but the answers are either:
Pre-fetching
When you scroll to item 2, kick off the fetch for items 4,5,6 (since you fetch 3 at a time) before you scroll down that far.
Also... you might consider fetching more than 3 at a time. Like... 50, or 100. Modern iOS devices have lots of memory. No real reason I can think of to limit it to so few.
Placeholders
Build your layout so it gives placeholder data and then kick off the fetch asynchronously to update the on-screen layout with the real data.
Either way is going to require you to restructure your code a bit. My intuition says that pre-fetching is going to be easier for you.

Swift - ReloadData not update

I'm trying to fill CollectionView in Swift with data from a Json Structure but I'm not able to reload the array and parse into the collectionView
I'm declaring an Array(V_textoturismo) and I parse the content of JSON to it, but seems that is not able or when it passes I can't reload the collection view.
If I made it with an static Array(texto) I can't do it without a problem
Thanks 4 everything
I Attach my code:
import UIKit
struct category: Codable {
let textoturismo: String
let imagenturismo: String
let destinoturismo: String
}
struct Entry_category: Codable {
let categories: [String: category]
}
class CollectionViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet var menuButton:UIBarButtonItem!
var v_textoturismo:[String] = []
var v_imagenturismo:[String] = []
var v_destinoturismo:[String] = []
let imagen = ["Perdidos", "Friends", "Breaking Bad", "Dexter"]
let texto = [NSLocalizedString("Hotels", comment: ""),
NSLocalizedString("Restaurants", comment: ""),
NSLocalizedString("Bars&Pubs", comment: ""),
NSLocalizedString("Discoteques", comment: "")]
override func viewDidLoad() {
super.viewDidLoad()
//collectionView?.dataSource = self;
//collectionView?.delegate = self;
if self.revealViewController() != nil {
menuButton.target = self.revealViewController()
menuButton.action = #selector(SWRevealViewController.revealToggle(_:))
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
if let layout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout{
//layout.minimumLineSpacing = 10
//layout.minimumInteritemSpacing = 10
//layout.sectionInset = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 0)
//let size = CGSize(width:(collectionView!.bounds.width)/2, height: 150)
let size = CGSize(width:(collectionView!.frame.size.width)/2, height: 150)
layout.itemSize = size
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return v_textoturismo.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let identifier = "Item"
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as! SeriesCollectionViewCell
//cell.itemLabel.text = texto[indexPath.row]
cell.itemLabel.text = v_textoturismo[indexPath.row]
cell.itemImage.image = UIImage.init(imageLiteralResourceName: imagen[indexPath.row])
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let item = sender as? UICollectionViewCell
let indexPath = collectionView.indexPath(for: item!)
let detailVC = segue.destination as! DetailViewController
detailVC.detailName = imagen[(indexPath?.row)!]
}
func parseCategories(){
//Leo JSON categorias
NSLog("entro")
if let url2 = URL(string: "http://s369243288.mialojamiento.es/WS_CB_Addicts/categorias_turismo/json_data.php") {
URLSession.shared.dataTask(with: url2) { data, response, error in
if let data = data {
let jsonDecoder = JSONDecoder()
do {
let parsedJSON = try jsonDecoder.decode(Entry_category.self, from: data)
for category in parsedJSON.categories {
self.v_textoturismo.append(category.value.textoturismo)
print(category.value.textoturismo)
self.v_imagenturismo.append(category.value.imagenturismo)
self.v_destinoturismo.append(category.value.destinoturismo)
print(category.value.destinoturismo)
//print(image.value.destino)
}
} catch {
print(error)
}
}
}.resume()
}
//End Leo JSON categorias
}
override func viewWillAppear(_ animated: Bool) {
//super.viewWillAppear(animated)
parseCategories()
//self.collectionView.reloadData()
DispatchQueue.main.async {
self.collectionView.reloadData()
}
print(v_textoturismo)
}
}
Your collection view datasource lines are commented out, so I assume you're setting this up in Interface Builder / Storyboards?
//collectionView?.dataSource = self;
//collectionView?.delegate = self;
If these are not set your collection view will always be empty.
Side note: semicolons not necessary here
Next, looking at your viewWillAppear method, there are multiple issues here. Firstly, always always always call super unless the framework specifically tells you not to. There could be hidden behavior here in a future version of iOS that you might miss out on.
In fact, the docs state:
If you override this method, you must call super at some point in your implementation.
Next, you might consider only calling parseCategories in viewDidLoad instead of viewWillAppear. You probably don't want this called every single time they arrive at this screen, if they switch tabs, for instance.
Your DispatchQueue.main.async call is unnecessary here. This method is always called on the main queue.
Calling reloadData here is not correct either, you want to do this when there is new data to be loaded, and right now you have just called parseCategories which issues a network request, so you have to wait for that to come back first.
Inside your parseCategories method you are not handling the case where the network request comes back with an Error, or a non-successful HTTP status code.
Finally, in the success block after parsing the data, you add the categories to an array, but you never tell the collection view to reload here. You need:
DispatchQueue.main.async {
self.collectionView.reloadData()
}
after you've added the data to the v_textoturismo array.

How to populate a TableView with data from Firebase Firestore?

I am very new to iOS development (with emphasis on very). I think I have grasped simple table views in Xcode without calling on a database, and I also think I understand the basics of how to call data from Firestore, but I cannot for the life of me figure out how to populate my TableView with data from Firestore.
The Firestore collection I want to populate with is called "articles", where each doc represents an article I want to display in a cell. Each doc has this structure of data:
imageURL: https://someurl.com
title: 5 places you don't want to miss
I have created a UITableView with a UITableViewCell inside it in Storyboard, where the TableViewCell's ContentView contains an ImageView for the 'imageURL' data in Firestore and a Label for the 'title' data in Firetore.
The UITableView in Storyboard is linked to ArtiklerTableViewController.swift.
Likewise is the UITableViewCell linked to ArtiklerCell.swift.
The two Swift files look like this now:
ArtiklerTableViewController.swift
class ArtiklerTableViewController: UITableViewController {
#IBOutlet var artiklerTableView: UITableView!
var artiklerArray: [String] = []
var documents: [DocumentSnapshot] = []
var db: Firestore!
override func viewDidLoad() {
super.viewDidLoad()
db = Firestore.firestore()
configureTableView()
loadData()
func configureTableView() {
tableView.delegate = self
tableView.dataSource = self
tableView.register(ArtiklerCell.self, forCellReuseIdentifier: "ArtiklerCell")
// remove separators for empty cells
tableView.tableFooterView = UIView()
// remove separators from cells
tableView.separatorStyle = .none
}
func loadData() {
db.collection("articles").getDocuments() { (QuerySnapshot, err) in
if let err = err {
print("Error getting documents : \(err)")
}
else {
for document in QuerySnapshot!.documents {
let documentID = document.documentID
let artiklerImageView = document.get("imageURL") as! URL
let artiklerTitleLabel = document.get("title") as! String
print(artiklerImageView, artiklerTitleLabel, documentID)
}
self.tableView.reloadData()
}
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("Tableview setup \(artiklerArray.count)")
return artiklerArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ArtiklerCell", for: indexPath) as! ArtiklerCell
let artikler = artiklerArray[indexPath.row]
print("Array is populated \(artiklerArray)")
return cell
}
}
ArtiklerCell.swift
import UIKit
import Firebase
class ArtiklerCell: UITableViewCell {
#IBOutlet weak var artiklerImageView: UIImageView!
#IBOutlet weak var artiklerTitleLabel: UILabel!
var db: Firestore!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
db = Firestore.firestore()
addSubview(artiklerImageView)
addSubview(artiklerTitleLabel)
configureImageView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureImageView() {
artiklerImageView.layer.cornerRadius = 20
artiklerImageView.clipsToBounds = true
}
}
When I try to run the app, I get an error message from the ArtiklerTableViewController.swift regarding the line let artikler = artiklerArray[indexPath.row] in the cellForRowAt function, saying 'Initialization of immutable value 'artikler' was never used; consider replacing with assignment to '_' or removing it'.
I see that this error message makes sense, but I have absolutely no idea what I should do instead.
Pardon my extreme lack of knowledge! I have spent many days now trying to look for the answers I need online without finding a solution. I think I am too inexperienced to correctly search for and absorb the necessary knowledge for this problem.
Any answer will be immensely appreciated!
Thanks in advance from a desperate girl who doesn't want to give up on learning iOS dev as I go through building an app.
You already have the strings in an array and got the artikler corresponding to the row of the cell, now you just need to set the title and the image. Also, you need to append each element to the array before reloading.
func loadData() {
db.collection("articles").getDocuments() { (QuerySnapshot, err) in
if let err = err {
print("Error getting documents : \(err)")
}
else {
for document in QuerySnapshot!.documents {
let documentID = document.documentID
let artiklerImageView = document.get("imageURL") as! URL
let artiklerTitleLabel = document.get("title") as! String
self.artiklerArray.append(artiklerTitleLabel)
}
self.tableView.reloadData()
}
}
}
...
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ArtiklerCell", for: indexPath) as! ArtiklerCell
let artikler = artiklerArray[indexPath.row]
cell.artiklerTitleLabel.text = artikler
return cell
}

Tableview need to be reloaded twice to update the data from textfield?

I have question about the tableView.
Here is my tableView code
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tierCount
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "InterestRateTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? InterestRateTableViewCell else {
fatalError("The dequed cell is not an instance of InterestRateTableViewCell.")
}
cell.interestRateTextField.delegate = self
cell.rowLabel.text = "\(indexPath.row + 1)."
if let interestText = cell.interestRateTextField.text {
if let interest = Double(interestText){
interestRateArray[indexPath.row] = interest
} else {
interestRateArray[indexPath.row] = nil
}
} else {
interestRateArray[indexPath.row] = nil
}
return cell
}
As you can see, I have the cellForRowAt method to get the value from the textfields in the cell, and assign to my arrays. (I actually have 2 textfields per cell.)
Basically, I let the users input and edit the textfield until they are happy then click this calculate button, which will call the calculation method. In the calculation method I call the "tableView.reloadData()" first to gather data from the textfields before proceed with the actual calculation.
The problem was when I ran the app. I typed values in all the textfields then clicked "calculate", but it showed error like the textfields were still empty. I clicked again, and it worked. It's like I had to reload twice to get things going.
Can anyone help me out?
By the way, please excuse my English. I'm not from the country that speak English.
edited: It may be useful to post the calculate button code here as someone suggested. So, here is the code of calculate button
#IBAction func calculateRepayment(_ sender: UIButton) {
//Reload data to get the lastest interest rate and duration values
DispatchQueue.main.async {
self.interestRateTableView.reloadData()
}
//Get the loan value from the text field
if let loanText = loanTextField.text {
if let loanValue = Double(loanText) {
loan = loanValue
} else {
print("Can not convert loan value to type Double.")
return
}
} else {
print("Loan value is nil")
return
}
tiers = []
var index = 0
var tier: Tier
for _ in 0..<tierCount {
if let interestRateValue = interestRateArray[index] {
if let durationValue = durationArrayInMonth[index] {
tier = Tier(interestRateInYear: interestRateValue, tierInMonth: durationValue)
tiers.append(tier)
index += 1
} else {
print("Duration array contain nil")
return
}
} else {
print("Interest rate array contain nil")
return
}
}
let calculator = Calculator()
repayment = calculator.calculateRepayment(tiers: tiers, loan: loan!)
if let repaymentValue = repayment {
repaymentLabel.text = "\(repaymentValue)"
totalRepaymentLabel.text = "\(repaymentValue * Double(termInYear!) * 12)"
} else {
repaymentLabel.text = "Error Calculating"
totalRepaymentLabel.text = ""
}
}
cellForRowAt is used for initially creating and configuring each cell, so the textfields are empty when this method is called.
UITableView.reloadData() documentation:
// Reloads everything from scratch. Redisplays visible rows. Note that this will cause any existing drop placeholder rows to be removed.
open func reloadData()
As it says in Apple's comment above, UITableView.reloadData() will reload everything from scratch. That includes your text fields.
There are a number of ways to fix your issue, but it's hard to say the best way without more context. Here's an example that would fit the current context of your code fairly closely:
class MyCustomTableViewCell: UITableViewCell {
#IBOutlet weak var interestRateTextField: UITextField!
var interestRateChangedHandler: (() -> ()) = nil
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
interestRateTextField.addTarget(self, action: #selector(interestRateChanged), for: UIControlEvents.editingChanged)
}
#objc
func interestRateChanged() {
interestRateChangedHandler?()
}
}
and cellForRowAtIndex:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "InterestRateTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? InterestRateTableViewCell else {
fatalError("The dequed cell is not an instance of InterestRateTableViewCell.")
}
cell.rowLabel.text = "\(indexPath.row + 1)."
cell.interestRateChangedHandler = { [weak self] in
if let interestText = cell.interestRateTextField.text {
if let interest = Double(interestText){
self?.interestRateArray[indexPath.row] = interest
} else {
self?.interestRateArray[indexPath.row] = nil
}
} else {
self?.interestRateArray[indexPath.row] = nil
}
}
return cell
}

Resources