【问题标题】:Reusing UIViewController (with UITableView) for Master/Detail View Controller in Master-Detail Application?在 Master-Detail 应用程序中为 Master/Detail View Controller 重用 UIViewController(带有 UITableView)?
【发布时间】:2019-01-19 13:18:51
【问题描述】:

我正在使用这个Ray Wenderlich tutorial 作为我正在从事的项目的指南。但是,我正在尝试扩展此模板以合并子目录、子子目录、子子子目录等。

例如,如果您单击主/主目录屏幕中的“Candy Cane”单元格,它将带您进入带有表格视图(也称为子目录屏幕)的“新”视图控制器,其中将显示各种拐杖糖供应商以及他们的价格是贵还是便宜(这将是每一行的副标题)。请参阅下面的subCategory... txt 文件。

我在这篇文章中交替使用类别和目录这两个词。

这是第一个问题。过去,我创建了一个项目来做这件事,但是我通过许多故事板和视图控制器文件来完成我导航的每个子类别的工作。同样的原则也适用于从子类别屏幕移动到子子类别屏幕等等。

我不想做广告:但具体来说,我正在开发的应用名为iEngineering,它可以在 AppStore 上免费下载。请查看应用程序的库部分,因为我相信它会为理解我的最终目标提供额外帮助。请记住,当前版本的应用程序是使用无数故事板和视图控制器文件构建的,每次我在 Xcode 中运行模拟器时都必须构建这些文件。无需下载应用,查看显示库功能的应用屏幕截图就足够了。

这是第二个问题。我在这个应用程序中也有子子类别等等。例如,如果您单击子类别屏幕中显示拐杖糖供应商和价格列表的单元格,它将导航到一个新屏幕(子子类别),以显示该特定供应商的所有不同拐杖糖销售量。因此,在下面我编造的文本文件subCandyCaneTextFile.txt 中,如果您单击带有“Candy Cane King”标签的单元格,它将带您进入一个显示“Candy Cane King”作为导航标题的屏幕,并呈现/加载一个列表(通过阅读文本文件导入),显示 Candy Cane King 提供的所有糖果手杖。 我没有提供这个和其他我认为是子子类别的 txt 文件。当我在下面问我问题的根本/核心时,我希望大家记住这一点。

对于这个项目,我想远离使用带有许多视图控制器的多个故事板。这是因为每次我在模拟器中构建它时,我之前的项目都需要大约 5 到 10 分钟来编译/构建。因此,我试图在项目运行时为我的应用程序中的每个新/类别屏幕读取文本文件。我希望这会给我带来更快的运行时间(约 10 秒)。

在下面的示例中,类别/主视图控制器通过读取 txt 文件 candyTextFile.txt 来呈现其数据

我的问题:我怎样才能为子类别屏幕处理(从|删除)旧数据和(加载|替换)新数据?我曾想过尝试将其作为Ray Wenderlich tutorial 教程中的详细视图控制器屏幕;但是,我不确定如何导航到子子类别屏幕。因此,我认为我的另一个选择可能是一遍又一遍地使用 UITableView(主视图控制器)重用一个视图控制器。我不确定解决这个问题的正确方法,因为我对学习 Swift 和 Xcode 还比较陌生。

这篇文章的current project via Xcode 概述。

