1

I'm attempting to recreate something like in the iPhone's native weather app, where you can select multiple locations and they are appended as TableViews to a UIScrollView with UIPageControl

I'm starting simple with a hard-coded model in a 2D array. I want each array to go in each tableView, five tableViews total with one row in the first tableView, two in the second, etc:

var model: [[String]] = [
["TableView 1: row 1"],
["TableView 2: row 1", "TableView 2: row 2"],
["TableView 3: row 1", "TableView 3: row 2", "TableView 3: row 3"],
["TableView 4: row 1", "TableView 4: row 2", "TableView 4: row 3", "TableView 4: row 4"],
["TableView 5: row 1", "TableView 5: row 2", "TableView 5: row 3", "TableView 5: row 4", "TableView 5: row 5"]
]

Right now I can only wrap my head around something very simple, like what is shown below, so that the first tableView has 5 cells which say "TableView 1", then the second has five cells which say "TableView 2" and so on:

//number of rows
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 5
}

//cell for row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    for i in 0...model.count-1{
        //grab the correct tableView from the array of tableViews
        if tableView == self.tableViews[i]{
        //dequeue the cell using a nib
            let cell = tableView.dequeueReusableCell(withIdentifier: ProtoTypeTableViewCell.identifier, for: indexPath) as! ProtoTypeTableViewCell
        //set text for the label in the nib file
            cell.label.text = "TableView \(i+1)"
            return cell
        }
    }
        return UITableViewCell()
}

Any help would be greatly appreciated; thank you.

jmsapps
  • 388
  • 2
  • 17

1 Answers1

0

This was quite an interesting question to me since I do like to recreate UIs of popular apps. I think there are a few ways you can achieve this, I will show you one way.

I think the main challenges to solve:

  • The best UI components to use to set this up
  • Getting the right model mapped from the 2D array to the right table view

For the UI components, I have chosen to go this way:

  • Paging UIScrollView
    • UIStackView contained in the Paging UIScrollView
      • UITableView contained in the UIStackView

Here is how I went about doing this and added comments to explain

Initial set up

class PagingTableView: UIViewController
{
    // Same model as yours
    var model: [[String]] = [
    ["TableView 1: row 1"],
        
    ["TableView 2: row 1",
     "TableView 2: row 2"],
        
    ["TableView 3: row 1",
     "TableView 3: row 2",
     "TableView 3: row 3"],
        
    ["TableView 4: row 1",
     "TableView 4: row 2",
     "TableView 4: row 3",
     "TableView 4: row 4"],
        
    ["TableView 5: row 1",
     "TableView 5: row 2",
     "TableView 5: row 3",
     "TableView 5: row 4",
     "TableView 5: row 5"]
    ]
    
    let scrollView: UIScrollView =
    {
        let scrollView = UIScrollView()
        scrollView.isPagingEnabled = true
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        return scrollView
    }()
    
    let stackView: UIStackView =
    {
        let stackView = UIStackView()
        stackView.axis = .horizontal
        stackView.distribution = .equalSpacing
        stackView.translatesAutoresizingMaskIntoConstraints = false
        return stackView
    }()
    
    let pageControl = UIPageControl()
    
    let padding: CGFloat = 20
    let pageWidth = UIScreen.main.bounds.width
    
    let tableViewCellIdentifier = "cell"
    
    // Use this factor to unique identify your table
    let tableViewUniqueIdFactor = 1000
    
    // https://stackoverflow.com/a/21130486/1619193
    // You can ignore this function, created for convenience
    private func randomColor() -> UIColor
    {
        let red = CGFloat(arc4random_uniform(256)) / 255.0
        let blue = CGFloat(arc4random_uniform(256)) / 255.0
        let green = CGFloat(arc4random_uniform(256)) / 255.0
        
        return UIColor(red: red, green: green, blue: blue, alpha: 1.0)
    }

    override func viewDidLoad()
    {
        super.viewDidLoad()
        
        title = "Paging TableView"
        view.backgroundColor = .white
        
        // Configure everything, functions come later
        configureScrollViewLayout()
        configureStackViewLayout()
        configurePageControl()
        addTableViewsToStackView()
    }

Configure your scroll view layout

private func configureScrollViewLayout()
{
    scrollView.delegate = self
    
    view.addSubview(scrollView)
    
    // Auto layout
    scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
        .isActive = true
    
    scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor,
                                    constant: padding).isActive = true
    
    scrollView.widthAnchor.constraint(equalToConstant: pageWidth).isActive = true
    
    scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor,
                                    constant: -padding * 3).isActive = true
}

Configure your stack view layout

private func configureStackViewLayout()
{
    scrollView.addSubview(stackView)
    
    // Auto layout
    
    stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor)
        .isActive = true
    
    stackView.topAnchor.constraint(equalTo: scrollView.topAnchor)
        .isActive = true
    
    stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor)
        .isActive = true
    
    stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor)
        .isActive = true
    
    stackView.heightAnchor.constraint(equalTo: scrollView.heightAnchor,
                                      multiplier: 1).isActive = true
}

Configure your page control layout

private func configurePageControl()
{
    pageControl.numberOfPages = model.count
    pageControl.currentPage = 0
    pageControl.tintColor = randomColor()
    pageControl.pageIndicatorTintColor = randomColor()
    pageControl.currentPageIndicatorTintColor = randomColor()
    
    view.addSubview(pageControl)
    
    // Auto layout
    pageControl.translatesAutoresizingMaskIntoConstraints = false
    
    pageControl.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        .isActive = true
    
    pageControl.topAnchor.constraint(equalTo: scrollView.bottomAnchor)
        .isActive = true
    
    pageControl.widthAnchor.constraint(equalToConstant: 200)
        .isActive = true
    
    pageControl.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
        .isActive = true
}

Configure and add table views to the stack view

    private func addTableViewsToStackView()
    {
        for modelIndex in 0 ..< model.count
        {
            let tableView = UITableView()
            
            tableView.translatesAutoresizingMaskIntoConstraints = false
            
            // Uniquely identify each table which will come in handy
            // when figuring out which model should be loaded for a specific
            // table view
            tableView.tag = tableViewUniqueIdFactor + modelIndex
            
            // Register a default UITableView Cell
            tableView.register(UITableViewCell.self,
                               forCellReuseIdentifier: tableViewCellIdentifier)
            
            tableView.dataSource = self
            
            tableView.backgroundColor = randomColor()
            
            // remove additional rows
            tableView.tableFooterView = UIView()
            
            stackView.addArrangedSubview(tableView)
            
            tableView.widthAnchor.constraint(equalToConstant: pageWidth)
                .isActive = true
            
            // height is calculated automatically based on the height 
            // of the stack view
        }
    }
    
    // end of the class PagingTableView
}

At this stage, if you run this, you were to run the app, you will see the set up is like the weather app without the data in it and the paging control is not wired yet but we have the same number of pages as models (5):

Paging UIScrollView Paging UIStackView iOS Weather App Swift

Now what's left is to wire up the page control and the tableview to the model

Update the page view with the scrollview delegate

extension PagingTableView: UIScrollViewDelegate
{
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView)
    {
        // Make sure you don't do anything with table view scrolls
        // This is only a worry if the view controller is the table view's
        // delegate also
        if !(scrollView is UITableView)
        {
            let pageNumber = round(scrollView.contentOffset.x / scrollView.frame.size.width)
            pageControl.currentPage = Int(pageNumber)
        }
    }
}

Implement the table view data source to map the table to the model

extension PagingTableView: UITableViewDataSource
{
    func tableView(_ tableView: UITableView,
                   numberOfRowsInSection section: Int) -> Int
    {
        // Retrieve the correct model using the unique identifier created earlier
        let modelIndex = tableView.tag - tableViewUniqueIdFactor
        
        // Get the correct array needed from model
        let modelForeCurrentTable = model[modelIndex]
        
        return modelForeCurrentTable.count
    }
    
    func tableView(_ tableView: UITableView,
                   cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {
        let cell = tableView.dequeueReusableCell(withIdentifier: tableViewCellIdentifier)!
        
        // Retrieve the correct model using the unique identifier created earlier
        let modelIndex = tableView.tag - tableViewUniqueIdFactor
        
        // Get the correct array needed from model
        let modelForeCurrentTable = model[modelIndex]
        
        cell.textLabel?.text = modelForeCurrentTable[indexPath.row]
        
        cell.backgroundColor = .clear
        
        return cell
    }
}

Now everything is set up and you get something similar to the weather app with the right data showing in each table view and the page control wired up as well:

Paging UITableView UIScrollView Paging UIStackView iOS Weather App Swift

Final comments

You can skip the layout parts if you create your UI using frames or in storyboard. I tried to give you something from start to finish that you would run in isolation and you could play around with it by tweaking things.

Shawn Frank
  • 4,381
  • 2
  • 19
  • 29