I have one tableview containing data that is fed from an Array with 14 entries. I have added a search bar where I can filter this data and this works fine, but when I try to send the filtered data to a second table view, the data that is sent is the count of my filtered data. So if I search something starting with G(13th and 14th place in my array), that brings back two results which is fine but when I choose the first entry off my filtered data(the two results), the data that is passed to the second Table View is the [0] entry(A) of my initial TVC and not the 13th(G12).So depending on the filtered data I always get the first few entries of my initial array.
struct ImageForManuf {
let image: UIImage
let name: String
init(nameInit: String) {
self.name = nameInit
image = UIImage(named: nameInit)!
}
}
struct SecondTable {
var secondTitle : [String]
var pic : [UIImage?]
}
//for search bar
#IBOutlet weak var searchBar: UISearchBar!
var filteredData: [ImageForManuf]!
var searching = false
var firstArray = [ImageForManuf]()
//we are adding elements on the SecondTable structure
var secondArray = [SecondTable]()
override func viewDidLoad() {
super.viewDidLoad()
//for search bar
searchBar.delegate = self
filteredData = firstArray
firstArray = [ "A","A2","B3","B4","B5","C6","C7","D8","E9","F10","F11","G12","G13","H14",
].map(ImageForManuf.init(nameInit:))
filteredData = firstArray
secondArray = [SecondTable(secondTitle: ["A1"],
pic: [UIImage(named: "A1")]),
SecondTable(secondTitle: ["B1","B2","B3"],
pic: [UIImage(named: "B1"),UIImage(named: "B2"),UIImage(named: "B3")]),
SecondTable(secondTitle: ["C1","C2"],
pic: [UIImage(named: "C1"),pic: [UIImage(named: "C2")]),
SecondTable(secondTitle: ["D1"],
pic: [UIImage(named: "D1"),
SecondTable(secondTitle: ["E1"],
pic: [UIImage(named: "E1"),
SecondTable(secondTitle: ["F1","F2"],
pic: [UIImage(named: "F1"),pic: [UIImage(named: "F2")]),
SecondTable(secondTitle: ["G1","G2"],
pic: [UIImage(named: "G1"),pic: [UIImage(named: "G2")]),
SecondTable(secondTitle: ["H1"],
pic: [UIImage(named: "H1")
]
This is my TV configuration
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if searching
{
return filteredData.count
}else{
return firstArray.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let Cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableTableViewCell
Cell.textLabel?.textColor = .systemBlue
Cell.textLabel?.font = .boldSystemFont(ofSize: 20)
if searching
{
Cell.textLabel?.text = filteredData[indexPath.row].name
Cell.imageManuf?.image = filteredData[indexPath.row].image
}else{
Cell.textLabel?.text = firstArray[indexPath.row].name
Cell.imageManuf?.image = filteredData[indexPath.row].image
}
return Cell
}
//we need to create a variable that can hold the destination view controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if searching {
let row = self.tableView?.indexPathForSelectedRow?.row ?? 0
let destViewController = segue.destination as! SecondTableViewController var secondTableArray : SecondTable
secondTableArray = secondArray[row]
destViewController.secondTableVcArray = secondTableArray.secondTitle
destViewController.detailVcArray = thirdArray[row]
}else{
let row = self.tableView?.indexPathForSelectedRow?.row ?? 0
let destViewController = segue.destination as! SecondTableViewController
var secondTableArray : SecondTable
secondTableArray = secondArray[row]
secondTableArray created in SecondTableViewController
destViewController.secondTableVcArray = secondTableArray.secondTitle
destViewController.detailVcArray = thirdArray[row]
}
}
Lastly this is my searchBar configuration
//Mark: Search Bar config
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredData = firstArray.filter({$0.name.lowercased().prefix(searchText.count) == searchText.lowercased()})
searching = true
//to reload the data
self.tableView.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searching = false
searchBar.text = ""
tableView.reloadData()
}
This is the second TVC:
class SecondTableViewController: UITableViewController {
//we are loading the secondArray via the secondTableVcArray
var secondTableVcArray = [String]()
//we are loading the thirdArray via the detailVcArray
var detailVcArray : [DetailView]?
override func viewDidLoad() {
super.viewDidLoad()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return secondTableVcArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "secondCell", for: indexPath) as! SecondTableViewCell
cell.textLabel?.textColor = .white
cell.textLabel?.font = .boldSystemFont(ofSize: 20)
//we load the data of the secondTableVcArray to our cell
cell.textLabel?.text = secondTableVcArray[indexPath.row]
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let row = self.tableView?.indexPathForSelectedRow?.row else {
print("Error: problem with second table view or selection")
return
}
guard let dest = segue.destination as? DetailsViewController else {
print("Error: couldn't find expected DetailsViewController")
return
}
guard let details = detailVcArray else {
print("Error: data not set for SecondTableViewController")
return
}
dest.displayDetail = details[row]
}
}
Can somebody help with the row element of my prepareforSeque method and advice the syntax on how to get the indexPath of my firstArray as #vadian suggested? The second condition works fine(not searching) but I don't seem to be able to pull the right item from my DataSource in the searching condition.
Related
In each row of a tableview there is a label and button. The label displays a quotes from an array. Users can tap the button to save the quote. Right now it works fine with UserDefaults, but I want to also save the information to Firebase. I can't seem to figure out how to get the quote to save to Firebase based on the heart/row that was tapped. I thought I could use IdexPath, but I can't seem to get it right. I'd like to save each quote as a unique value in Firebase and be able to delete it when the button is tapped again. However, I'm not so familiar with firebase.
I thought I could use IdexPath to determine which row was selected and then grab the label in that row to send to Firebase, but I can't seem to get it right. Instead I got an error preventing the code from running "Instance member 'row' cannot be used on type 'IndexPath'; did you mean to use a value of this type instead?"
import UIKit
import FirebaseDatabase
import FirebaseAuth
class QuotesMainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var quotesTableView: UITableView!
struct Quote: Codable {
var label: String
var like: Bool = false // a priori, false
}
var quoteList: [Quote] = []
override func viewDidLoad() {
super.viewDidLoad()
quotesTableView.delegate = self
quotesTableView.dataSource = self
let defaults = UserDefaults.standard
if let data = defaults.data(forKey: "QuoteListKey") {
if let array = try? PropertyListDecoder().decode([Quote].self, from: data) {
quoteList = array
}
} else {
quoteList = [Quote(label: "Quote1"), Quote(label: "Quote2"), Quote(label: "Quote3")]
}
}
#IBAction func likeTapped(_ sender: UIButton) {
var ref: DatabaseReference?
ref = Database.database().reference()
quoteList[sender.tag].like.toggle() // update the dataSource ; sender.tag gives the row in the array
if quoteList[sender.tag].like {
sender.setImage(UIImage(named: "GreenHeart"), for: .normal) // You can change here or ask for a reloadData()
guard let user = Auth.auth().currentUser?.uid else { return }
ref!.child("users").child(Auth.auth().currentUser!.uid).child("Quotes").setValue(quoteList[IndexPath.row].label)
if let data = try? PropertyListEncoder().encode(quoteList) {
UserDefaults.standard.set(data, forKey: "QuoteListKey")
}
} else {
sender.setImage(UIImage(named: "blankHeart"), for: .normal)
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return quoteList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = quotesTableView.dequeueReusableCell(withIdentifier: "cell") as! QuotesTableViewCell
cell.quoteLabel.text = quoteList[indexPath.row].label
cell.likeButton.tag = indexPath.row // Use tag to reference the cell, not to set true / false
cell.likeButton.setImage(UIImage(named: quoteList[indexPath.row].like ? "GreenHeart" : "blankHeart"), for: .normal)
return cell
}
}
Essentially I am adding or removing the cell values to an array that is built up based on which cells are selected/deselected.
I need my array to look like this:
[{"value":"One","id":"1"},{"value":"two","id":"4"}]
Currently it looks like this:
["One", "two"]
Currently the array does not include the struct titles (id and value) and does not include the curly brackets. The values for the id and values are coming from the array populating the tableview. How can this be done?
My code so far:
var structure = [Struct]()
var selectionArray: [String] = []
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
cell!.accessoryType = .checkmark
let item = structure[indexPath.row]
let cellvalue = item.value
selectionArray.append(cellvalue)
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
cell!.accessoryType = .none
let item = structure[indexPath.row]
let cellvalue = item.value
if let index = selectionArray.index(of: cellvalue) {
selectionArray.remove(at: index)
}
}
struct Struct: Decodable {
let id: Int
let value: String
}
You don't need to store the selectionArray array as a property at all. You can use indexPathsForSelectedRows to get all the selected rows. So you can generate the array whenever you need it.
let selectedIndexPaths = tableView.indexPathsForSelectedRows ?? []
// this contains the selected structs
let selectedStructs = selectedIndexPaths.map { structure[$0.row] }
Now you can encode it to JSON:
// remember to conform Struct to Codable!
let encoder = JSONEncoder()
do {
let jsonData = try encoder.encode(selectedStructs)
// if you want to see the encoded JSON string:
print(String(data: jsonData, encoding: .utf8)!)
} catch {
print(error)
}
In Add_EditAddressViewController i need to show all added address in tableview, for that i have created one ARRAY and appending values to array in NewZoomAddressViewController to show in tableview but all the time i am getting single row in table view.. so here how to add value to array dynamically without replacing into oldvalue in Add_EditAddressViewController
and navigation is:
Add_EditAddressViewController: butnTap -> ProfileVC: btnTap -> NewZoomAddressViewController: btnTap -> Add_EditAddressViewController
here each time when i come to NewZoomAddressViewController need to append \(self.sublocalityName!) \(localityName!) \(self.zipName!) to addressArray to show in tableview of Add_EditAddressViewController
Note: here i have added this question related code in github: https://github.com/SwiftSamples/AddressBug here in profileVC you need to tap on map or continue Button then it navigates to NewZoomAddressViewController
class Add_EditAddressViewController: UIViewController,DataEnteredDelegate {
#IBOutlet weak var addeditTableview: UITableView!
var addressArray = [String]()
var city: String?
var pincode: String?
var locality: String?
override func viewDidLoad() {
super.viewDidLoad()
addeditTableview.register(UINib(nibName: "EditAddressTableViewCell", bundle: nil), forCellReuseIdentifier: "EditAddressTableViewCell")
print("zoooom valuew \(pincode)")
addeditTableview.reloadData()
}
}
extension Add_EditAddressViewController : UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return addressArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: EditAddressTableViewCell = tableView.dequeueReusableCell(withIdentifier: "EditAddressTableViewCell") as! EditAddressTableViewCell
cell.editButton.addTarget(self, action: #selector(editbuttonClicked(sender:)), for: .touchUpInside)
cell.nameHeader.text = "header"
cell.addressLabel.text = addressArray[indexPath.row]
return cell
}
}
NewZoomAddressViewController code:
class NewZoomAddressViewController: UIViewController {
weak var delegate: DataEnteredDelegate? = nil
var addressModel: ProfileModelUserAddress?
var addressArray = [String]()
var zipName: String?
var localityName: String?
var sublocalityName: String?
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var addressLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
print("in Zoom map VC")
mapView.delegate = self
addressLabel.text = "\(self.sublocalityName!) \(localityName!) \(self.zipName!)"
}
#IBAction func confirmBtn(_ sender: Any) {
let viewController = storyboard?.instantiateViewController(withIdentifier: "Add_EditAddressViewController") as! Add_EditAddressViewController
addressArray.append("\(sublocalityName ?? "") \(zipName ?? "") \(localityName ?? "")")
viewController.addressArray = addressArray
print("total address array all rows \(viewController.addressArray)")
navigationController?.pushViewController(viewController, animated: true)
}
}
please try to help to display all added address in tableview. i got stuck here from long time.
In your NewZoomAddressViewController replace confirm button action with
#IBAction func confirmBtn(_ sender: Any) {
for controller in navigationController?.viewControllers ?? [] {
if let listController = controller as? Add_EditAddressViewController {
let string = "\(sublocalityName ?? "") \(zipName ?? "") \(localityName ?? "")"
listController.addressArray.append(string)
navigationController?.popToViewController(controller, animated: true)
return
}
}
}
In Add_EditAddressViewController reload TableView on viewWillAppear
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.navigationBar.isHidden=true
addeditTableview.reloadData()
}
Well what you need to do is to have address array in your profile view as well to pass it to other controller.. so your code becomes
First you will have array in profile like this
class ProfileAddressViewController: UIViewController, CLLocationManagerDelegate, UISearchBarDelegate, DataEnteredDelegate {
var addressArray = [String]()
}
Then when you call NewZoomAddressViewController you pass that array to them like this
#objc func triggerTouchAction(_ sender: UITapGestureRecognizer) {
print("Please Help!")
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "NewZoomAddressViewController") as! NewZoomAddressViewController
viewController.delegate = self
viewController.zipName = self.pincodeField.text
viewController.sublocalityName = self.colonyField.text
viewController.localityName = self.cityField.text
viewController.addressArray = addressArray
self.navigationController?.pushViewController(viewController, animated: true);
}
And in your Add_EditAddressViewController where you call profile.. assign array to profile
#objc func editbuttonClicked(sender: UIButton) {
print("in button")
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "ProfileAddressViewController") as! ProfileAddressViewController
viewController.addressArray = addressArray
self.navigationController?.pushViewController(viewController, animated: true)
}
I have an Array of strings which is populating a collection view and that works well. The issue is I want to append that array with Strings that is saved in user defaults from user input textfield. I am getting the UserDefault data, The issue is it is not showing up in seperate collection view cell. it is getting attached at the end of each string in the current cells. Thanks in advance, any help would be appreciated.
This is what I tried so far:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
let defaults = UserDefaults.standard
let value = defaults.string(forKey: "Gratitude")
print(value!)
//Array that I am trying to append with userdefault data
gratitudeArray.append(value!)
// Configure the cell
cell.cellLabel.text = gratitudeArray[indexPath.row]
return cell
}
// I am geeting user input from alert and saving in userdefaults like this:
func presentAlert() {
let alertController = UIAlertController(title: "", message: "Create your own Gratitude:", preferredStyle: .alert)
let confirmAction = UIAlertAction(title: "Save", style: .default) { (_) in
if let field = alertController.textFields?[0] {
// store data
UserDefaults.standard.set(field.text, forKey: "Gratitude")
UserDefaults.standard.synchronize()
} else {
print()
}
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (_) in }
alertController.addTextField { (textField) in
//print(textField.text!)
//textField.placeholder = ""
}
alertController.addAction(confirmAction)
alertController.addAction(cancelAction)
self.present(alertController, animated: true, completion: nil)
}
Store String in ViewDidLoad() like below:
var strValue: String = ""
override func viewDidLoad() {
super.viewDidLoad()
let defaults = UserDefaults.standard
strValue= defaults.string(forKey: "Gratitude")
}
and display in cellForItemAt like this:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
cell.cellLabel.text = gratitudeArray[indexPath.row] + " " + strValue
return cell
}
If I understand your problem correctly, you want number of cells to increase by one (the value you extract out of UserDefaults). For that, you should append it some place which is outside collection view's data source methods (like viewDidLoad()) and then reload your collection view.
override func viewDidLoad() {
super.viewDidLoad()
let defaults = UserDefaults.standard
strValue = defaults.string(forKey: "Gratitude")
gratitudeArray.append(strValue)
self.collectionView.reloadData()
}
I solved the issue by creating an array in my alert controller to hold user input and then save that array to user defaults.
func presentAlert() {
let alertController = UIAlertController(title: "", message: "Create your own Gratitude:", preferredStyle: .alert)
let confirmAction = UIAlertAction(title: "Save", style: .default) { (_) in
if let field = alertController.textFields {
let textFieldArray = field as [UITextField]
let text = textFieldArray[0].text
let key = "Gratitude"
var myArray = UserDefaults.standard.array(forKey: key) ?? []
myArray.append(String(describing: text!))
UserDefaults.standard.set(myArray, forKey: key)
print(myArray)
} else {
print()
}
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (_) in }
alertController.addTextField { (textField) in
}
alertController.addAction(confirmAction)
alertController.addAction(cancelAction)
self.present(alertController, animated: true, completion: nil)
}
I have a tablecell to work with and I can populate it when I use a written array (like values = [""]) so I know it is working.
But I am using json with swiftyjson to get my info in my table, which is part of a right slideout page I made with mmdrawer. When I println the json output I get all the info I need, but it is not being taken to the table or other variables/arrays.
How do I make this code work?
import UIKit
class RightSideViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var songname = [String]()
var menuImage = [String]()
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBarHidden = true
}
override func prefersStatusBarHidden() -> Bool {
return true
}
override func viewDidLoad() {
super.viewDidLoad()
getmusiclist()
}
func getmusiclist(){
let search:NSString = "music" as NSString
let url = NSURL(string:"http://xxxxxx/music-manager.php")
let cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData
var request = NSMutableURLRequest(URL: url!, cachePolicy: cachePolicy, timeoutInterval: 2.0)
request.HTTPMethod = "POST"
// set Content-Type in HTTP header
let boundaryConstant = "----------V2ymHFg03esomerandomstuffhbqgZCaKO6jy";
let contentType = "multipart/form-data; boundary=" + boundaryConstant
NSURLProtocol.setProperty(contentType, forKey: "Content-Type", inRequest: request)
// set data
var dataString = "search=\(search)"
let requestBodyData = (dataString as NSString).dataUsingEncoding(NSUTF8StringEncoding)
request.HTTPBody = requestBodyData
// set content length
//NSURLProtocol.setProperty(requestBodyData.length, forKey: "Content-Length", inRequest: request)
var response: NSURLResponse? = nil
var error: NSError? = nil
let dataReply = NSURLConnection.sendSynchronousRequest(request, returningResponse:&response, error:&error)
var results = NSJSONSerialization.JSONObjectWithData(dataReply!, options: nil, error: &error) as! NSDictionary
var jsonOutput = JSON(data: dataReply!)
println(jsonOutput)
let musicListArray = jsonOutput.arrayValue
dispatch_async(dispatch_get_main_queue(), {
for playlist in musicListArray
{
let trackname = playlist["track_name"].stringValue
println("trackName: \(trackname)")
self.songname.append(trackname)
}
/* dispatch_async(dispatch_get_main_queue(),{
self.tableView.reloadData()
})*/
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return songname.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
var mycell = tableView.dequeueReusableCellWithIdentifier("playerCell", forIndexPath: indexPath) as! MusicTableViewCell
mycell.artistLabel?.text = songname[indexPath.row]
return mycell
}
}
Eventually I would also like to take the name, genre and streaming url and have avplayer play it - will that be something I can add to this code?
the problem with in the part that i forgot to add the uitableview outlet in the uiviewcontroller. I have added it and now the error is gone. I still have to figure out why i am not getting anything but it seems that i am nu getting the data from the jsonOutput.arrayvalue.