Retrieving a specific value from an array to the custom cell - Swift - ios

I'm playing around with custom cells.
With the great help from the stackoverflow community i've been able to put some code together. I'm able to fetch array values from a text string into a custom cell uilabel and uibutton, but the issue is - the fetched result is always the last object in the array.
Here is the code
func setUpQuestion()
{
// setting variables
var Question: String?
var option1: String?
// using a text string with custom separators
let text = ">>Here is the grocery question\n>>and another one\n--Apples\n-
-Oranges\n[pickApples]pickOranges\n[pickApples2]"
// splitting this string into four different arrays depending on the separator
let lines = split(text) { $0 == "\n" }
for line in lines {
if line.hasPrefix(">>") {
Question = line.substringFromIndex(advance(line.startIndex, 2))
} else if line.hasPrefix("[") {
if let index = line.rangeOfString("]")?.startIndex {
option1 = line.substringWithRange(Range<String.Index>(
start: advance(line.startIndex, 1), end: index))
}
}
}
// creating variables for appending the values - here I'm using a custom class called QuestionMark created in a separate .swift file
var question1 = QuestionMark(Question: Question!, option:option1!)
var question2 = QuestionMark(Question: Question!, option:option1!)
// appending the values into uilabel and uibutton in the custom cell
arrayOfQuestions.append(question1)
arrayOfQuestions.append(question2)
}
// regular tableView protocol functions
func tableView(tableView: UITableView, numberOfRowsInSection
section: Int) ->Int
{
return arrayOfQuestions.count
}
func updateCount(){
if let list = mainTableView.indexPathsForSelectedRows() as? [NSIndexPath] {
rowsCount.text = String(list.count)
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath
indexPath: NSIndexPath) -> UITableViewCell
{
let cell: CustomCellForTableViewTableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell") as! CustomCellForTableViewTableViewCell
// the SetCell function i'm using here was created in a separate .swift file
let quest = arrayOfQuestions[indexPath.row]
cell.setCell(quest.Questme!, optionone: quest.optionize!)
cell.optionOne.backgroundColor = UIColor.redColor()
return cell
}
Here are the additional codes i'm using for the class and setCell function
class QuestionMark
{
var Questme: String?
var optionize: String?
init(Question: String, option: String)
{
self.Questme = Question
self.optionize = option
}
// separate swift file
func setCell(Question: String, optionone: String)
{
self.mainText.text = Question
self.optionOne.setTitle(optionone, forState:UIControlState.Normal)
}
As a result in both cells i'm getting the last object from the text string and it looks like this
And another one - PickApples2
And another one - PickApples2
How do i start appending cells from the first array value and then move forward to second,third,fourth ?
Any ideas are greatly appreciated.
Thank you.

First of all, the syntax of the text to parse is pretty complicated ;-) …
Second of all, the problem to get always the last object is that you create the array of questions after the repeat loop. At that moment the variables question and option contain always the last found string.
Here a solution:
After getting a question a new object QuestionMark is created and appended to the array (without the optionize property)
After getting an option the appropriate QuestionMark object is fetched from the array by an index counter, the property optionize is set and the counter is increased.
Two notes:
Variable names should always start with a lowercase letter. Even the syntax highlighter of StackOverflow follows that naming convention.
In my solution all variables are non-optionals.
class QuestionMark
{
var questme: String
var optionize: String
init(question: String, option: String = "")
{
self.questme = question
self.optionize = option
}
...
var arrayOfQuestions = [QuestionMark]()
func setupQuestion() {
let text = ">>Here is the grocery question\n>>and another one\n--Apples\n--Oranges\n[pickApples]pickOranges\n[pickApples2]"
// splitting this string into four different arrays depending on the separator
var counter = 0
var question = ""
var option = ""
let lines = split(text) { $0 == "\n" }
for line in lines {
if line.hasPrefix(">>") {
question = line.substringFromIndex(advance(line.startIndex, 2))
let questionMark = QuestionMark(question: question)
arrayOfQuestions.append(questionMark)
} else if line.hasPrefix("[") {
if let index = line.rangeOfString("]")?.startIndex {
option = line.substringWithRange(Range<String.Index>(
start: advance(line.startIndex, 1), end: index))
let questionMark = arrayOfQuestions[counter]
questionMark.optionize = option
counter++
}
}
}
}

Related

'subscript(_:)' is unavailable: cannot subscript String with an Int, use a String.Index instead [duplicate]

This question already has answers here:
Get nth character of a string in Swift
(47 answers)
Closed 1 year ago.
I'm trying to pass the data and got this error: 'subscript(_:)' is unavailable: cannot subscript String with an Int, use a String.Index instead. Here is my code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: CustomLoopsCell = beatTableView.dequeueReusableCell(withIdentifier: "firstLoopCell", for: indexPath) as! CustomLoopsCell
let beatLoop = overtunePacks[indexPath.row]
// On the lines which is down I'm getting this error
let beatDesc = loopsDesc[indexPath.row]
let beatProd = loopsProd[indexPath.row]
let beatAudio = loopsAudio[indexPath.row]
let beatInst = loopsInst[indexPath.row]
cell.loopNameLabel.text = beatDesc
cell.producerLabel.text = beatProd
cell.instrumentLabel.text = beatInst
Here is the code from another view controller, from where I'm passing the data:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = storyboard?.instantiateViewController(identifier: "BeatViewID") as? BeatPackViewController
vc?.beatInfoCollectName = beatInfoCollect[indexPath.row].name
vc?.beatProducerName = beatInfoCollect[indexPath.row].producer
vc?.imageBeat = beatInfoCollect[indexPath.row].imagename
vc?.loopsAudio = beatAudioCollect[indexPath.row].loopPath
vc?.loopsDesc = beatAudioCollect[indexPath.row].name
vc?.loopsInst = beatAudioCollect[indexPath.row].instrument
vc?.loopsProd = beatAudioCollect[indexPath.row].producer
self.navigationController?.pushViewController(vc!, animated: true)
}
Here is my variables in second view controller, where I'm passing data:
var loopsAudio = ""
var loopsDesc = ""
var loopsInst = ""
var loopsProd = ""
Here is what I want to display:
struct BeatLoops: Decodable {
let name: String
let instrument: String
let loopPath: String
let producer: String
var songURL : URL {
let components = loopPath.components(separatedBy: ".")
guard components.count == 2,
let url = Bundle.main.url(forResource: components[0], withExtension: components[1]) else { fatalError("Audio file is missing") }
return url
}
}
I'm parsing this data and have instance: var beatAudioCollect = [BeatLoops]()
Seems that you are trying to get Substring from a loopsDesc String here
let beatDesc = loopsDesc[indexPath.row]
...
let beatInst = loopsInst[indexPath.row]
The point is that you can't just get String's characters using subscription index.
If you want to get a character from a String, you should get it's Substring with a specified range.
Like that:
let index = loopsDesc.index(loopsDesc.startIndex, offsetBy: 1)
let mySubstring = loopsDesc[..<index]
Or another way:
1 - add this extension to a String:
extension StringProtocol {
subscript(offset: Int) -> Character {
self[index(startIndex, offsetBy: offset)]
}
}
2 - use it like that
let input = "My String Here"
let char = input[3]
//or
let char = String(input[3])
Please see another stackoverflow question related to that.
But it's unsafe method and I would suggest to use another way to get params (for example store them in Array, not in String)
Your design is wrong.
Extracting single characters from a string in a table view to be displayed in different rows makes no sense.
Actually you are passing two objects to the second view controller, an infoCollect object and an audioCollect object.
First of all rather than extracting all properties just pass the entire objects.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = storyboard?.instantiateViewController(identifier: "BeatViewID") as! BeatPackViewController
vc.infoCollect = beatInfoCollect[indexPath.row]
vc.audioCollect = beatAudioCollect[indexPath.row]
self.navigationController?.pushViewController(vc, animated: true)
}
By the way using multiple arrays as data source is strongly discouraged!.
In the second controller replace the String properties with
var infoCollect : Loop!
var audioCollect : BeatLoops!
If the structs are declared inside in first view controller you have to write
var infoCollect : BPLibraryViewController.Loop!
var audioCollect : BPLibraryViewController.BeatLoops!
Then remove the table view and add UILabels instead. In viewDidLoad assign the values for example
imageBeatLabel.text = infoCollect.name
loopsAudioLabel.text = audioCollect.loopPath

