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.
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:
For the UI components, I have chosen to go this way:
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):
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:
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.