Xcode Swift Table View: Use of Slider and SegmentedControl as custom cells - swift

I am currently using numerous custom cells in a tableview, of which consist of sliders and segmented controls. My issue is that in the simulator, when these rows are selected, the values of the sliders and pickers are reset to default. So for example, if I changed the value of the slider to 10, and then selected the row of the slider, the slider's value would reset to 0. I am trying to diagnose this issue so when the row is selected, the value remains the same; however, I am not sure where this problem arises. I have a hunch that it has something to do with .dequeueReusableCell or .reloadData, but I do not have a great grasp of what they do to start debugging.
Below is my code for cellForRowAt indexPath:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let currentCellDescriptor = getCellDescriptorForIndexPath(indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: currentCellDescriptor["cellIdentifier"] as! String, for: indexPath) as! CustomCell
// I am using identifiers to set Titles within the TableView
if currentCellDescriptor["cellIdentifier"] as! String == "idCellNormal" {
if let primaryTitle = currentCellDescriptor["primaryTitle"] {
cell.textLabel?.text = primaryTitle as? String
}
eventType = ((cellDescriptors[0] as! NSMutableArray)[0] as! NSDictionary)["primaryTitle"]! as! String
if let secondaryTitle = currentCellDescriptor["secondaryTitle"] {
cell.detailTextLabel?.text = secondaryTitle as? String
}
}
else if currentCellDescriptor["cellIdentifier"] as! String == "idCellTextfield" {
cell.textField.placeholder = currentCellDescriptor["primaryTitle"] as? String
}
else if currentCellDescriptor["cellIdentifier"] as! String == "idCellValuePicker" {
cell.textLabel?.text = currentCellDescriptor["primaryTitle"] as? String
}
cell.delegate = self
return cell
}
Code from CustomCell, my class for all my cells
//outlet properties
#IBOutlet weak var moneyLabel: UILabel!
#IBOutlet weak var moneySlider: UISlider!
#IBOutlet weak var privacy: UISegmentedControl!
#IBAction func sliderValueChanged(_ sender: UISlider) {
moneyValue = Int(sender.value)
moneyLabel.text = String(moneyValue)
moneyAmount = moneyLabel.text!
}
#IBAction func privacyChanged(_ sender: AnyObject) {
switch privacy.selectedSegmentIndex
{
case 0:
print("public")
privacyDescription = "public"
case 1:
print("private")
privacyDescription = "private"
default:
break;
}
}

Related

How to maintain state of switch button inside tableviewcell with searchbar in xcode

