【问题标题】:What is a better way of handling this type of issue?处理此类问题的更好方法是什么?
【发布时间】:2020-06-02 12:59:00
【问题描述】:

我对使用 Swift 进行 iOS 开发有点陌生。所以,我可能在 Swift 中遗漏了一个我不知道的简单解决方案。 我正在开发一个 tvOS 应用程序,在其中显示用户可以从中选择的视频内容列表。该应用程序还包含一个设置选项卡,允许用户配置 5 种不同类型的设置。一旦他们选择了一个特定的类别,它就会显示一个新的表格视图,其中包含选项数组中的相应选项。这就是我需要帮助的“问题”。

我有这个结构,我将其用作单例:

struct BMUserSettings
{
    internal static var shared = BMUserSettings()
    var categories = [String]()
    var options = [[String]]()
    var currOptionsSelected: [Int] = [0,0,0,0,0] // This array corresponds to the categories array.  It tells us what option within that group was selected.
    init()
    {
        self.categories = ["Brand", "Environment","UI Language", "Playback Language", "Geo Location Permission"]

        let brandOptionsGroup: [String] = ["CTV", "CTVHUB", "TSN", "Snackable", "RDS", "CP24", "BNN", "CTVNews", "Crave", "BRAVO", "E_BRAND", "SE", "VIDIQA"]
        let environmentOptionsGroup: [String] = ["Staging", "Prod"]
        let uiLanguageOptionsGroup: [String] = ["en", "fr"]
        let playbackLanguageOptionsGroup: [String] = ["en", "fr"]
        let geoLocationOptionsGroup: [String] = ["Allow", "Don't Allow"]

        options.append(brandOptionsGroup)
        options.append(environmentOptionsGroup)
        options.append(uiLanguageOptionsGroup)
        options.append(playbackLanguageOptionsGroup)
        options.append(geoLocationOptionsGroup)
    }

    // MARK: - Custom Methods
    func displayUserSettings() -> String
    {
        let displayText: String = "Brand=\(options[0][currOptionsSelected[0]])     Environment=\(options[1][currOptionsSelected[1]])     UI Language=\(options[2][currOptionsSelected[2]])     Playback Language=\(options[3][currOptionsSelected[3]])     Geo Location=\(options[4][currOptionsSelected[4]])"
        return displayText
    }

    // MARK: - User Defaults
    func saveToUserDefaults()
    {
        UserDefaults.standard.set(BMUserSettings.shared.currOptionsSelected, forKey: "currentoptions")
    }

    func loadFromUserDefaults(){
        if let currentOptionsSelected = UserDefaults.standard.object(forKey: "currentoptions") as? [Int]{
            BMUserSettings.shared.currOptionsSelected = currentOptionsSelected
        }
        else{
            BMUserSettings.shared.currOptionsSelected = [0,0,0,0,0]
        }
    }
}

如您所见,“currOptionsSelected”整数数组包含用户为每个类别选择的选项。例如,如果用户选择品牌“Snackable”,则 currOptionsSelected 数组的第一个元素将保存 3 作为值。

我正在保存和加载 currOptionsSelected 到/从 UserDefaults 中,以便我知道用户的当前设置是什么。

这种方法的问题是: 1)即使我知道用户选择的特定选项的索引,我仍然需要设置 if-else 或 switch 条件,以确保我实际上可以从相应的“选项”数组中获取正确的字符串值 2)如果任何其他开发人员需要添加类别和相应的选项,那么他们需要确保他们保持一切井井有条 3) 我只是不知道这是否是处理此类问题的最佳方式

有什么更好的方法?


这是我尝试在表格视图中使用它的方式:

import UIKit

final class BMSettingsViewController: UIViewController
{
    // MARK: - Instance Variables
    private static let reuseIdentifier = String(describing: BMContentCell.self)
    private let tableview = UITableView(backgroundColor: .white, autoResizingMask: false)
    private let tabBarBannerHeight: CGFloat = 150
    private var selectedCategoryIndex: Int = 0
    private var settingsDetailVC: BMDetailSettingsViewController?
    private var categoryNames: [String] = [String]()
    private var categoryOptions: [String] = [String]()

    // MARK: - View Lifecycle Methods
    override func viewWillAppear(_ animated: Bool)
    {
        super.viewWillAppear(animated)
        categoryNames = BMCategory.allValues
        categoryOptions = BMUserSettings.shared.currOptionsSelected.map { $0.value }
        self.tableview.reloadData()
    }

