#Code Swift : UIScrollView and PageControl, add Custom Views (xib) dynamically

Massimo Mannoni
5 min readJul 19, 2019

--

In the last few months I’ve been focused on developing a mobile trading app for crypto markets and one of the issues I had to solve, was the creation, at runtime, of complex custom views, to use into a scrollview.

Basically the Custom View has a UITable inside in order to show some random data.

Let me say one thing before I start.I’m a .Net Developer, so my expertise in Swift started only 6 months ago, but I hope you’ll find something useful for your projects and this is why I’m sharing my experience.

Let’s start!

Once the project has been created, add a storyboard to create our first controller and flag it as “initial view controller”

To complete the design of our controller, add a UIScrollView and Page Control with relative outlets.

The next step concerns the design of your Custom View and a basic Model class to use for filling the UITable inside the Custom View.

The Model , “DataItem” is really simple, it’s an int array and could be like this.

import Foundationstruct DataItem  {var items = [Int]()init() {
items = []
}
init(number : Int) {
self.items.append(number)
}
}

Now it’s time to create our Custom View.

Basically it’s the view in which we added a UITableView “tbData” in my case. At the same create a CustomView class inherit from UIView and use it as Custom View from File’s Owner xib file.

class CustomView: UIView {@IBOutlet weak var tbData: UITableView!
@IBOutlet weak var customView: UIView!
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
override init(frame: CGRect) {
super.init(frame: frame)
}
}

As shown above, we created the two Outlets to our interface and now it’s time to Code ;)

Because it’s time to create our DataSource and Delegate to manage the table population and we prefer using, for simplicity ;), a different file “TableData.swift” .

import Foundationimport UIKitclass TableData : NSObject, UITableViewDataSource {  private var table: UITableView;
private var source : DataItem;
init(table: UITableView, source: DataItem) { self.table = table
self.source = source

super.init()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return source.items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = UITableViewCell() cell.textLabel?.text = String(source.items[indexPath.row]) return cell
}
}
class TableDelegate: NSObject, UITableViewDelegate { var homeController: HomeController?
}

Our “TableData” class inherits from NSOject and UITableViewDataSource, it has two private variable “table” types of UITableView and “source” type of DataItem, the model class we created at beginning.

Once the TableData constructor has invoked, strong references are created with our private variables.

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int)

will return the number of items stored in our array and

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

will create and return a UITableViewCell containing the item value in the same position of the row rendered.

class TableDelegate: NSObject, UITableViewDelegate {  var homeController: HomeController?
}

Also the delegate is created as “TableDelegate”. In this example I’ll not describe why we need a reference to the calling controller, but it will be useful on storyboard segue functions.

So going back to our Custom View, let’s complete it using our new classes ;)

First add a constructor, with the parameters we need to use

private var tableDelegate : TableDelegate!
private var tableData : TableData!
private var controller : HomeController!
private var dataItems : DataItem!
init(frame: CGRect, data : DataItem, controller : HomeController){ super.init(frame: frame) self.dataItems = data
self.controller = controller
}

Second register our Custom View

private func RegisterXib() { let xibCustomView = UINib(nibName: "CustomView", bundle: nil)                   xibCustomView.instantiate(withOwner: self, options: nil)  
customView.frame = bounds
addSubview(customView)
}

Third assign delegate and datasource

private func ConfigTable(){ tableDelegate = TableDelegate()
tableDelegate.homeController = self.controller
tableData = TableData(table: tbData, source: dataItems) tbData.dataSource = tableData
tbData.delegate = tableDelegate
}

and forth modify the init as shown below

init(frame: CGRect, data : DataItem, controller : HomeController){  super.init(frame: frame)  self.dataItems = data
self.controller = controller
RegisterXib() ConfigTable()
}

At this point we need to come back to our controller and add all functionality we need for creating and scrolling the Custom Views.

The “HomeController” need to inherit from UIScrollViewDelegate and a delegate must be assigned to our UIScrollView “scMain” so add the following code to the viewDidLoad() func:

scMain.delegate = self

allowing our controller to manage the scrollview functionality.

Now we have to create a function returning a random number, representing the number of views to create.

private func numberOfViews() -> Int {
return Int.random(in: 3..<7)
}

For each Custom View we need some data to display using our Model class DataItem and we could close this step with this code:

extension HomeController {func generateRundumNumber()->DataItem {  var data : DataItem = DataItem()  for _ in 1...10 {
let randomNumber = Int.random(in: 1..<100)
data.items.append(randomNumber)
}
return data }
}

The function returns 10 random numbers, between 1 and 100 added to a DataItem object.

The last two steps ;)

private func setupScrollView() {

let viewsToRender = numberOfViews()
pgView.numberOfPages = viewsToRender
pgView.currentPage = 0
scMain.contentSize = CGSize(width: view.frame.width * CGFloat(viewsToRender), height: view.frame.height) addCustomViews(numberOfViews: viewsToRender)
}

viewToRender: represent the number of view to create

pgView.numberOfPages: set the number of pages equal to the number of views to represent (one for each page)

scMain.contentSize: set the UIScrollView dimension making sure to multiply the “width” with the viewNumber so if width is 300px and we have 6 views, width will be 1800px.

addCustomViews is a call to the following function

private func addCustomViews(numberOfViews: Int) { var index : Int = 0 while index <= numberOfViews - 1
{
// design runtime frame x view
let frame = CGRect(x: self.view.frame.size.width * CGFloat(index), y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height)
// get array as data for tables
let dataItems = generateRundumNumber()
// design runtime view component passing it's data source
let customView = CustomView(frame: frame, data: dataItems, controller: self)
scMain.addSubview(customView) index += 1 }
}

The while cycle designs a frame for each view, gets the data using the relative function and invokes the CustomView constructor passing the relative data.

Once the Custom View is created we can proceed adding a UIScrollView Subview.

The last step is represented by the scroll function that is reported below :

func scrollViewDidScroll(_ scrollView: UIScrollView) { let pageIndex = round(scMain.contentOffset.x/view.frame.width)
pgView.currentPage = Int(pageIndex)
}

Basically the pageControl currentPage is the result of the proportion between the UISrollView Offset.x and the view.

The result ?

Need code ? Enjoy ;)

Thanks Massimo

--

--