Description : i have a content in tableview along with switch button .
Problem : after using the searchbar for serching items in tableview state of switch button is lost - switch button which was set on for row 0 and row 1 has lost .
To achieve this you have to store button state with your data.
And Read the data in cellForRowAtIndexPath: Handle UI
Example:
if indexPath.row == 0 || indexPath.row == 1{
// Handle Button UI here
let value = buttonArrayData[indexPath.row]
if(value){
//button selected
//update buttonArrayData data accordingly.
}
else{
//button unselected
//update buttonArrayData data accordingly.
}
}
It is not a problem associated with UISearchBar but seems because of TableView reload.
To save the state of UISwitch in the case of table reload, you need have a custom UITableViewCell class and UISwitch outlet inside it.
Then to save a UISwitch state for further use
You can store only the selected cell index as state in UserDefault and check the same against the current index in cellForRowAt tableView method. If it is found then your last state is ON otherwise default one is OFF.
This code snippet will work as you expect:
Custom UITableViewCell class
class CustomCell: UITableViewCell {
#IBOutlet weak var switchObj: UISwitch!
#IBOutlet weak var textValue: UILabel!
#IBAction func toggleState(_ sender: Any) {
var loadValues: [Int: Bool]!
if let values = Memory.getStatesFromMemory() {
loadValues = values
} else {
loadValues = [Int: Bool]()
}
loadValues[switchObj.tag] = switchObj.isOn
Memory.saveSwitchStatesToMemory(savedStates: loadValues)
}
}
UIViewController class
class CustomTableView: UIViewController, UITableViewDelegate, UITableViewDataSource {
var savedStates: [Int: Bool]!
override func viewDidLoad(){
super.viewDidLoad()
if let values = Memory.getStatesFromMemory() {
savedStates = values
} else {
savedStates = [Int: Bool]()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell") as? CustomCell
cell?.switchObj.isOn = savedStates[indexPath.row] == nil ? false : savedStates[indexPath.row]!
cell?.switchObj.tag = indexPath.row
cell?.textValue.text = "Switch stored state >>"
return cell!
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
}
Custom class to use for save/retrieve values from memory
class Memory {
static func getStatesFromMemory() -> [Int: Bool]? {
if let switchValues = UserDefaults.standard.object(forKey: "SwitchStates") as? Data {
let decodedTeams = NSKeyedUnarchiver.unarchiveObject(with: switchValues) as! [Int: Bool]
return decodedTeams
}
return nil
}
static func saveSwitchStatesToMemory(savedStates: [Int: Bool]) {
let encodedData = NSKeyedArchiver.archivedData(withRootObject: savedStates)
let userDefaults = UserDefaults.standard
userDefaults.set(encodedData, forKey: "SwitchStates")
UserDefaults.standard.synchronize()
}
}

Swift - Search in UITableView [duplicate]

This question already has answers here:
Searchbar filtering issue
(3 answers)
Closed 3 years ago.
I'm trying to be able to search my Firebase data using a UISearchBar in my app & I am stuck. I am successfully receiving data from Firebase in a table view. I have a memories-writing app that the user can create memories (that are shown in a tableview from the firebase). memories has a title, description, a pic and a date and I want it to be able to search memories by the title.
I have a code here that doesn't work for some reason... il'l be glad if you could help me find out what's wrong in the code or find a replacement for this code :)
MemoryTitles class:
class MemoryTitles {
var title : String
init(withTitle: String) {
self.title = withTitle
}
}
MemoryViewController:
class MemoryViewController: UIViewController,UITableViewDelegate,UITableViewDataSource
// the filtered memories array for the search bar
var memoriesTitlesArr: [String] = []
var filteredDataa: [String] = []
// connections from storyboard to the code
#IBOutlet weak var tbl: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
// an array of memories
var memories : [Memory] = []
var ref = Database.database().reference()
let sref = Storage.storage().reference()
var lastIndex : Int = 0
var strMode : String = ""
// TableView functions
// Return the number of rows in section
// section - an index number identifying a section in tableView.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return filteredDataa.count
} else {
return memories.count
}
// return memories.count
}
// Return Cell for row function : an object inheriting from UITableViewCell
// indexPath - an index path locating a row in tableView.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "iden")
if searching {
cell?.textLabel?.text = filteredDataa[indexPath.row]
} else {
var cell : UITableViewCell? = tableView.dequeueReusableCell(withIdentifier: "iden", for: indexPath)
if cell == nil
{
cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "iden")
}
let temp = memories[indexPath.row]
cell?.textLabel?.text = temp.title
cell?.imageView?.image = temp.image
return cell!
}
return cell!
}
// Can edit row : asks the data source to verify that the given row is editable.
// indexPath - an index path locating a row in tableView.
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true // true if the row indicated by indexPath is editable; otherwise, false.
}
// Asks the data source to commit the insertion or deletion of a specified row.
// indexPath - an index path locating a row in tableView.
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete // editingStyle - the cell editing style corresponding to a insertion or deletion requested for the row specified by indexPath.
{
let temp = self.memories[indexPath.row]
self.memories.remove(at: indexPath.row)
self.ref.child("MEmories/\(temp.key)").removeValue()
tableView.deleteRows(at: [indexPath as IndexPath], with: .fade)
}
}
override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference()
let rightButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(MemoryViewController.barButtonItemClicked(_:)))
self.navigationItem.setRightBarButton(rightButton, animated: true)
// Do any additional setup after loading the view.
self.loadMemories()
self.tbl.delegate = self
self.tbl.dataSource = self
}
// click on bar-Button function
#objc func barButtonItemClicked(_ sender:UIBarButtonItem)
{
print("+ clicked") // writes "+ clicked"
let addMemoryViewController = self.storyboard?.instantiateViewController(withIdentifier: "AddMemoryViewController") as! AddMemoryViewController
self.strMode = "newMemory"
self.navigationController?.pushViewController(addMemoryViewController, animated: true)
}
// Reading from NSUserDefault (A class that provides simple storage of different data types solution.)
func readFromNSUserDefault()-> Memory?
{
let d : UserDefaults = UserDefaults.standard
let strTitle = d.object(forKey: "title") as? String
let strBody = d.object(forKey: "body") as? String
let strImageRef = d.object(forKey: "imageRef") as? String
let uid = d.object(forKey: "uid") as? String
let imageData = d.object(forKey: "imageData") as? Data
let key = d.object(forKey: "key") as? String
let date = d.object(forKey: "date") as? NSNumber
let m = Memory(title: strTitle!, body: strBody!, key: key!, uid: uid!, imageRef: strImageRef!, date: date!) // A variable from type memory
m.image = UIImage(data: imageData!)
m.key = key!
return m
}
override func viewDidAppear(_ animated: Bool) {
let d = UserDefaults.standard
let newMemory = readFromNSUserDefault()
let userAdded = d.bool(forKey: "userAdded") //key new user = true
let userEdited = d.bool(forKey: "userEdited")//key user edited = true
if self.strMode == "newMemory" && userAdded
{
self.memories.append(newMemory!)
self.tbl.reloadData()
}
else if self.strMode == "edit" && userEdited
{
memories[lastIndex] = newMemory!
self.tbl.reloadData()
}
d.set(false, forKey: "userAdded")
d.set(false, forKey: "userEdited")
d.synchronize()
self.strMode = " "
}
// loading the memories from the Database
func loadMemories()
{
let UID = Auth.auth().currentUser!.uid
self.ref.child("MEmories").queryOrdered(byChild: "uid").queryEqual(toValue: UID).observeSingleEvent(of: .value, with: {
snapShot in
if let dict = snapShot.value as? NSDictionary
{
for d in (dict as? Dictionary<String,AnyObject>)!
{
let title = d.value["title"] as?String
let body = d.value["body"] as? String
let uid = d.value["uid"] as? String
let imageRef = d.value["imageRef"] as? String
let date = d.value["date"] as? NSNumber
let m = Memory(title: title!, body: body!, uid: uid!,imageRef:imageRef!, date: date!)
m.key = d.key
let tempImageRef = self.sref.child(m.imageRef)
tempImageRef.getData(maxSize: 500*1024*1024, completion: {(data,error) in
if error == nil
{
if let imageData = data
{
m.image = UIImage(data: imageData)
self.memories.append(m)
self.tbl.reloadData()
}
}
})
self.memoriesTitlesArr.append(title!)
}
}//end of if
})
}
// Notifies the view controller that a segue is about to be performed.
// segue - The segue object containing information about the view controllers involved in the segue.
// senderThe object that initiated the segue.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier
{
if identifier == "goToEdit"
{
let indexPath = self.tbl.indexPathForSelectedRow
let addMemoryViewController = segue.destination as! AddMemoryViewController
self.strMode = "edit"
self.lastIndex = (indexPath?.row)!
addMemoryViewController.mode = self.strMode
addMemoryViewController.current = memories[(indexPath?.row)!]
}
}
}
var searching = false
}
extension MemoryViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredDataa = memoriesTitlesArr.filter({ $0.prefix(searchText.count)==searchText.lowercased()})
searching = true
tbl.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searching = false
searchBar.text = ""
tbl.reloadData()
}
}
Here is how you can get that working.
1. First of all, there is no need for searching to maintain the state of searching and not searching.
2. Secondly, use filteredData as the dataSource for tableView instead of memories. filteredData will initially contain all the objects from memories, i.e.
var memories : [Memory] = []
lazy var filteredData = self.memories
The UITableViewDataSource methods will be like,
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.filteredData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "iden") {
cell.textLabel?.text = self.filteredData[indexPath.row].title
return cell
}
return UITableViewCell()
}
Now, while searching update the filteredData with filtered memories using the relevant condition, i.e.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
self.filteredData = self.memories.filter({ $0.title.hasPrefix(searchText) })
//change the condition as per your requirement......
self.tbl.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.text = nil
self.filteredData = self.memories
self.tbl.reloadData()
}
When cancelled, refill the filteredData with the whole memories data.