    override func viewWillDisappear(_ animated: Bool)
    {
        BMUserSettings.shared.saveToUserDefaults()
    }

    override func loadView()
    {
        super.loadView()
        self.tableview.dataSource = self
        self.tableview.delegate = self
        self.tableview.register(BMUserSettingsCell.self, forCellReuseIdentifier: BMSettingsViewController.reuseIdentifier)
        displayContent()
    }

    // MARK: - Custom Methods
    private func displayContent()
    {
        view.addSubview(tableview)
        tableview.anchor(
            top: self.view.topAnchor,
            leading: self.view.leadingAnchor,
            bottom: self.view.bottomAnchor,
            trailing: self.view.trailingAnchor,
            padding: UIEdgeInsets(top: tabBarBannerHeight, left: 0, bottom: 0, right: 0)
        )
    }
}

// MARK: - UITableView Datasource & Delegate Extension
extension BMSettingsViewController: UITableViewDataSource, UITableViewDelegate
{
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
    {
        return "Select an option below to configure it's settings..."
    }

    func numberOfSections(in tableView: UITableView) -> Int
    {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    {
        return categoryNames.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {
        let cell = tableView.dequeueReusableCell(withIdentifier: BMSettingsViewController.reuseIdentifier, for: indexPath) as! BMUserSettingsCell
        cell.configureCell(categoryName: categoryNames[indexPath.row], optionDetailDescription: categoryOptions[indexPath.row])
        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
    {
        let selectedCategory = categoryNames[indexPath.row]
        settingsDetailVC = BMDetailSettingsViewController()
        guard let settingsVC = settingsDetailVC else {return}
        settingsVC.options = BMUserSettings.shared.options[BMCategory.init(rawValue: selectedCategory)!]!
        settingsVC.delegate = self
        settingsVC.selectedCategoryIndex = indexPath.row
        BMViewControllerManager.shared.getTopViewController()?.present(settingsVC, animated: true)
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
    {
        return 120
    }
}

// MARK: - Protocol Extension
extension BMSettingsViewController: OptionsSelector
{
    func didFinishSelectingOption(selectedCategoryIndex: Int, selectedOptionIndex: Int)
    {
        self.selectedCategoryIndex = selectedCategoryIndex
//      BMUserSettings.shared.currOptionsSelected[self.selectedCategoryIndex] = selectedOptionIndex
    }
}

以下是设置详细信息控制器,其中仅列出了该特定类别中的选项:

import UIKit

// MARK: - Protocol (used to notify Settings view controller when an option was selected)
protocol OptionsSelector
{
    func didFinishSelectingOption(selectedCategoryIndex: Int, selectedOptionIndex: Int)
}

final class BMDetailSettingsViewController: UIViewController
{
    // MARK: - Instance Variables
    private let cellId = "cellId"
    private let tabBarBannerHeight: CGFloat = 150   
    private var selectedOptionIndex: Int = 0
    private let tableview = UITableView(backgroundColor: .white, autoResizingMask: false)

    var options: [String] = [String]()
    var selectedCategoryIndex: Int = 0
    var delegate: OptionsSelector?

    // MARK: - View Life Cycle Methods
    override func loadView()
    {
        super.loadView()
        self.tableview.dataSource = self
        self.tableview.delegate = self
        self.tableview.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
        displayContent()
    }

    // MARK: - Custom Methods
    private func displayContent()
    {
        view.addSubview(tableview)
        tableview.anchor(
            top: self.view.topAnchor,
            leading: self.view.leadingAnchor,
            bottom: self.view.bottomAnchor,
            trailing: self.view.trailingAnchor,
            padding: UIEdgeInsets(top: tabBarBannerHeight, left: 0, bottom: 0, right: 0)
        )
    }
}

// MARK: - UITableView Datasource & Delegate Extension
extension BMDetailSettingsViewController: UITableViewDataSource, UITableViewDelegate
{
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return "Select an option below..." }
    func numberOfSections(in tableView: UITableView) -> Int { return 1 }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return options.count }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {
        let cell = UITableViewCell(style:  .subtitle, reuseIdentifier: nil)
        cell.textLabel?.text = "\(options[indexPath.row])"
        return cell
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
    {
        BMViewControllerManager.shared.getTopViewController()?.dismiss(animated: true)
        self.selectedOptionIndex = indexPath.row
        self.delegate?.didFinishSelectingOption(selectedCategoryIndex: self.selectedCategoryIndex, selectedOptionIndex: self.selectedOptionIndex)
    }
}

谢谢!

【问题讨论】:

  • currOptionsSelected 设为字符串数组,以便您立即了解类别。更好的是,让它成为一个集合,而不是一个数组。更好的是,将类别定义为枚举。
  • 你能告诉我你的意思吗?
  • 数组options是干什么用的?
  • options 包含每个类别的单独选项。这是一个多维数组...
  • 这样你就可以解决 2) 我可能会转向字典结构。 var options = [OptionsKey, [String]]。我会做一个enum OptionsKey: String { case brandOptionsGroup, environmentOptionsGroup, etc}。访问该字典类似于let array = options[.brandOptionsGroup] 添加另一个类别不会成为问题。

标签: ios arrays swift uitableview multidimensional-array


【解决方案1】:

首先创建一个enum Category

enum Category: String {
    case brand = "Brand"
    case environment = "Environment"
    case uiLanguage = "UI Language"
    case playbackLanguage = "Playback Language"
    case geoLocationPermission = "Geo Location"
}

接下来,

  1. 创建[Category:[String]]类型的options[Category:String]类型的currOptionsSelecteddefaultOptions类型的[Category:String]

  2. 另外,代替displayUserSettings,将struct BMUserSettingsCustomStringConvertible 一致并实现description 以返回相关的String 值。

  3. 并且,要创建 Singleton,请将 init() 标记为 private

  4. 无需为categories 创建单独的array

所以整个struct BMUserSettings 会像,

struct BMUserSettings: CustomStringConvertible {
    static var shared = BMUserSettings()

    let options: [Category:[String]]
    let defaultOptions: [Category:String]
    var currOptionsSelected: [Category:String]
    let categories: [Category]

    private init() {
        options = [
            .brand : ["CTV", "CTVHUB", "TSN", "Snackable", "RDS", "CP24", "BNN", "CTVNews", "Crave", "BRAVO", "E_BRAND", "SE", "VIDIQA"],
            .environment : ["Staging", "Prod"],
            .uiLanguage : ["en", "fr"],
            .playbackLanguage : ["en", "fr"],
            .geoLocationPermission : ["Allow", "Don't Allow"]
        ]

        defaultOptions = self.options.mapValues{ $0.first! }
        currOptionsSelected = self.defaultOptions
        categories = [.brand, .environment, .uiLanguage, .playbackLanguage, .geoLocationPermission]
    }

    var description: String {
        return self.currOptionsSelected.reduce("") { (result, option) -> String in
            return "\(result) \(option.key.rawValue) = \(option.value)\n"
        }
    }

    // MARK: - User Defaults
    func saveToUserDefaults() {
        var dict = [String:String]()
        currOptionsSelected.forEach { dict[$0.key.rawValue] = $0.value }
        UserDefaults.standard.set(dict, forKey: "currentoptions")
    }

    mutating func loadFromUserDefaults() {
        if let currentOptionsSelected = UserDefaults.standard.object(forKey: "currentoptions") as? [String:String] {
            var dict = [Category:String]()
            currentOptionsSelected.forEach {
                if let category = Category(rawValue: $0.key) {
                    dict[category] = $0.value
                }
            }
            self.currOptionsSelected = dict
        }
        else {
            self.currOptionsSelected = self.defaultOptions
        }
    }
}

按如下方式使用,

BMUserSettings.shared.currOptionsSelected[.brand] = "Snackable"
BMUserSettings.shared.saveToUserDefaults()
BMUserSettings.shared.loadFromUserDefaults()
print(BMUserSettings.shared)
BMUserSettings.shared.categories.forEach {
    print($0.rawValue, ":", BMUserSettings.shared.currOptionsSelected[$0]!)
}

【讨论】:

  • 优秀的解决方案!谢谢!
  • 此解决方案存在缺陷。我收到以下错误:“同时访问 0x100b53618,但修改需要独占访问。”。它发生在 loadFromUserDefaults() 中的 else 条件
  • 改用self.currOptionsSelected = self.defaultOptions
  • 其实,不……还有一个问题。问题是类别名称的顺序与 currOptionsSelected 的顺序不同......基本上,我试图让它们彼此同步,但它们的顺序不同。
  • 你是个天才!
猜你喜欢
  • 1970-01-01
  • 2016-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-08
  • 1970-01-01
  • 1970-01-01
  • 2019-04-14
相关资源
最近更新 更多