I have a main screen that contains a Table View and a Segmented Control.
The switch outputs operations from the array depending on the selected type. If there are no operations in the selected type, then the corresponding text will be displayed in the UILabel.
I noticed the following error - if I swipe a cell in a table (for example, to delete it), then text will be displayed that is completely unrelated to the selected type (because there are values there). That is, if I have a cell in expense, but there are no values in income, then when I swipe "expense" of the cell, a text will be displayed stating that there are no values for income where I do not expect it.
The screenshots describe it better:
class HomeViewController: UIViewController {
var currentSegmentType: RecordType {
return RecordType(rawValue: switchButton.titleForSegment(at: switchButton.selectedSegmentIndex)!)!
}
let switchButton: UISegmentedControl = {
let switcher = UISegmentedControl(items: [RecordType.allOperations.rawValue, RecordType.income.rawValue, RecordType.expense.rawValue])
switcher.selectedSegmentIndex = 0
return switcher
}()
var operationIsEmpty = UILabel()
func setEmptyOperationsLabel() {
if (Storage.shared.records[currentSegmentType]!.isEmpty) {
if currentSegmentType == .income {
operationName = """
You have not yet
received incomes
"""
}
if currentSegmentType == .expense {
operationName = """
You haven't had
any expenses yet
"""
}
operationIsEmpty.text = operationName
self.view.addSubview(operationIsEmpty)
self.operationIsEmpty.easy.layout([CenterY(100), CenterX()])
} else {
operationIsEmpty.isHidden = true
}
}
#objc func switchButtonAction(target: UISegmentedControl) {
setEmptyOperationsLabel()
tableView.reloadData()
}
}
Related
My goal is to use a button (that contains multiple messages) to trigger a text (making a marker such as first click will be method 1, second click will be method 2) correspondingly added at the end of the my data (after joined(separator: "~")) so that it could help me to analyze which button was clicked when I look back at the data.
Currently, I have a struct that will output the data:
struct CaptureData {
var vertices: [SIMD3<Float>] //A vector of three scalar values. It will return a list of [SIMD3<Float>(x,y,z)]
var mode: Mode = .one
mutating func nextCase() { // the data method will be changed
mode = mode.next()
}
var verticesFormatted : String { //I formatted in such a way so that it can be read more clearly without SIMD3
let v = "<" + vertices.map{ "\($0.x):\($0.y):\($0.z)" }.joined(separator: "~") + "trial: \(mode.next().rawValue)"
return "\(v)"
}
}
Based on #Joshua suggestion
enum Mode: String, CaseIterable {
case one, two, three
}
extension CaseIterable where Self: Equatable {
var allCases: AllCases { Self.allCases }
var nextCase: Self {
let index = allCases.index(after: allCases.firstIndex(of: self)!)
guard index != allCases.endIndex else { return allCases.first! }
return allCases[index]
}
#discardableResult
func next() -> Self {
return self.nextCase
}
}
And the button is alternating the messages after each click,
var x = 0
var instance = CaptureData(vertices: [SIMD3<Float>])
// Next button for changing methods
#IBAction func ChangingTapped(_ btn: UIButton) {
if(x==0){
Textfield.text = "changing to driving"
}
else if(x==1){
Textfield.text = "changing to walking"
instance.nextCase()
}
else{
Textfield.text = "changing to cycling"
instance.nextCase()
}
x += 1
}
Updates: I am able to print one of the methods , .two (method two), after separator: "~". However, at the moment I am still not be able to click button to switch the case in the data.
The main problem is the initialization of variables. I am not able to define var instance = CaptureData(vertices: [SIMD3<Float>]) because it comes with error: Cannot convert value of type '[SIMD3<Float>].Type' to expected argument type '[SIMD3<Float>]'
I am sorry if my explanation is a bit messy here. I am trying to describe the problem I have here. Let me know if there is anything missing! Thank you so much in advance.
Enums is a data type that is more like a constant but much more readable.
An example will be passing in a status to a function.
enum Status {
case success
case failure
}
func updateStatus(_ status: Status) {
statusProperty = status
}
// somewhere in your code
instance.updateStatus(.success)
versus using an Int as a value.
func updateStatus(_ status: Int) {
statusProperty = status
}
// somewhere in your code
instance.updateStatus(1) // eventually you'll forget what this and you'll declare more of a global variable acting as constant, which technically what enums are for.
Enums in swift are a bit different though, much more powerful. More info about enums here
Back to the topic.
enum Mode: String, CaseIterable {
case one, two, three
}
extension CaseIterable where Self: Equatable {
var allCases: AllCases { Self.allCases }
var nextCase: Self {
let index = allCases.index(after: allCases.firstIndex(of: self)!)
guard index != allCases.endIndex else { return allCases.first! }
return allCases[index]
}
#discardableResult
func next() -> Self { // you don't need to update self here, remember self here is one of the items in the enum, i.e. one, so assigning one = two just doesn't work.
return self.nextCase
}
}
// The data struct
struct CaptureData {
var mode: Mode = .one
// we add a mutation function here so we can update the mode
mutating func nextCase() { // the data your concern about, that can actually mutate is the mode property inside CaptureData struct.
mode = mode.next()
}
}
So lets say somewhere in the app you can use it like this you initialised an instance of CaptureData:
var instance = CaptureData() // Don't forget it should be var and not let, as we are updating its property.
instance.nextCase() // get the next case, initially it was .one
print(instance.mode) // prints two
instance.nextCase() // from .two, changes to .three
print(instance.mode) // prints three
Hope this helps.
I am trying to filter my homeCollectionView with SegmentControl. On taping on the segment, I am filtering the content based on tag available in the dictionary. When I am performing ReloadData and switching between the segments, in the first go (when I am taping on the segments the first time), the filter is working and all data is coming, but when I tap back on the segments, part of the content in the cell, especially the LabelViews text are not showing up afterwards. Also, it's happening for random indexPath.
This is my code:
#objc func toggleHomeContent(_ notification: NSNotification) {
toggleValContType = notification.object as? String ?? "all"
if (toggleValContType == "all") {
mainArrayData = primaryArrayData
}
else if (toggleValContType == "collections") {
mainArrayData = primaryArrayData { $0["filterType"] == "Col" || $0["filterType"] == "CTA" }
}
else if (toggleValContType == "books") {
mainArrayData = primaryArrayData { $0["filterType"] == "Book" || $0["filterType"] == "CTA" }
}
homeCollectionView?.reloadData()
homeCollectionView?.layoutIfNeeded()
homeCollectionView?.collectionViewLayout.invalidateLayout()
//DispatchQueue.main.async(execute: homeCollectionView.reloadData)
}
And by arrays are declared like this:
var mainArrayData : [[String:String]] = HomeArray().mainArray
var primaryArrayData: [[String:String]] = HomeArray().mainArray
Heres the snapshot of what the issue is:
Snapshot of the issue
Thanks in advance!
Add this code in main thread :
homeCollectionView?.reloadData()
homeCollectionView?.layoutIfNeeded()
DispatchQueue.main.async {
homeCollectionView?.reloadData()
homeCollectionView?.layoutIfNeeded()}
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.
So I'm working on a quiz-app and I want to give the user an option screen before starting the quiz, where the user can choose which categories the quiz should have.
Its currently only working with 1 category at a time but I want to be able to concatenate the questionArrays when the user selects button in the option screen.
At this time the user selects a button and depending on the sender.tag an Int gets passed through the var called 'pickedCategory' which then decides which of the arrays to base the questions on. This happens in the prepare for segue:
let selectedCategory = quizCategories[pickedCategory]
secondVC.allQuestions = selectedCategory.questions
quizCategories is declared at the top:
var quizCategories = [QuestionArray]()
What I would like to do is have 4 buttons and whenever one is selected, concatenate an array to the selectedCategory
For instance, button 1 & 2 is selected, i.e. the user wants to have questions based on 2 categories. Resulting in something like this?
let selectedCategory = quizCategories[0].questions + quizCategories[1].questions
and if 3 buttons selected, add another quizCategories array to the final array etc etc
Please note, its not HOW to concatenate I'm looking for, its how to automatically do this depending on buttons selected..
This is my first question posted and I am very new to Swift.. hope I didn't confuse u guys.. thanks :)
What I would suggest is for each category button, keep track of which is selected (and even deselected if the user decides they don't want that category), by adding or removing from an array of categoryIDs. Then, once the "Done" button (or whatever final decision button is tapped), take the array of categoryIDs they selected, and flatMap over them to produce the ultimate array of questions.
let questions = categoryIDs.flatMap { quizCategories[$0].questions }
Now, you'd have your array of questions to present to the user. Use flatMap vs map, because you'd want to flatten the returned array of arrays into a single array.
Tags are not the best way to identify buttons, but I'll ignore that.
Don't use buttons, use UISwitches. Have a switch for each category.
Say you have a constant switchTagBase, with a value of 100:
let switchTagBase = 100
So the switch for your first category has a tag of 100, the next one has a tag of 101, etc.
You already have an array of categories, which is good.
Now, when it's time to build your array of selected questions, simply go through the categories, figure out which switches are on, and add in those categories
var selectedCategories = [QuestionArray]()
for (index, array) in quizCategories.enumerated() {
let thisTag = index + switchTagBase
guard let thisSwitch = view.viewWithTag(thisTag),
thisSwitch.isOn else { continue }
selectedCategories += quizCategories[index]
}
(Note that you should really maintain an array of selection states based on the switches the user activates, and iterate through that array rather than looping through the switches directly, but in the interest of brevity I fetched switch states directly.)
You can track which button is selected and when you want to start quizz, concatenate the corresponding arrays.
Your code could look like this:
import UIKit
class QuizzSelection: UIViewController
{
#IBAction func firstButtonAction(_ sender: Any)
{
firstButtonWasClicked()
}
#IBAction func secondButtonAction(_ sender: Any)
{
secondButtonWasClicked()
}
#IBAction func goForwardWithQuizz(_ sender: Any)
{
startQuizzWasClicked()
}
var isFirstCategorySelected = false
var isSecondCategorySelected = false
let firstCategoryQuestions = ["Question Cat 1"]
let secondCategoryQuestions = ["Question Cat 2"]
var emptyArrayOfQuestions = [String]()
func firstButtonWasClicked()
{
if isFirstCategorySelected == true{
isFirstCategorySelected = false
} else{
isFirstCategorySelected = true
}
}
func secondButtonWasClicked()
{
if isSecondCategorySelected == true{
isSecondCategorySelected = false
} else{
isSecondCategorySelected = true
}
}
func startQuizzWasClicked()
{
if isFirstCategorySelected == true{
emptyArrayOfQuestions += firstCategoryQuestions
}
if isSecondCategorySelected == true{
emptyArrayOfQuestions += secondCategoryQuestions
}
}
}
EDIT
Improved code for six categories:
import UIKit
class QuizzSelection: UIViewController
{
#IBAction func firstButtonAction(_ sender: Any)
{
firstButtonWasClicked()
}
#IBAction func secondButtonAction(_ sender: Any)
{
secondButtonWasClicked()
}
// Four mour button actions
#IBAction func goForwardWithQuizz(_ sender: Any)
{
startQuizzWasClicked()
}
var wichCategoryAreSelected = [false, false, false, false, false, false] //six categories
var arrayOfQuestions = [["Question 1 Cat 1","Question 2 Cat 1"], ["Question 1 Cat 2", "Question 2 Cat 2"], ...]
var emptyArrayOfQuestions = [String]()
func firstButtonWasClicked()
{
wichCategoryAreSelected[0] = !wichCategoryAreSelected[0]
}
func secondButtonWasClicked()
{
wichCategoryAreSelected[1] = !wichCategoryAreSelected[1]
}
func startQuizzWasClicked()
{
for i in 0...(wichCategoryAreSelected.count-1)
{
if wichCategoryAreSelected[i]
{
emptyArrayOfQuestions += arrayOfQuestions[i]
}
}
}
}
I have a table view where two cells have UITextView into which the user can enter long data. Following some answers here in Stackoverflow, I implemented a protocol/delegate to detect when the user has finished entering the data which then will be saved in a global dictionary:
class DetailsNewTaskViewController: UITableViewController, TextViewCellDelegate {
var cellData:[String:String] = [:]
func controllerView(controller: UITableViewCell, textViewDidEndEditing: String, atIndex:Int) {
switch(atIndex) {
case 0:
self.cellData["titolo"] = (controller as! LittleTextCell).textView.text
break
case 1:
self.cellData["oggetto"] = (controller as! BigTextCell).textView.text
break
default:
break
}
}
and this is the relative custom cell class:
class LittleTextCell: UITableViewCell, UITextViewDelegate {
#IBOutlet weak var label : UILabel!
#IBOutlet weak var textView : UITextView!
var delegate:TextViewCellDelegate?
var rowIndex:Int?
func textViewDidEndEditing(textView: UITextView) {
delegate?.controllerView(self, textViewDidEndEditing: textView.text, atIndex: rowIndex!)
}
}
where the delegate for textView is the class itself.
And this is a screenshot of the application:
The "problem" is that only AFTER the user taps another cell/field then the text is stored in the global dictionary. What about if the user taps "Fine" button (to save data) without having touched another field after he's finished entering the text? That a fatal nil error is raised. So I would like to know if there is a way to detect that the user has stopped typing in even if he's still inside that cell so that the content is always stored.
Is it possible? Is there a particular method to implement?
UPDATE: the function associated to "Fine" button:
func saveTask(sender:UIButton!) {
self.dateFormatter.dateFormat = "yyyy-MM-dd"
var taskToSave = Task(id: -1,
titolo: self.cellData["titolo"]!,
oggetto: self.cellData["oggetto"]!,
check_mail: self.cellData["check_mail"]!.toBool()!,
id_progetto: self.projects[self.cellData["progetto_nome"]!]!.id,
progetto_nome: nil,
assegnato_a: nil,
id_assegnato_a: self.users[self.cellData["assegnato_a"]!]!.id,
richiesto_da: nil,
id_richiesto_da: self.users[self.cellData["richiesto_da"]!]!.id,
priorita: self.cellData["priorita"]!,
termine_consegna: self.dateFormatter.dateFromString(self.cellData["termine_consegna"]!)!,
stato: self.cellData["stato"]!)
self.taskService.addTaskService(taskToSave) {
(response: String) in
if ((response.rangeOfString("Could not connect to the server.")) != nil) {
dispatch_async(dispatch_get_main_queue()) {
self.alertView.title = "Operazione fallita!"
self.alertView.message = "Impossibile connettersi al server. \n Riprovare."
self.alertView.delegate = self
self.alertView.addButtonWithTitle("OK")
self.alertView.show()
}
println(response)
}
else {
if ((response.rangeOfString("status code: 200")) != nil) {
dispatch_async(dispatch_get_main_queue()) {
self.alertView.title = "Operazione eseguita!"
self.alertView.message = "Task creato correttamente"
self.alertView.delegate = self
self.alertView.addButtonWithTitle("OK")
self.alertView.show()
self.navigationController?.popViewControllerAnimated(true)
}
}
else {
println(response)
}
}
}
}
define global selectedIndexPath
var selectedIndexPath:Int = 0
set it to selected indexPath
func controllerView(controller: UITableViewCell, textViewDidEndEditing: String, atIndex:Int) {
selectedIndexPath = indexPath
switch(atIndex) {
case 0:
self.cellData["titolo"] = (controller as! LittleTextCell).textView.text
break
case 1:
self.cellData["oggetto"] = (controller as! BigTextCell).textView.text
break
default:
break
}
}
In saveTask function get cell with cellForRowAtIndexPath
func saveTask(sender:UIButton!) {
let cell = tableView.cellForRowAtIndexPath(selectedIndexPath)
self.cellData["titolo"] = cell.LittleTextCell.textView.text
self.cellData["oggetto"] = cell.BigTextCell.textView.text
}
just use this line on the top in the method fine in your controller
self._tableView.superview?.endEditing(true);
I was facing the same problem , my textfields in cell and i want to check all the fields in controller.When i get my data so last data is not up to date because after last data, i click on the button not on the text field. So i found the solution. I wrote this line in my method (in your case, this line should be in fine method) before getting my dictionary and after that i had updated data.
Thanks