Search code examples
swiftrealmuisegmentedcontrolpickerview

Using enums case as segment control to show types of data in pickerview


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

  • CategoryType Segment Control : to control CategoryType)
  • CategoryTextField with picker function : keyboard will show list of data from category

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
    }


Solution

  • 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
            }
        }
    }