How can I use one view controller with different Realm classes

I am trying to create an app where I have 4 buttons which each one corresponds to a different category. Now the categories are different Realm Objects saved in a swift file.
class HealthData: Object {
#objc dynamic var name : String = ""
}
class SelfImprovement: Object {
#objc dynamic var name : String = ""
}
class TopSecret: Object {
#objc dynamic var name : String = ""
}
class Ohter: Object {
#objc dynamic var name : String = ""
}
Now my problem is that I want a single view controller with a TableView to have different data that will get passed on to TableView from the corresponding category.
My idea was that I can create the var categories : Results<HealthData>! and use an if statement to change the categories to be Results etc using the prepare for a segue to know which button was pressed.
override func viewWillAppear(_ animated: Bool) {
if categoryNo == 1 {
title = "Health"
} else if categoryNo == 2 {
title = "Self Improvement"
categories = Results<SelfImprovement>!
}
}
But of course, XCode cannot assign the value of type 'Results?.Type' to type 'Results?'.
Any ideas?
Thank you all for your time!
So the issue is you want to re-use the tableView to display data from different tableView datasources. You're heading down the right path but the answer is to tell the tableView where to get it's data from.
I am pairing this down to really basic code so don't copy paste - trying to keep it short.
Assume we have a view controller with a tableView
class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
#IBOutlet weak var myTableView: NSTableView!
var myHealthArray = [String]() //one tableview datasource
var mySelfImprovementArray = [String]() //another tableview datasource
var tableType = "" //will tell the tableView what it should be displaying
then, further down, we have the tableView delegate methods that populate the tableview
func numberOfRows(in tableView: NSTableView) -> Int {
if self.tableType == "health" {
return self.myHealthArray.count
} else if self.tableType == "self_improvement" {
return self.mySelfImprovementArray.count
} //etc
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let cell = tableView.makeView(withIdentifier: myIdentifier, owner: self) as! NSTableCellView
var s = ""
if self.tableType == "health" {
s = self.myHealthArray[row]
} else if self.tableType == "self_improvement" {
s = self.mySelfImprovementArray[row]
} //etc
cell.textField?.stringValue = s
return cell
}
So the idea here is to set the tableType to whatever is supposed to be displayed in the tableView then the logic within the tableView delegate methods will know which dataSource array to get its data from.
It's important to remember that Realm Results are homogenous so they can only store one type of object.
In this example, I defined the dataSources as strings but in your case they may be realm results objects. If they are strings, you could just re-use the array as it would only contain string objects. However, if the dataSources are Realm Results, you'll need separate arrays as shown in this answer.

