I have two prototype cells. One appears if messagesArray[indexPath.row] value is "", the other if that value contains characters. One of the cells' row height is greater than the second and contains additional variables. They're both hooked up to their own cell classes and have their own cell identifiers. I want them both to coexist in the same tableview, under one section, but I'm struggling to achieve that. I keep getting fatal error: Array index out of range. The array value is being populated from an async DB request, which could be the explanation.
What am I doing wrong/how can I do this successfully?
var messagesArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
var query = PFQuery(className: "Class")
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if error == nil {
if let objects = objects {
for object in objects {
if let message = object["message"] as? String {
self.messagesArray.append(message)
}
}
}
} else {
println(error)
}
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if self.messagesArray[indexPath.row] == "" {
var cell = tableView.dequeueReusableCellWithIdentifier("cellOne", forIndexPath: indexPath) as! CellOne
return cell
} else {
var cell = tableView.dequeueReusableCellWithIdentifier("cellTwo", forIndexPath: indexPath) as! CellTwo
return cell
}
}
EDIT: If messagesArray[indexPath.row] == some value other than "" (there's actually a message), then that first cell in which the message is displayed in will be larger than the second cell and be displayed by a UILabel that doesn't exist in second cell.
if you are using dynamic cells you must not have two prototype cells on your tableView, only one is needed to get the job done.
var messagesArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
var query = PFQuery(className: "Class")
query.findObjectsInBackgroundWithBlock { (objects:[AnyObject]?, error:NSError) -> Void in
if error == nil
{
if let objects = objects as? [PFObject]
{
for object in objects
{
var message = object["message"]
self.messagesArray.append(message)
self.tableView.reloadData()
}
}
}
else
{
println(error)
}
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cellOne", forIndexPath: indexPath) as! CellOne
var data = self.messagesArray[indexPath.row]
cell.textlabel.text = data
}
Related
I have been trying to create an app that gets a website data through a function ( func obtainData) and display some of the data on a tableView.
I have figured out the part on how to get the data from the website then make it as an array so I can use indexpath.row but I have not able to find out the way to pass on the data I'm getting to display it on a tableView.
Any ideas!
Thanks
Below is the code I wrote.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var recommendation = ""
var recommendationArray = ""
var delExtraTextArray = [String]()
var urlRecommendationArrayStart = [String]()
var urlRecommendationArrayEnd = [String]()
var RecommendationStart = [String]()
var RecommendationEnd = [String]()
// need the var below to make the recommendations as an array to be used in a table view as indexpath.row
var cellNumber = [String]()
var cellTitle = [String]()
var cellDetails = [String]()
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
obtainData()
tableView.delegate = self
tableView.dataSource = self
}
func obtainData () {
var url = NSURL (string: "http://www.choosingwisely.org/societies/american-college-of-obstetricians-and-gynecologists")
if url != nil {
let task = NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler: { (data, response, error) -> Void in
if error == nil {
// to get all of the url content and covert them into string
var urlContent = NSString(data: data!, encoding: NSUTF8StringEncoding) as NSString!
// to get to a specific contect seperated by a string
self.urlRecommendationArrayStart = (urlContent?.componentsSeparatedByString("<ol class=\"society-ol\">"))!
if self.urlRecommendationArrayStart.count > 0 {
self.urlRecommendationArrayEnd = self.urlRecommendationArrayStart[1].componentsSeparatedByString("</ol>")
// print(self.urlRecommendationArrayEnd)
// to check if there is any extra not needed text at the end of the recommnedations in the source page
self.delExtraTextArray = self.urlRecommendationArrayEnd[0].componentsSeparatedByString("<p><a")
if self.delExtraTextArray.count > 0 {
self.recommendationArray self.delExtraTextArray[0] as! String
self.obtainRecommendationTitle()
} else {
self.recommendationArray = self.urlRecommendationArrayEnd[0] as! String
self.obtainRecommendationTitle()
// print("method 2 worked")
}
} else {
self.textView.text = "Sorry, couldn't get the recommendation at this point. Please make sure to download the updated version of the app"
}
} else {
self.textView.text = "Please check connection then try again"
}
})
task.resume()
} else {
self.textView.text = "Please check connection then try again"
}
}
// to get the title of each recommendation
func obtainRecommendationTitle() -> Array<String> {
for var i = 2; i < urlRecommendationArrayEnd[0].componentsSeparatedByString("<p>").count - delExtraTextArray.count ; i = i + 4 {
self.RecommendationStart = self.delExtraTextArray[0].componentsSeparatedByString("<p>")
self.RecommendationEnd = RecommendationStart[i].componentsSeparatedByString("</p>")
self.recommendationArray = self.RecommendationEnd[0] as! String
self.cellTitle.append(recommendationArray)
}
return cellTitle
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellTitle.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cell")
cell.textLabel?.text = cellTitle [indexPath.row]
return cell
}
You would pass it via the cellForRowAtIndexPath delegate method. This question is too open ended for a firm answer, but following along any half-decent online UITableView tutorial should do the trick.
A quick glance at this one appears to hit the basics: https://www.weheartswift.com/how-to-make-a-simple-table-view-with-ios-8-and-swift/
I think what you need is to configure the contents of the table view cell with the data you want. Based on this assumption, you can use something like this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCellWithIdentifier("PUT_YOUR_CELL_IDENTIFIER_HERE") as? UITableViewCell {
let stuff = yourArray[indexPath.row]
stuff.some_property_you_want = the_value_you_want
return cell
} else {
return UITableViewCell()
}
}
If you show some code or explain your problem a little better, you will get a better support from the people here on Stack Overflow.
EDIT (based on your edit):
Are you using a normal cell?
The cell has the text field to put the string you want?
Did you define the cell's identifier, "cell", in the storyboard?
Did you connect the tableView outlet to the tableView itself?
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCellWithIdentifier("cell") as? UITableViewCell {
print("ENTERED HERE!")
let myCellText = cellTitle[indexPath.row]
//what is the result of this print?
print("TEXT TO ADD: \(myCellText)")
//(...)
cell.textLabel?.text = myCellText
return cell
} else {
return UITableViewCell()
}
}
What is the result of these prints?
I have a TableViewController below that I am trying to populate with a query request from Parse. The idea is that the call (which I have checked and is returning the necessary information) then fills the arrays, which I then use to populate the TableViewCells. These cells also have a custom class ('TableViewCell').
For some reason, 'self.tableView.reloadData()' is definitely causing the crash. When I remove it, it doesn't crash but the tableviewcells don't update with the parse information. Any ideas?
import UIKit
import Parse
class AuctionViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(TableViewCell.self, forCellReuseIdentifier: "Cell")
}
var capArray = [String]()
var imageDic = [String: [PFFile]]()
var priceArray = [Int]()
override func viewDidAppear(animated: Bool) {
capArray.removeAll(keepCapacity: true)
imageDic.removeAll(keepCapacity: true)
priceArray.removeAll(keepCapacity: true)
let query = PFQuery(className: "SellerObject")
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if let objects = objects {
for o in objects {
if o.objectForKey("caption") != nil && o.objectForKey("imageFile") != nil && o.objectForKey("price") != nil {
let cap = o.objectForKey("caption") as? String
self.capArray.append(cap!)
let imdic = o.objectForKey("imageFile") as? [PFFile]
self.imageDic[cap!] = imdic
let price = o.objectForKey("price") as? String
let priceInt = Int(price!)
self.priceArray.append(priceInt!)
print(self.capArray)
print(self.imageDic)
print(self.priceArray)
}
self.tableView.reloadData()
}
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return capArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! TableViewCell
cell.captionLabel.text = self.capArray[indexPath.row]
return cell
}
First of all, you are not checking cap, imdic, price by type. And reloading tableView many times in cycle. Replace
for o in objects {
if o.objectForKey("caption") != nil && o.objectForKey("imageFile") != nil && o.objectForKey("price") != nil {
let cap = o.objectForKey("caption") as? String
self.capArray.append(cap!)
let imdic = o.objectForKey("imageFile") as? [PFFile]
self.imageDic[cap!] = imdic
let price = o.objectForKey("price") as? String
let priceInt = Int(price!)
self.priceArray.append(priceInt!)
print(self.capArray)
print(self.imageDic)
print(self.priceArray)
}
self.tableView.reloadData()
}
with
for o in objects {
if let cap = o.objectForKey("caption") as? String,
let imdic = o.objectForKey("imageFile") as? [PFFile],
let priceInt = (o.objectForKey("price") as? String).flatMap({ Int($0))}) {
self.capArray.append(cap)
self.imageDic[cap] = imdic
self.priceArray.append(priceInt)
print(self.capArray)
print(self.imageDic)
print(self.priceArray)
}
}
self.tableView.reloadData()
Also, don't dequeue cell that way. Replace
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! TableViewCell
cell.captionLabel.text = self.capArray[indexPath.row]
return cell
}
with
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as? TableViewCell else {
assertionFailure("cell for index-path:\(indexPath) not found")
return UITableViewCell()
}
cell.captionLabel.text = self.capArray[indexPath.row]
return cell
}
But I think that problem could always be inside TableViewCell class🤔 For example, captionLabel could be nil.
I have a 2D Array which I want to populate in UITableView Custom Cell in a specific pattern.
//Retrieved from Parse backend
var myArray = [["Name1", "Age1"],["Name2", "Age2"],["Name3", "Age3"]]
//What I need is:
nameArray = ["Name1", "Name2", "Name3"]
ageArray = ["Age1", "Age2", "Age3]
So that I can use indexPath to fill the Name data in the custom UITableView cell For Ex: nameArray[indexPath.row]
I tried using the for in loop,
var nameArray = NSMutableArray()
var ageArray = NSMutableArray()
//Inside CellForRowAtIndexPath
for data in myArray {
self.nameArray.addObject(data[0])
self.ageArray.addObject(data[1])
}
cell.nameLabel.text = "\(nameArray[indexPath.row])"
cell.ageLabel.text = "\(ageArray[indexPath.row])"
But I am getting repetitive name and age label filled with Name1 and Age1 in both the cell. Does anyone know whats wrong in this?
Is there a better way to reload this data as needed?
// UPDATED FULL WORKING CODE Thanks to #l00phole who helped me solve the problem
class NewViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
var data = [[String]]()
var cost = Double()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
uploadData()
}
func uploadData() {
let query = PFQuery(className:"Booking")
query.getObjectInBackgroundWithId("X0aRnKMAM2") {
(orders: PFObject?, error: NSError?) -> Void in
if error == nil && orders != nil {
self.data = (orders?.objectForKey("orderDetails"))! as! [[String]]
//[["Vicky","21"],["Luke", "18"],["7253.58"]]
//*****Removing the last element as it is not needed in the tableView data
let count = self.data.count - 1
let c = self.data.removeAtIndex(count)
cost = Double(c[0])!
//******
} else {
print(error)
}
self.reloadTableData()
}
}
func reloadTableData()
{
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
return
})
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return data.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:NewTableViewCell = self.tableView!.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! NewTableViewCell
// Configure the cell...
cell.nameLabel.text = "\(data[indexPath.row][0])"
cell.ageLabel.text = "\(data[indexPath.row][1])"
return cell
}
You are adding to the nameArray and ageArray every time cellForRowAtIndexPath is called and you are not clearing them first. This seems inefficient and you should only populate those arrays when the input data changes, not when generating the cells.
I don't even think you need those arrays, as you could just do:
cell.nameLabel.text = "\(data[indexPath.row][0])"
cell.ageLabel.text = "\(data[indexPath.row][1])"
You don't have to create separate array for name and age, you can use the existing myArray as below
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:NewTableViewCell = self.tableView!.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! NewTableViewCell
// Configure the cell...
let dataArray = myArray[indexPath.row]
cell.nameLabel.text = "\(dataArray[0])"
cell.ageLabel.text = "\(dataArray[1])"
return cell
}
}
I’m trying to get rows from a UITableView that have been marked by the user and then save them as Parse relations to the current user.
Here is the code for the IBAction (Save button) and the function:
#IBAction func saveButton(sender: AnyObject) {
getCheckmarkedCells(tableView: UITableView,indexPath: NSIndexPath)
}
func getCheckmarkedCells(tableView: UITableView, indexPath: NSIndexPath) {
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
if cell.accessoryType == .Checkmark {
let checkedCourse = cell.textLabel?.text
var query = PFQuery(className: "Courses")
query.whereKey("coursename", equalTo: checkedCourse!)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) scores.")
// Do something with the found objects
}
}
} else {
// Log details of the failure
println("Error")
}
}
}
}
I get an error in line 2:
Cannot invoke 'getCheckmarkedCells' with an argument list of type '(tableView: UITableView.Type, indexPath: NSIndexPath.Type)‘
What am I doing wrong?
EDIT:
// Configure cells for Checkmarks
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
if cell.accessoryType == .Checkmark
{
cell.accessoryType = .None
}
else
{
cell.accessoryType = .Checkmark
}
}
}
EDIT2:
import UIKit
import Parse
import ParseUI
class KurseTableViewController: PFQueryTableViewController {
// Initialise the PFQueryTable tableview
override init(style: UITableViewStyle, className: String!) {
super.init(style: style, className: className)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// Configure the PFQueryTableView
self.parseClassName = "Courses"
self.textKey = "coursename"
self.pullToRefreshEnabled = true
self.paginationEnabled = false
}
// Define the query that will provide the data for the table view
override func queryForTable() -> PFQuery {
var query = PFQuery(className: "Courses")
query.orderByDescending("coursename")
return query
}
var selectedRows = NSMutableIndexSet()
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
if (self.selectedRows.containsIndex(indexPath.row)) {
cell.accessoryType = .None
self.selectedRows.removeIndex(indexPath.row)
} else {
cell.accessoryType = .Checkmark
self.selectedRows.addIndex(indexPath.row);
}
}
}
#IBAction func saveButton(sender: AnyObject) {
//getCheckmarkedCells(tableView: UITableView indexPath: NSIndexPath)
}
/*func getCheckmarkedCells(tableView: UITableView, indexPath: NSIndexPath) {
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
if cell.accessoryType == .Checkmark {
let checkedCourse = cell.textLabel?.text
var query = PFQuery(className: "Courses")
query.whereKey("coursename", equalTo: checkedCourse!)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) scores.")
// Do something with the found objects
}
}
} else {
// Log details of the failure
println("Error")
}
}
}*/
}
EDIT3:
#IBAction func saveButton(sender: AnyObject) {
let currentUser = PFUser.currentUser()
var selectedObjects = Array<PFObject>()
let cUserRel = currentUser?.relationForKey("usercourses")
for object in qObjects {
cUserRel!.removeObject(object as! PFObject)
}
println(selectedRowSets)
for selectedRows in self.selectedRowSets {
println("count")
selectedRows.enumerateIndexesUsingBlock(
{(index, stop) -> Void in
// Get object reference
if self.objects != nil{
let anObject = self.objects![index] as! PFObject
selectedObjects.append(anObject)
println(anObject)
cUserRel!.addObject(anObject)
}
})
}
currentUser?.save()
navigationController?.popViewControllerAnimated(true)
}
When you are calling your function you are specifying the parameter types (which is why the error message says that you call it with UITableView.Type and NSIndexPath.Type.
You need to specify instances of a UITableView and an NSIndexPath -
Something like
getCheckmarkedCells(tableview:self.tableView indexPath: someIndexPath);
However, you probably don't want to send a specific index path to this method because you want to scan the entire table, not just look at a specific row.
Your fundamental problem is that you appear to be using your table view as a data model - the tableview should simply be a view of data stored in some other data structure. For example, cellForRowAtIndexPath may return nil for a cell that isn't currently on screen.
You can use an NSMutableIndexSet for storing your selected rows -
var selectedRowSets = [NSMutableIndexSet]() // You will need to add a NSMutableIndexSet to this array for each section in your table
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
let selectedRows=self.selectedRowSets[indexPath.section]
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
if (selectedRows.containsIndex(indexPath.row)) {
cell.accessoryType = .None
selectedRows.removeIndex(indexPath.row)
} else {
cell.accessoryType = .Checkmark
selectedRows.addIndex(indexPath.row);
}
}
}
You should use the same test in cellForRowAtIndexPath to set the accessory on cells as they are reused.
You can retrieve the checked row using
func getSelectedObjects() {
self.selectedObjects=Array<PFObject>()
for selectedRows in self.selectedRowSets {
selectedRows.enumerateIndexesUsingBlock({index, stop in
// Get object reference
if self.objects != nil{
let anObject=self.objects![index] as! PFObject
self.selectedObjects.append(anObject)
}
})
}
}
Do you already have an array that contains all of the Courses from Parse?
You need to allocate a new NSIndexSet for each section. Remove the selectedSet instance variable and change your loop in objectsDidLoad to
for index in 1...section {
self.selectedRowSets.append(NSMutableIndexSet())
}
Another solution would be to parse through each cell in the tableView at the end and check if it is marked or not. I am assuming you have only one section
let rowCount = tableView.numberOfRowsInSection(0)
let list = [TableViewCell]()
for var index = 0; index < rowCount; ++index {
let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: index, inSection: 0)) as! YourCell
if cell.accessoryType = .Checkmark{
list.append(cell)
}
I am running into difficulty displaying the data from the query I made in the individual cells of my tableview. I believe that my logic is correct, but I'm not seeing the console.log's that I am calling within my function that contains the Parse queried data. This might be a simple fix, but it isn't coming to me at the moment. The console log I should be seeing to validate that my query is coming through correctly is the println("\(objects.count) users are listed"), it should then be displayed within the usernameLabel.text property.
import UIKit
class SearchUsersRegistrationViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var userArray : NSMutableArray = []
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
loadParseData()
}
func loadParseData() {
var query : PFQuery = PFUser.query()
query.findObjectsInBackgroundWithBlock {
(objects:[AnyObject]!, error:NSError!) -> Void in
if error == nil {
if let objects = objects {
println("\(objects.count) users are listed")
for object in objects {
self.userArray.addObject(object)
}
self.tableView.reloadData()
}
} else {
println("There is an error")
}
}
}
let textCellIdentifier = "Cell"
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.userArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(textCellIdentifier, forIndexPath: indexPath) as! SearchUsersRegistrationTableViewCell
let row = indexPath.row
let cellDataParse : PFObject = self.userArray.objectAtIndex(row) as! PFObject
//cell.userImage.image = UIImage(named: usersArr[row])
cell.usernameLabel.text = cellDataParse.objectForKey("_User") as! String
return cell
}
}
I fixed the issue. I needed to cast the index path row in the users array as a PFUser and then cast the user's username property as a String and then set that as the label text.
let row = indexPath.row
var user = userArray[row] as! PFUser
var username = user.username as String
cell.usernameLabel.text = username