Invalid UILabel content when clicking on a cell

I have an UITableView with custom cells:
class customChatCell: UITableViewCell {
#IBOutlet weak var userName: UILabel!
#IBOutlet weak var userMessage: UILabel!
And I have a code for open URL contained in the userMessage:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "ClientCell"
self.cell = self.tableView.dequeueReusableCell(withIdentifier: identifier) as? customChatCell
self.cell.selectionStyle = UITableViewCell.SelectionStyle.none
// Unpack message from Firebase DataSnapshot
let messageSnapshot = self.messages[indexPath.row]
guard let message = messageSnapshot.value as? [String: String] else { return cell }
let name = message[Constants.MessageFields.name] ?? ""
if let imageURL = message[Constants.MessageFields.imageURL] {
self.cell.userName.text = name
self.cell.hiddenLabel.isHidden = true
self.cell.userMessage.text = imageURL
let tap = UITapGestureRecognizer(target: self, action: #selector(FCViewController.tapFunction))
self.cell.addGestureRecognizer(tap)
if self.cell.userName.text == Auth.auth().currentUser?.displayName {
self.cell.userName.textAlignment = .right
self.cell.userMessage.textAlignment = .right
self.cell.hiddenLabel.textAlignment = .right
} else {
self.cell.userName.textAlignment = .left
self.cell.userMessage.textAlignment = .left
self.cell.hiddenLabel.textAlignment = .left
}
self.cell.setNeedsLayout()
} else {
let text = message[Constants.MessageFields.text] ?? ""
self.cell.userName.text = name
self.cell.userMessage.text = text
self.cell.hiddenLabel.isHidden = true
self.cell.hiddenLabel.isUserInteractionEnabled = false
if self.cell.userName.text == Auth.auth().currentUser?.displayName {
self.cell.userName.textAlignment = .right
self.cell.userMessage.textAlignment = .right
} else {
self.cell.userName.textAlignment = .left
self.cell.userMessage.textAlignment = .left
}
}
return cell
}
#objc func tapFunction(sender:UITapGestureRecognizer) {
var url = URL(string: self.cell.userMessage.text!)!
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
But when clicking on any cell, only the link that is contained in the last cell always opens. No matter which cell I click.
How to make it so that when you click on a cell, the specific link that is contained in a particular userMessage opens?
You have to set Tag to each Cell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "ClientCell"
self.cell = self.tableView.dequeueReusableCell(withIdentifier: identifier) as? customChatCell
...
self.cell.tag = indexPath.row // SETTING TAG TO EACH CELL. 0,1,2...
...
let tap = UITapGestureRecognizer(target: self, action: #selector(FCViewController.tapFunction))
self.cell.addGestureRecognizer(tap)
...
}
#objc func tapFunction(sender:UITapGestureRecognizer) {
print("CELL_TAG_NUMBER. ", sender.view.tag)
let messageSnapshot = self.messages[sender.view.tag] THIS WILL GIVE, 0,1,2...
print("Exact_URL. ", messageSnapshot)
//var url = URL(string: self.cell.userMessage.text!)!
//UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
No need to add a tap gesture to the cell, a UITableView has a delegate method for when a cell is selected. if you use that then you also don't need to track your cells using a tag(Not GOOD!), instead you will use the direct indexPath of that specific cell.
implement this method
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Now you have your selected cell
let messageSnapshot = self.messages[indexPath.row]
print(messageSnapshot)
}