在我的 MasterViewController.swift 中,

    import UIKit

    class MasterViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    // MARK: - Properties
    @IBOutlet var tableView: UITableView!
    @IBOutlet var searchFooter: SearchFooter!

    var detailViewController: DetailViewController? = nil

    var candies = [Candy]()
    var filteredCandies = [Candy]()

    let searchController = UISearchController(searchResultsController: nil)

    // MARK: - View Setup
    override func viewDidLoad() {
    super.viewDidLoad()

    // Setup the Search Controller
    searchController.searchResultsUpdater = self
        searchController.obscuresBackgroundDuringPresentation = false
    searchController.searchBar.placeholder = "Search Candies"
    navigationItem.searchController = searchController
    definesPresentationContext = true

    // Setup the Scope Bar
    searchController.searchBar.scopeButtonTitles = ["All", "Chocolate","Hard", "Other"]
    searchController.searchBar.delegate = self

    // Setup the search footer
    tableView.tableFooterView = searchFooter


    setupArray()



    if let splitViewController = splitViewController {
        let controllers = splitViewController.viewControllers
        detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
    }

    }


    private func setupArray() {
        if let filepath = Bundle.main.path(forResource: "candyTextFile", ofType: "txt") {
            do {
                let contents = try String(contentsOfFile: filepath)
                let lines_separatedBy_n : [String] = contents.components(separatedBy: "\n")
                let string = lines_separatedBy_n.map { String($0) }.joined(separator: ", ")
                var lines_separatedBy_comma : [String] = string.components(separatedBy: ", ")

                // I've put this in to remove the last bit of the file that was causing the count to be one too high.
                // I'm guessing that's why you had something similar previously?
                lines_separatedBy_comma.removeLast()

                for (index, element) in lines_separatedBy_comma.enumerated() {
                    if index % 2 == 0 {
                        let newCategory = element
                        let newName = lines_separatedBy_comma[index + 1]
                        let newCandy = Candy(category: newCategory, name: newName)
                        candies.append(newCandy)
                    }
                }
                for candy in candies {
                    print("category: \(candy.category), name: \(candy.name)")
                }
                //("\ncandies: \(candies)")
            } catch let error as NSError {
                print(error.localizedDescription)
            }
        }

            print("\ncandies: \(candies)")

    }

    override func viewWillAppear(_ animated: Bool) {
    print("splitViewController!.isCollapsed: \(splitViewController!.isCollapsed)")

    if splitViewController!.isCollapsed {
      if let selectionIndexPath = tableView.indexPathForSelectedRow {
        tableView.deselectRow(at: selectionIndexPath, animated: animated)
      }
    }
    super.viewWillAppear(animated)
    }

    override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    }

    // MARK: - Table View
    func numberOfSections(in tableView: UITableView) -> Int {
    return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if isFiltering() {
        print("Is filtering")
        searchFooter.setIsFilteringToShow(filteredItemCount: filteredCandies.count, of: candies.count)
        return filteredCandies.count
    }
    print("Is not filtering")
    searchFooter.setNotFiltering()
    return candies.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
    let candy: Candy
    if isFiltering() {
        candy = filteredCandies[indexPath.row]
    } else {
        candy = candies[indexPath.row]
    }
    cell.textLabel!.text = candy.name
    cell.detailTextLabel!.text = candy.category
    return cell
    }

    // MARK: - Segues
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "showDetail" {
            if let indexPath = tableView.indexPathForSelectedRow {
                let candy: Candy
                if isFiltering() {
                    candy = filteredCandies[indexPath.row]
                } else {
                    candy = candies[indexPath.row]
                }
                let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
                controller.detailCandy = candy
                controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
                controller.navigationItem.leftItemsSupplementBackButton = true
            }
        }
    }

    func filterContentForSearchText(_ searchText: String, scope: String = "All") {


        filteredCandies = candies.filter({
            (candy : Candy) -> Bool in
        let doesCategoryMatch = (scope == "All") || (candy.category == scope)
            if searchBarIsEmpty(){
                return doesCategoryMatch
            }else {
                return doesCategoryMatch && candy.name.lowercased().contains(searchText.lowercased())

            }
        })

        tableView.reloadData()
    }


    func searchBarIsEmpty() -> Bool {
        return searchController.searchBar.text?.isEmpty ?? true
    }

    func isFiltering() -> Bool {
        let searchBarScoperIsFiltering = searchController.searchBar.selectedScopeButtonIndex != 0
        return searchController.isActive && (!searchBarIsEmpty() || searchBarScoperIsFiltering)
    }


    }

    extension MasterViewController: UISearchBarDelegate {
    // MARK: - UISearchBar Delegate
    func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
        filterContentForSearchText(searchBar.text!, scope: searchBar.scopeButtonTitles![selectedScope])
    }

    }

    extension MasterViewController: UISearchResultsUpdating {
    // MARK: - UISearchBar Delegate
    func updateSearchResults(for searchController: UISearchController) {
        let searchBar = searchController.searchBar
        let scope = searchBar.scopeButtonTitles![searchBar.selectedScopeButtonIndex]
        filterContentForSearchText(searchController.searchBar.text!, scope: scope)
    }
    }

