I am new in swift and I was thinking a way to populate data ideally with segment control which I did not attempt before.
VC has layout below
Expecting result of selected CategoryType Segment Control to show list of data based on CategoryType on pickerview
This code is in trial and error mode, as I did not have an exact idea on how to execute the result I wish to obtain.
func appendDefaultCategoryTypes() {
categories = realm.objects(Category.self)
if categories.count == 0 {
try! realm.write() {
let defaultCategories = [
Category(type: .Expense, name: "EXPENSE 1"),
Category(type: .Expense, name: "EXPENSE 2"),
Category(type: .Income, name: "INCOME 1"),
Category(type: .Income, name: "INCOME 2"),
]
realm.add(defaultCategories)
}
}
}
//MARK: - Transaction Section
class Transaction : Object {
//Child of Transaction
let parentAccount = LinkingObjects(fromType: Account.self, property: "ofTransactions")
@objc dynamic var categoryType : Category?
@objc dynamic var amount : String = ""
@objc dynamic var date : String = ""
}
//MARK: - Transaction Category Section
enum CategoryType : String, CaseIterable {
case Income = "Income"
case Expense = "Expense"
static let allValues = [Income, Expense]
init?(id : Int) {
switch id {
case 1:
self = .Income
case 2:
self = .Expense
default:
return nil
}
}
}
class Category : Object {
@objc dynamic var type : String = CategoryType.Income.rawValue
@objc dynamic var name : String = ""
convenience init(type:CategoryType, name: String) {
self.init()
self.type = type.rawValue
self.name = name
}
}
//VC
var categories : Results<Category>!
var picker = UIPickerView()
@IBAction func categoryTypeSC(_ sender: UISegmentedControl) {
guard let selectedCategoryType = CategoryType.(rawValue: sender.selectedSegmentIndex) else {
fatalError("no corresponding category type for the index selected by segment control")
}
switch selectedCategoryType {
case .income :
print("Income in SC selected")
case .expense :
print("Expense in SC selected")
}
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
// if categorySCoutlet.selectedSegmentIndex == 0 {
// return CategoryType.income.count
// } else if categorySCoutlet.selectedSegmentIndex == 1 {
// return CategoryType.expense.count
// }
return categories.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
// if categorySCoutlet.selectedSegmentIndex == 0 {
// return
// } else if categorySCoutlet.selectedSegmentIndex == 1 {
// return
// }
// return "None"
return categories[row].name
}
In your view controller you need to keep track of the categories that correspond to the type indicated by the segmented control. I call it currentCategories
.
class ViewController: UIViewController {
@IBOutlet weak var segmentedControl: UISegmentedControl!
@IBOutlet weak var textField: UITextField!
var categories: Results<Category>!
var currentCategories: Results<Category>!
lazy var pickerView: UIPickerView = UIPickerView()
override func viewDidLoad() {
super.viewDidLoad()
let realm = try! Realm()
categories = realm.objects(Category.self)
appendDefaultCategoryTypes()
currentCategories = categories.filter("type == %@", CategoryType.income.rawValue)
textField.text = currentCategories.first?.name
textField.inputView = pickerView
pickerView.delegate = self
pickerView.dataSource = self
segmentedControl.addTarget(self, action: #selector(onCategoryTypeChanged(_:)), for: .valueChanged)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if touches.first?.view == view {
textField.resignFirstResponder()
}
}
}
When the segmented control value changes you need to refresh the picker so that the contents reflect the selected category type.
extension ViewController {
@IBAction func onCategoryTypeChanged(_ sender: UISegmentedControl) {
guard let type = CategoryType(id: sender.selectedSegmentIndex) else {
fatalError("no corresponding category type for the index selected by segment control")
}
currentCategories = categories.filter("type == %@", type.rawValue)
textField.text = currentCategories.first?.name
pickerView.reloadAllComponents()
pickerView.selectRow(0, inComponent: 0, animated: true)
}
}
In your picker data source and delegate methods you need to reference data from the categories that reflect the current type.
extension ViewController: UIPickerViewDelegate, UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return currentCategories.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return currentCategories[row].name
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
textField.text = currentCategories[row].name
}
}
Note that I took the liberty of changing a couple of things in your CategoryType
enum. Indexes should start at zero, and cases should be lowercased.
enum CategoryType : String, CaseIterable {
case income = "income"
case expense = "expense"
init?(id : Int) {
if id < CategoryType.allCases.count {
self = CategoryType.allCases[id]
} else {
return nil
}
}
}