Segmented control and table view issue swift

so I am creating a table view with segmented control. at first - I didn't do segmented control but modified some code to at it. When I add numbers to my array, it is not actually populating the cells of the table view. In the table view every time I click a button that appends an amount to the array, the lines increase, but no data is shown. So I know it is working, but it is just not actually displaying the numbers in the table view cells. is there a specific reason why my code will not populate the views?
class SpendingHistoryViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var savedInformation = false
var newDefault : Double?
var selectedSegment = 1
let array2 = ["1","2","3"]
let userDefaults = UserDefaults.standard
#IBOutlet weak var tableViewSpending: UITableView!
#IBOutlet weak var youSpentLabel: UILabel!
#IBOutlet weak var youSavedLabel: UILabel!
#IBOutlet weak var segmentControl: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
self.tableViewSpending.delegate = self
self.tableViewSpending.dataSource = self
//GETS user defaults that was set in ADDSUBTRACTMONEY VC - and then sets the default to Array- then gets total
let moneySpentArray = UserDefaults.standard.array(forKey: "moneySpent") as? [Double] ?? [Double]()
AddSubtractMoneyController.moneySpentArray = moneySpentArray
let arraySum = moneySpentArray.reduce(0.0, +)
youSpentLabel.text = String(format: "%.2f", arraySum)
}
#IBAction func spentSavedAction(_ sender: UISegmentedControl) {
if sender.selectedSegmentIndex == 0 {
selectedSegment = 1
}
else {
selectedSegment = 2
}
self.tableViewSpending.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if selectedSegment == 1 {
return(AddSubtractMoneyController.moneySpentArray.count)
}
else {
return array2.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "cell")
//let cell2 = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "cell2")
let cell2 = tableViewSpending.dequeueReusableCell(withIdentifier: "cell2")! as UITableViewCell
let cell = tableViewSpending.dequeueReusableCell(withIdentifier: "cell")! as UITableViewCell
cell.textLabel?.text = String(format: "%.2f", AddSubtractMoneyController.moneySpentArray[indexPath.row])
cell2.textLabel?.text = array2[indexPath.row]
self.tableViewSpending.reloadData()
if selectedSegment == 1 {
return cell
}
else {
return cell2
}
}
}