Swift: Array Size with UITableView

I'm trying to figure out how the array works with Swift. I understand that you use let to create an immutable array and var to create a mutable array. Yet, Swift's array is not quite the same as Objective's NSArray and NSMutableArray. I get that.
In the example that follows, I create a mutable array with one element. Actually, I want to start with no element. But if I do, I won't be able to add a new string to it. If I start with one element, then I won't be able to add a new string to it after the original element is removed. So what am I doing wrong?
Thank you
EDIT
class ViewController: UIViewController {
let textCellIdentifier = "TextCell"
var myArray:[String] = ["GGG"] // or var myArray:[String] = []
#IBAction func add1Tapped(sender:AnyObject) {
let index = tableView1.indexPathForSelectedRow;
let selectedRow = index()?.row
if selectedRow < 0 {
return
} else {
let txt = nameField1.text
myArray.append(txt)
tableView1.reloadData()
}
}
func tableView(tableView: UITableView,numberOfRowsInSection section:Int) -> Int {
return myArray.count
}
func tableView(tableView:UITableView,cellForRowAtIndexPath indexPath:NSIndexPath) -> UITableViewCell {
let cell:UITableViewCell=UITableViewCell(style: UITableViewCellStyle.Subtitle,reuseIdentifier:textCellIdentifier)
let row = indexPath.row
cell.textLabel!.text = myArray[row]
return cell
}
}
all your needs should work as expected:
// create an empty array of strings
var myArray: [String] = []
// add two elements
myArray.append("the first element")
myArray.append("the second element")
// remove both elements
myArray.removeAll()
// add another element
myArray.append("the third but now first element")
myArray.count
EDIT
try and change your add method like this:
#IBAction func add1Tapped(sender:AnyObject) {
if let _ = tableView1.indexPathForSelectedRow, txt = nameField1.text {
print("will append \(txt) to myArray")
myArray.append(txt)
tableView1.reloadData()
}
}

Swift 2 Unable to remove optional binding