在candyTextFile.txt中,

Chocolate, Chocolate Bar
Chocolate, Chocolate Chip
Chocolate, Dark Chocolate
Hard, Lollipop
Hard, Candy Cane
Hard, Jaw Breaker
Other, Caramel
Other, Sour Chew
Other, Gummi Bear
Other, Candy Floss
Chocolate, Chocolate Coin
Chocolate, Chocolate Egg
Other, Jelly Beans
Other, Liquorice
Hard, Toffee Apple

在 subCandyCaneTextFile.txt 中,

Cheap, Brachs
Expensive, Spangler
Expensive, Bobs
Cheap, Candy Cane King
Expensive, Jelly Belly

其他,子类别,文件:

在 subDarkChocolateTextFile.txt 中,

Cheap, Ghirardelli
Expensive, Dove
Expensive, Lindt
Cheap, Hersheys
Expensive, Hu Dark

在 subLollipopTextFile.txt 中,

Cheap, Zollipops
Cheap, YumEarth
Expensive, Dum Dums

感谢大家抽出宝贵的时间和大家可以提供的任何指导。非常感谢。

【问题讨论】:

  • 在评论了两个不够具体的问题后,我很抱歉,但您的问题太具体了! (而且罗嗦,我一直被指责。)我怎样才能复制你的问题?这是我对你的具体问题。我有可能工作的代码。子类UINavigationControllersUITableViews 之间导航,在UIViewController 中使用,可以作为从单一“层”到四“层”的任何方式来实现所需。它可能不适用于您的问题,但是您问的方式我只是不知道....
  • @dfd 哈哈,我很抱歉。我的总体问题是我不知道如何使用表视图创建一个可重用的视图控制器来创建一个大型目录列表应用程序,该应用程序从文本文件中为每个新的表视图列表导入新数据。我不知道我提到的两种方法是否是解决这个问题的正确方法。
  • 方法一:将detail view controller设为子类屏。但是,我如何导航到子子类别屏幕等等?
  • 方法二:重复使用带有表视图的单个视图控制器。我觉得这是我应该做的,但我完全不知道如何开始。

标签: ios swift xcode uitableview


【解决方案1】:

您可以重复使用同一故事板中的DetailViewController。为其分配一个Storyboard ID,然后您可以从当前实例创建DetailViewController 类的新实例。您可以保留新 VC 的索引,例如递增它并使用它例如从预定义的数组中获取下一个类别文件名并加载它,例如在 viewDidLoad

DetailViewController 类中:

    var index = 0;

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let st = UIStoryboard(name: "Main", bundle: nil)
        let vc = st.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
        vc.index = self.index + 1;
        self.navigationController?.pushViewController(vc, animated: true)
    }

编辑

如何使用索引

用你的文件名声明数组:let files = ["file1", "file2", "file3"]

setupArray() 使用它

if let filepath = Bundle.main.path(forResource: files[index], ofType: "txt")

【讨论】:

  • 我很抱歉回来晚了。我是一名本科工程专业的学生,​​我的春季学期开始回升。您是否介意向我指出如何保持需要显示的视图控制器数据的索引的正确方向?我看到其他人谈论这个,但它总是让我感到困惑。总之,我迷失了如何将索引与需要为详细视图控制器导入的正确数据连接起来?然后如何让“新”索引为下一次数据导入做好准备。对不起,如果这有点罗嗦。
猜你喜欢
  • 2013-01-07
  • 2013-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多