I'm working in a project in Swift 3.0 and I have a loop that would give me the state of an object (gives a boolean value in a an array sequence). My requirement is if the element state is "true" I wants to get the relevant index path of that, so using that indexPath I can get the whole object from my original array. So inside a loop how can i get that as well? My partially done code is below. In a comment I have stated where I wants to pass this indexPath.
func tapFunction(sender: UIButton) {
// This gives me the selected indexPath
print("ID :",sender.accessibilityIdentifier!)
if let rowIndexString = sender.accessibilityIdentifier, let rowIndex = Int(rowIndexString) {
self.sateOfNewSongArray[rowIndex] = !self.sateOfNewSongArray[rowIndex]
}
sender.isSelected = !sender.isSelected
for (element) in self.sateOfNewSongArray {
//This is where I wants to pass the rowIndex that became true
if element == true {
selectedSongList.addingObjects(from: [songList[rowIndex]])
}
}
}
Note: Make sure your array 'sateOfNewSongArray' has boolean types
of elements
Try following solutions:
Sample Code 1: According to your current code in your question.
func tapFunction(sender: UIButton) {
// This gives me the selected indexPath
print("ID :",sender.accessibilityIdentifier!)
if let rowIndexString = sender.accessibilityIdentifier, let rowIndex = Int(rowIndexString) {
self.sateOfNewSongArray[rowIndex] = !self.sateOfNewSongArray[rowIndex]
}
sender.isSelected = !sender.isSelected
for (index, element) in self.sateOfNewSongArray.enumerated() {
if element {
if let songName:String = songList[index] {
selectedSongList.append(songName)
}
}
}
}
Sample Code 2: More easier and concise.
func tapFunction(sender: UIButton) {
// This gives me the selected indexPath
print("ID :",sender.accessibilityIdentifier!)
if let rowIndexString = sender.accessibilityIdentifier, let rowIndex = Int(rowIndexString) {
if let songName:String = songList[rowIndex] {
selectedSongList.append(songName)
}
}
}
enumerated() is a function, returns sequence of pairs enumerating with index and element of array (by iterating elements of array)
Here is basic sample code, for your understanding. Copy and execute this code:
let songList = [“cat”, “dog”, “elephant”]
let sateOfNewSongArray = [false, true, false]
let selectedSongList = [String]()
for (index, element) in self.sateOfNewSongArray.enumerated() {
if element {
if let animalName:String = songList[index] {
selectedSongList.append(animalName)
}
}
}
print(“selectedSongList - \(selectedSongList)”)
Related
people. Im learning how to work with CoreData and i have a question.
I have a "WordArrayEntity" which have oneToMany relationship with "WordEntity" and Nulify deletion rule.
So at first when my app start im fetching info from my CoreData to special array
var wordEntities:[[WordEntity]] = []
After it my tableView recieves an attribute that it needs.
As i understand i can delete entities by this method
context.delete(wordEntity)
everything works fine when i do this on my tableView.
But, when i move to editor ViewController and try to add new Entity or delete previous nothing happens.
This is my code to move to next view controller
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if let controller = self.storyboard?.instantiateViewController(withIdentifier: "ShowWordVC") as? ShowWordVC {
controller.word = SingleTonForEntities.shared.wordEntities[indexPath.section][indexPath.row]
controller.wordIndex = indexPath.row
self.present(controller, animated: true)
}
}
This is my method to delet word, which perfectly works at tableView
func deleteWordFromVocabulary(word: WordEntity,indexPath: Int) {
var wordStartsFromLetter = false
if let firstCharacter:Character = word.englishWord?.first {
var index = 0
for array in wordEntities {
if SingleTon.shared.sectionName[index].lowercased() == firstCharacter.lowercased() {
var newArray = array
context.delete(word)
newArray.remove(at: indexPath)
wordEntities[index] = newArray
wordStartsFromLetter = true
}
index += 1
}
}
if wordStartsFromLetter == false {
var newArray = wordEntities[26]
context.delete(word)
newArray.remove(at: indexPath)
wordEntities[26] = newArray
}
saveContext()
}
When i try to save word after editing or a new one i have the following code
#IBAction func saveButtonIsPressed(_ sender: UIButton) {
keyboardDissapears()
guard let englishWord = self.englishWordTextField.text,
!englishWord.isEmpty,
let wordImage = addedImageView.image,
let belarusianWord = self.belarusianWordTextField.text,
!belarusianWord.isEmpty,
let englishDefinition = self.englishDefinitionTextView.text,
!englishDefinition.isEmpty,
let belarusianDefinition = self.belarusianDefinitionTextView.text,
!belarusianDefinition.isEmpty else {
createAndShowAlert()
return
}
if editModeIsActivated == true {
if let wordToDelete = word, let wordIndex = wordIndex {
SingleTonForEntities.shared.deleteWordFromVocabulary(word: wordToDelete, indexPath: wordIndex)
}
}
word!.englishDefinition = englishDefinition
word!.belarusianDefinition = belarusianDefinition
word!.englishWord = englishWord
word!.belarusianWord = belarusianWord
let data = wordImage.pngData()
word!.wordImage = data
SingleTonForEntities.shared.addNewWordToVocabulary(word: self.word)
self.createAndShowDoneProgressAlert()
}
at first im checking if my fields are empty. If they aren't empty and we are in edit mode i delete the "WordEntity" from our context and then from our array.
And then i try to save a new word and add it to context with this method
func addNewWordToVocabulary(word: WordEntity!) {
var wordStartsFromLetter = false
word.englishWord = word.englishWord!.trimmingCharacters(in: .whitespacesAndNewlines)
if let firstCharacter:Character = word.englishWord?.first {
var index = 0
for array in wordEntities {
if SingleTon.shared.sectionName[index].lowercased() == firstCharacter.lowercased() {
var newArray = array
newArray.append(word)
for element in wordEntitesArray {
if element.arrayName == SingleTon.shared.sectionName[index].lowercased() {
element.addToWordEntities(word)
}
}
wordEntities[index] = newArray
wordStartsFromLetter = true
}
index += 1
}
}
if wordStartsFromLetter == false {
var newArray = wordEntities[26]
newArray.append(word)
wordEntities[26] = newArray
}
saveContext()
}
And there is a question. What am i doing wrong?
When i try to add new word to vocabulary - my app crashes.
But when im in edit mode and after it adding a new word to vocabulary - it just returns an empty tableViewCell.
I am new to CoreData and working with for about a week, but i would be glad to here what am i doing wrong and what i should do in such situations.
P.S. Everything worked well with UserDefaults
I want to trigger an else statement if there is no object at the indexPath i look for in an array.
The array is
let exerciseSets = unsortedExerciseSets.sorted { ($0.setPosition < $1.setPosition) }
Then i use let cellsSet = exerciseSets[indexPath.row]
There is a chance, when a user is adding a new cell, that it wont already have an exerciseSet at the indexPath to populate it (adding a new set to an existing array of sets) and so I need to run an else statement to set up a blank cell rather than attempting to populate it and crashing my app.
However if i use if let then i get this error:
Initializer for conditional binding must have Optional type, not
'UserExerciseSet'
Here is the whole function for context if needed:
func configure(_ cell: NewExerciseTableViewCell, at indexPath: IndexPath) {
if self.userExercise != nil {
print("RESTORING CELLS FOR THE EXISTING EXERCISE")
let unsortedExerciseSets = self.userExercise?.exercisesets?.allObjects as! [UserExerciseSet]
let exerciseSets = unsortedExerciseSets.sorted { ($0.setPosition < $1.setPosition) }
if let cellsSet = exerciseSets[indexPath.row] { // this causes a creash when user adds new set to existing exercise as it cant populate, needs an else statement to add fresh cell
cell.setNumber.text = String(cellsSet.setPosition)
cell.repsPicker.selectRow(Int(cellsSet.setReps), inComponent: 0, animated: true)
let localeIdentifier = Locale(identifier: UserDefaults.standard.object(forKey: "locale") as! String)
let setWeight = cellsSet.setWeight as! Measurement<UnitMass>
let formatter = MassFormatter()
formatter.numberFormatter.locale = localeIdentifier
formatter.numberFormatter.maximumFractionDigits = 2
if localeIdentifier.usesMetricSystem {
let kgWeight = setWeight.converted(to: .kilograms)
let finalKgWeight = formatter.string(fromValue: kgWeight.value, unit: .kilogram)
let NumericKgResult = finalKgWeight.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789.").inverted)
cell.userExerciseWeight.text = NumericKgResult
} else {
let lbsWeight = setWeight.converted(to: .pounds)
let finalLbWeight = formatter.string(fromValue: lbsWeight.value, unit: .pound)
let NumericLbResult = finalLbWeight.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789.").inverted)
cell.userExerciseWeight.text = NumericLbResult
}
} else {
cell.setNumber.text = String((indexPath.row) + 1)
}
You could do something crazy like this:
if let cellSet = (indexPath.row < exerciseSets.count ? exerciseSets[indexPath.row] : nil) {
//
}
but it would be more straightforward to do:
if indexPath.row < exerciseSets.count {
let cellSet = exerciseSets[indexPath.row]
...
}
OK, so your problem is that you are trying to access a value in an array that may or may not be there.
If you just try to access the value based on indexPath you can crash because indexPath may refer to a value not there. On the other hand, the array does not return an optional so you can't use if let either.
I kind of like the idea of using an optional, so how about introducing a function that could return an optional to you if it was there.
Something like:
func excerciseSet(for indexPath: IndexPath, in collection: [UserExcerciseSet]) -> UserExcerciseSet? {
guard collection.count > indexPath.row else {
return nil
}
return collection[indexPath.row]
}
and then you can say:
if let cellsSet = exerciseSet[for: indexPath, in: excerciseSets] {
//It was there...showtime :)
} else {
cell.setNumber.text = String((indexPath.row) + 1)
}
Hope that helps you.
Just check the index against your array count:
if indexPath.item < exerciseSets.count {
// Cell exists
let cellsSet = exerciseSets[indexPath.row]
} else {
// cell doesn't exists. populate new one
}
I have problem with selectedRows() in SelectableSection.
Using Xcode 8, Swift 3, Eureka 2.0.0-beta.1.
func viewDidLoad() {
let branch_section = SelectableSection<ImageCheckRow<String>>("Branches", selectionType: .multipleSelection)
branch_section.tag = "branch_section"
for branch in branchList {
let branchStr = String(branch.id)
branch_section <<< ImageCheckRow<String>(branch.name){ row in
row.title = branch.name
row.selectableValue = branchStr
row.value = nil
}
}
}
#IBAction func saveFilter(_ sender: AnyObject) {
let branch_section = self.form.sectionBy(tag: "branch_section") as? SelectableSection<ImageCheckRow<String>>
invoiceParams["branches"] = branch_section!.selectedRows().map({$0.value!})
}
now i have problem with this line invoiceParams["branches"] = branch_section!.selectedRows().map({$0.value!})
map' produces '[T]', not the expected contextual result type
'AnyObject?'
What is problem here? This worked with previous versions on swift 2.3.
As far as I can see from SelectableSection.swift selectedRows returns an array of SelectableRow items:
public func selectedRows() -> [SelectableRow] {
return filter({ (row: BaseRow) -> Bool in
row is SelectableRow && row.baseValue != nil
}).map({ $0 as! SelectableRow})
}
So the map function also returns an array.
Your invoiceParams seems to be a dictionary that expects AnyObject? as value.
You can try to change the declaration of invoiceParams to something like var invoiceParams: [String: [Any]] = [:].
Since I don't know Eureka this is just a guess. But I hope it still helps a little.
I am practicing iOS (Swift) with Firebase. the first viewcontroller retrieves all the records from firebase db and populate the tableView from an array. when the user selects an item from that tableview a new viewcontroller pops up segueing the object from the listView viewcontroller to the detail viewcontroller. data is populated to the fields successfully!
however when i try to update any of the textfield, the moment i switch to another textfield the initial value is restored in the edited textfield.
I have tried to removeAllObservers... but nothing worked. i even removed "import Firebase" and all associated objects and still it restores the initial value.
am i missing any concept here?
this is the code from the ListViewController:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated);
self.activityIndicator.startAnimating();
self.observeIngrdUpdates = ref.observeEventType(.Value, withBlock: { (snapshot) in
self.ingredients.removeAll();
for child in snapshot.children {
var nutrition:String = "";
var type:Int = 0;
var desc:String = "";
var img:String = "";
var name:String = "";
var price:Double = 0.0;
if let _name = child.value["IngredientName"] as? String {
name = _name;
}
if let _nutrition = child.value["NutritionFacts"] as? String {
nutrition = _nutrition;
}
if let _type = child.value["IngredientType"] as? Int {
type = _type;
}
if let _desc = child.value["UnitDescription"] as? String {
desc = _desc;
}
if let _img = child.value["IngredientImage"] as? String {
img = _img;
}
if let _price = child.value["IngredientPrice"] as? Double {
price = _price;
}
let ingredient = Ingredient(name: name, type: type, image: img, unitDesc: desc, nutritionFacts: nutrition, price: price);
ingredient.key = child.key;
self.ingredients.append(ingredient);
}
self.tableView.reloadData();
})
}
and the PrepareForSegue:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destinationVC = segue.destinationViewController as! EditIngredientVC;
if segue.identifier?.compare(SEGUE_EDIT_INGREDIENTS) == .OrderedSame {
destinationVC.ingredient = ingredients[tableView.indexPathForSelectedRow!.row];
destinationVC.controllerTitle = "Edit";
} else if segue.identifier?.compare(SEGUE_ADD_INGREDIENT) == .OrderedSame {
destinationVC.controllerTitle = "New";
}
}
this is the code for populating the fields in DetailViewController:
override func viewDidLayoutSubviews() {
lblControllerTitle.text = controllerTitle;
if controllerTitle?.localizedCaseInsensitiveCompare("NEW") == .OrderedSame {
self.segVegFru.selectedSegmentIndex = 0;
} else {
if ingredient != nil {
self.txtIngredientName.text = ingredient!.name;
self.txtUnitDesc.text = ingredient!.unitDesc;
self.segVegFru.selectedSegmentIndex = ingredient!.typeInt;
self.txtNutritionFacts.text = ingredient!.nutritionFacts;
self.txtPrice.text = "\(ingredient!.price)";
}
}
}
Thank you all for your help.
It's probably because you are putting your textField populating code, in viewDidLayoutSubviews() method.
This method will be triggered every time the layout of a views changes in viewcontroller.
Move it to viewDidLoad() and it should be fine.
The reason that it's being reverted to the previous value is because. You are populating the textField.text from the "ingredient" object. The ingredient will have the same value retained that you passed from previous view controller. Until you mutate it at some point.
And by the way Firebase is very cool I like it too :)
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()
}
}