I am new in Swift and don't have much more idea on optional (! , ?). I tried to fetch data from plist, create Model and show to UITableView. Table data shows perfectly, but it shows with Optional() binding. I tried change ! to ? but unable to unwrap. Could you please, guide me to solve this problem.
Here is my code & output -
var fileName : String?
var dataArray : Array<SHQuesAns>?
For fetch data from pList -
func loadTableView(){
dataArray = SHDataAccess.init(fname: fileName).arrayFromPlist()
self.questionTableView.dataSource = self
self.questionTableView.delegate=self
self.questionTableView.reloadData()
}
SHDataAccess class -
import UIKit
var fileName : String!
class SHDataAccess: NSObject {
init(fname:String?) {
super.init()
fileName = fname
}
func arrayFromPlist() -> Array <SHQuesAns>?{
let dataPlists = NSMutableArray(contentsOfFile:NSBundle.mainBundle().pathForResource(fileName, ofType: "plist")!)
var dataObj : Array <SHQuesAns>? = Array()
for data in dataPlists! {
dataObj?.append(SHQuesAns.init(_dic: data as! NSDictionary))
}
return dataObj
}
}
And UITableView delegates -
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArray == nil ? 0 : dataArray!.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let aCell = self.questionTableView.dequeueReusableCellWithIdentifier("qcell",forIndexPath: indexPath) as! SHQuestionCell
let q : SHQuesAns = dataArray![indexPath.row]
aCell.lblQuestion.text = "\(q.question)"
return aCell
}
Here is the output -
This will remove that Optional() text:
if let q = q.question as? String {
aCell.lblQuestion.text = "\(q.question!)"
} else {
aCell.lblQuestion.text = ""
}
The key is to unwrap the string contained in the question object so that when it is assigned to the text of the label, the Optional() part will not be included.
I’ve added support for the nil case if the question string is not defined.
You might also consider not making your dataObj array optional? what purpose does it serve to be optional? Seems to me that if you need to add items to the array then you know it should exist and since you've initialized it it will always exist but then may be empty. Instead just make it implicitly unwrapped and then return nil if there's no data, then the objects of the array won't all be optional.
if you have a default in mind that you would want the optional string to fall back to, a simple fix would be something like:
"\(q.question ?? "")"
which will default to an empty string if q.question is nil
also: be careful of all of your force unwraps. it might make more sense to have some guard statements or if let unwraps.
and swift array's can be written like so: var dataArray : [SHQuesAns]?
but there aren't many situations where you need to differentiate between a nil array and an empty array so you can just do var dataArray = [SHQuesAns]() and save yourself the need to unwrap

How can I remove an object from an array using its original index path?

I have a UICollectionView in which the user can select data from cells to add to an array. I am attempting to highlight the selected cells when tapped, and un-highlight them when tapped again. In the same bit of code that highlights and un-highlights, I would like to add/remove the data from the array. I have no problem adding the data to the array, but I can't figure out how to remove it when un-highlighted.
Code here:
var removeFromList = [AnyObject]()
func collectionView(collectionView: UICollectionView, shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool {
var cell = self.collectionView2.cellForItemAtIndexPath(indexPath)
if cell!.tag == 0 {
cell!.layer.borderWidth = 2.0
cell!.layer.borderColor = UIColor.blueColor().CGColor
removeFromList.append(objectIds[indexPath.row])
cell!.tag = 1
} else {
cell!.layer.borderWidth = 0.0
cell!.tag = 0
removeFromList.//WHAT CAN I PUT HERE?
}
return true
}
Use removeAtIndex(index: Int) method to remove an item
var removeFromList = [NSString]()
if let index = find(removeFromList, objectIds[indexPath.row] as! NSString) {
removeFromList.removeAtIndex(index)
}
Use Dictionary for this:
var removeFromList = [NSIndexPath:AnyObject]()
if cell!.tag == 0 {
cell!.layer.borderWidth = 2.0
cell!.layer.borderColor = UIColor.blueColor().CGColor
removeFromList[indexPath] = objectIds[indexPath.row]
cell!.tag = 1
} else {
cell!.layer.borderWidth = 0.0
cell!.tag = 0
removeFromList.removeValueForKey(indexPath)
}
Because the array is being used as a list, you need to find the index before you can remove the element. Use Swift's find method:
removeFromList.removeAtIndex(find(removeFromList, objectIds[indexPath.row])!)
The problem with this method is that find does not work with AnyObject. Looping through the array and comparing each term won't work either, because AnyObject is not comparable. You need to change removeFromList and objectIds to a more specific class.
To make this code "prettier" and prevent your app from crashing when it tries to remove something not in the array, you should use an extension of the array class. Unfortunately, you'll have to make a new function for each data type.
func removeObjMyClass(inout arr: [MyClass], obj: MyClass) {
if let index = find(arr, obj) {
arr.removeAtIndex(index)
}
}
removeObjMyClass(&removeFromList, objectIds[indexPath.row])

Resources