Custom UITableViewCell with optionals

I'm creating custom cell for my tableView. I made a swift file:
import Foundation
import UIKit
class CellForPost: UITableViewCell {
#IBOutlet weak var postLikes: UILabel!
#IBOutlet weak var postText: UILabel!
#IBOutlet weak var postDate: UILabel!
#IBOutlet weak var postPhoto: UIImageView!
}
and implemented it in delegate method:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("postCell", forIndexPath: indexPath) as! CellForPost
cell.postPhoto.image = UIImage.init(data: (posts[indexPath.item].postPhoto)! as NSData)
cell.postText.text = posts[indexPath.item].postText
cell.postLikes.text = String(posts[indexPath.item].postLikes!)
cell.postDate.text = timestampToDate(posts[indexPath.item].postDate!)
return cell
}
everything works great when post has full content, but when for example there is no photo(which is optional in post struct) it crashes with message
fatal error: unexpectedly found nil while unwrapping an Optional value
I understand this message, so I tried to make
#IBOutlet weak var postPhoto: UIImageView?
like an optional value, but it doesn't work, 'cos compiler wants me to unwrap values before inserting to cell.
P.S. If it's possible to give a short advice about deleting imageView at all when it is nil and resize row height to fit.
You don't need touch your outlets declarations, you need check for nil in cellForRowAtIndexPath method (and setup nil as value thought)
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("postCell", forIndexPath: indexPath) as! CellForPost
var image: UIImage? = nil
if let imageData = posts[indexPath.item].postPhoto {
image = UIImage.init(data: imageData)
}
cell.postPhoto.image = image
cell.postText.text = posts[indexPath.item].postText
var postLikes: String? = nil
if let likesData = posts[indexPath.item].postLikes {
postLikes = String(likesData)
}
cell.postLikes.text = postLikes
var postDate: String? = nil
if let dateData = posts[indexPath.item].postDate {
postDate = timestampToDate(dateData)
}
cell.postDate.text = postDate
return cell
}
You need to check that your object has value before assigning it to the IBOutelet. You need to change your cellForRowAtIndexPath something like this
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("postCell", forIndexPath: indexPath) as! CellForPost
if let data = posts[indexPath.item].postPhoto) as? NSData {
cell.postPhoto.image = UIImage.init(data: data)
}
else {
cell.postPhoto.image = nil
}
if let postText = posts[indexPath.item].postText) as? String {
cell.postText.text = postText
}
else {
cell.postText.text = ""
}
if let postLikes = posts[indexPath.item].postLikes) as? Int {
cell.postLikes.text = String(postLikes)
}
else {
cell.postLikes.text = ""
}
if let postDate = posts[indexPath.item].postDate) as? NSDate {
cell.postDate.text = timestampToDate(postDate)
}
else {
cell.postDate.text = ""
}
return cell
}
Hope this will help you.