HW#38 TableView & Custom Cell- MovieList & Passing data
Hello everyone, today I’ll show you how to do tableView & customCell in Swift’s storyboard.
Here is reference base on Peter Pan article:
Summary:
I find the most interesting part of coding to be solving problems, creating things, and making them happen. I know that the tableView is quite important for iOS development, so I’m trying to use both the storyboard and programmatic methods to implement a tableView.
Initially, I wanted to use AVPlayerViewController to show movie trailers after a user taps a tableViewCell.
However, due to copyright issues, I’ve decided to use WebKit to display movie trailers on YouTube, which seems like a safer choice for showing movies without any risk.
Subject:
- Create the data model.
- Custom Cell by Storyboard.
- Add segment Control in
NavigationBar
- Setup
tableView
. - Passing Data from
tableViewCell
to webView. - Setup Web view and setup forward Button for last page & back button for next page.
1. Create the data models:
Create a struct
named MovieList
and define the movieName
/ movieTitle
/ url
as strings.
struct MovieList {
var movieName : String
var movieTitle: String
var url: String
}
I created two different data arrays for different subjects, one for startWar
another for starTrek
, which are my favorite movie series.
StarWar:
let movieList = [
MovieList(movieName: "Star Wars episode 1", movieTitle: "The Phantom Menace", url: "https://www.youtube.com/watch?v=bD7bpG-zDJQ"),
MovieList(movieName: "Star Wars episode 2", movieTitle: "Attack of the Clones", url: "https://www.youtube.com/watch?v=gYbW1F_c9eM"),
MovieList(movieName: "Star Wars episode 3", movieTitle: "Revenge of the Sith", url: "https://www.youtube.com/watch?v=5UnjrG_N8hU"),
MovieList(movieName: "Star Wars episode 4", movieTitle: "A New Hope", url: "https://www.youtube.com/watch?v=vZ734NWnAHA"),
MovieList(movieName: "Star Wars episode 5", movieTitle: "The Empire Strikes Back", url: "https://www.youtube.com/watch?v=JNwNXF9Y6kY"),
MovieList(movieName: "Star Wars episode 6", movieTitle: "Return of the Jedi", url: "https://www.youtube.com/watch?v=7L8p7_SLzvU"),
MovieList(movieName: "Star Wars episode 7", movieTitle: "The Force Awakens", url: "https://www.youtube.com/watch?v=sGbxmsDFVnE"),
MovieList(movieName: "Star Wars episode 8", movieTitle: "The Last Jedi", url: "https://www.youtube.com/watch?v=Q0CbN8sfihY"),
MovieList(movieName: "Star Wars episode 9", movieTitle: "The Rise of Skywalker", url: "https://www.youtube.com/watch?v=8Qn_spdM5Zg")
]
StarTrek:
let starTrekMovieList = [
MovieList(movieName: "Star Trek", movieTitle: "The Motion Picture",
url: "https://www.youtube.com/watch?v=zrXvBaFFu80"),
MovieList(movieName: "Star Trek II", movieTitle: "The Wrath of Khan",
url: "https://www.youtube.com/watch?v=WCpYqWAIwFA"),
MovieList(movieName: "Star Trek III", movieTitle: "The Search for Spock",
url: "https://www.youtube.com/watch?v=mkJ3--2K7yo"),
MovieList(movieName: "Star Trek IV", movieTitle: "The Voyage Home",
url:"https://www.youtube.com/watch?v=zZH3OD9d9Sc&list=PLZbXA4lyCtqp8DAiaHxYkC7u1drvVHMCu"),
MovieList(movieName: "Star Trek V", movieTitle: "The Final Frontier",
url: "https://www.youtube.com/watch?v=7S5TDrAWBd8"),
MovieList(movieName: "Star Trek VI", movieTitle: "The Undiscovered Country",
url: "https://www.youtube.com/watch?v=VPz-6HuM8Sc"),
MovieList(movieName: "Star Trek", movieTitle: "Generations",
url: "https://www.youtube.com/watch?v=w9Ubnn2vP6Y"),
MovieList(movieName: "Star Trek", movieTitle: "First Contact",
url: "https://www.youtube.com/watch?v=D7KCb-O20Fg"),
MovieList(movieName: "Star Trek", movieTitle: "Insurrection",
url: "https://www.youtube.com/watch?v=I7cb4ZK9WV4"),
MovieList(movieName: "Star Trek", movieTitle: "Nemesis",
url: "https://www.youtube.com/watch?v=9wD5S1mWl0I"),
MovieList(movieName: "Star Trek", movieTitle: "Star Trek",
url: "https://www.youtube.com/watch?v=a3VbUpscgOc"),
MovieList(movieName: "Star Trek", movieTitle: "Into Darkness",
url: "https://www.youtube.com/watch?v=QAEkuVgt6Aw"),
MovieList(movieName: "Star Trek", movieTitle: "Beyond",
url: "https://www.youtube.com/watch?v=rf5_lKS42mc")
]
2. Custom Cell by Storyboard:
MovieTableViewCell:
I’m using storyboard to create a custom cell and setup the auto-layout for cell.
- imageView
- titleLabel
- detailLabel
Create a function named nib
that returns a UINib
object with the nibName MovieTableViewCell
.
What is nib?(we could throw back to old documentation from Apple)
Set up a string identifier
named MovieTableViewCell
so that we can use this identifier
in the next steps.
import UIKit
class MovieTableViewCell: UITableViewCell {
@IBOutlet weak var movieImageView: UIImageView!
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var detailLabel: UILabel!
static let identifier = "MovieTableViewCell"
static func nib() -> UINib {
return UINib(nibName: "MovieTableViewCell", bundle: nil)
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
movieImageView.layer.cornerRadius = movieImageView.bounds.height / 2
movieImageView.clipsToBounds = true
titleLabel.adjustsFontSizeToFitWidth = true
detailLabel.adjustsFontSizeToFitWidth = true
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Explain Static
:
Reference:
3. Add custom segment Control in NavigationBar
Create a function named segmentedControl
to add a UISegmentedControl
into the titleView
of the navigationItem
.
func segmentedControl () {
let items: [String] = ["Star Wars", "Star Trek"]
segmentedController = UISegmentedControl(items: items)
segmentedController.selectedSegmentIndex = 0
print(segmentedController.selectedSegmentIndex)
navigationItem.titleView = segmentedController
segmentedController.addTarget(self, action: #selector(segmentedControlValueChanged) , for: .valueChanged)
}
4. Setup tableView:
Before creating a tableView
, it's important to understand the resources needed for its setup, namely dataSource
and delegate
.
The dataSource
is responsible for providing the data that the table will display. This includes specifying the number of rows, the data each cell contains, and other data-related aspects.
On the other hand, the delegate
handles events related to the appearance and functionality of the tableView
.
These responsibilities include responding to user interactions, specifying the height of rows, and other aspects of the table's behavior and style. Therefore, at the beginning, our code needs to properly configure both the dataSource
and the delegate
for the tableView
.
tableView.dataSource = self
tableView.delegate = self
I have created a function named setupTableViewDelegate
to configure the delegate and dataSource of the tableView.
In addition, this function registers the custom tableView cells, StarTrekTableViewCell
and MovieTableViewCell
, which were created in the previous step under CustomCell.
func setupTableViewDelegate () {
self.navigationItem.title = ""
tableView.dataSource = self
tableView.delegate = self
tableView.rowHeight = 100
self.tableView.isEditing = false
// Register the starWar cell to xib cell.
tableView.register(MovieTableViewCell.nib(), forCellReuseIdentifier: MovieTableViewCell.identifier)
// Register the starTrek cell to xib cell.
tableView.register(StarTrekTableViewCell.nib(), forCellReuseIdentifier: StarTrekTableViewCell.identifier)
segmentedControl ()
}
cellForRowAt:
- Asks the data source for a cell to insert in a particular location of the table view.
Firstly, we create an instance named starWarCell
by using the dequeueReusableCell(withIdentifier:)
method of the UITableView
.
This method dequeues a reusable cell with the specified identifier.
The cell is then downcast to the specific cell type MovieTableViewCell
using as!
for force casting, ensuring the cell is of the correct type.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let starWarCell = tableView.dequeueReusableCell(withIdentifier: MovieTableViewCell.identifier, for: indexPath) as! MovieTableViewCell
let starTrekCell = tableView.dequeueReusableCell(withIdentifier: StarTrekTableViewCell.identifier, for: indexPath) as! StarTrekTableViewCell
self.navigationController?.navigationBar.prefersLargeTitles = true
switch segmentedController.selectedSegmentIndex {
case 0:
self.navigationItem.title = "Star War"
print("Star Wars is \(movieList.count)")
starWarCell.titleLabel.text = movieList[indexPath.row].movieName
starWarCell.detailLabel.text = movieList[indexPath.row].movieTitle
starWarCell.movieImageView.image = UIImage(named: movieList[indexPath.row].movieTitle)
case 1:
self.navigationItem.title = "Star Trek"
print("StarTrek is \(starTrekMovieList.count)")
starTrekCell.starTrekTitleLabel.text = starTrekMovieList[indexPath.row].movieName
starTrekCell.starTrekDetailTitleLabel.text = starTrekMovieList[indexPath.row].movieTitle
starTrekCell.posterImageView.image = UIImage(named: starTrekMovieList[indexPath.row].movieTitle)
return starTrekCell
default:
break
}
return starWarCell
}
Documentation:
numberOfRowsInSection(一個section顯示多少列):
- Tells the data source to return the number of rows in a given section of a table view.
Use the MovieList
array to return the count of items in it, which indicates the number of data entries in the MovieList
array.
This is implemented in the numberOfRowsInSection
method of the UITableView
.
The method uses a segmentedController
to determine which array's count to return.
If the selected segment index is 0, it returns the count of movieList
. If the index is 1, it returns the count of starTrekMovieList
.
In other cases, it defaults to returning the count of movieList
.
// numberOfRowsInSection
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch segmentedController.selectedSegmentIndex {
case 0:
print("Case 0: numberOfRowsInSection is \(segmentedController.selectedSegmentIndex)")
return movieList.count
case 1:
print("Case 1: numberOfRowsInSection is \(segmentedController.selectedSegmentIndex)")
return starTrekMovieList.count
default:
print("numberOfRowsInSection is break")
return movieList.count
}
}
Documentation:
5. Passing Data from tableViewCell to webView:
Using segmentedContoller.selectedSegmentIndex
for switching different content.
And use instantiateViewController(withIdentifier:)
for passing data.
// didSelectRowAt
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch segmentedController.selectedSegmentIndex {
case 0:
urlAddress = movieList[indexPath.row].url
webVCNavigationString = movieList[indexPath.row].movieTitle
case 1:
urlAddress = starTrekMovieList[indexPath.row].url
webVCNavigationString = starTrekMovieList[indexPath.row].movieTitle
default:
break
}
let webVC = storyboard?.instantiateViewController(withIdentifier: "WebViewController") as? WebViewController
webVC?.url = urlAddress
webVC?.navigationTitle = webVCNavigationString
navigationController?.pushViewController(webVC!, animated: true)
print(urlAddress)
print(indexPath)
}
Documentation:
6. Setup Webview and setup forward Button & back button:
Before we setup the WebView, we could read the documentation from Apple.
We can simply follow the step-by-step instructions provided by Apple to complete this process.
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.navigationDelegate = self
webView.uiDelegate = self
view = webView
}
Documentation:
1. Setup Auto-layout:
Add the layout constraint for webView.
2. Load the webView from URL:
Create a url and setup it’s property as string?
.
var url: String?
Create a function named showWebsite
that loads a web request into a WebView.
This function first creates a URL
object from a string (url
).
It then creates a URLRequest
object using this URL. Finally, the function loads this request into a webView
.
func showWebsite() {
let myURL = URL(string: url!)
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
}
3. Go back or Go forward:
Create a function named configureSegmentItem
to set up the tintColor and to configure segmentedControl
as a custom view for UIBarButtonItem
.
Additionally, add a target to segmentedControl
so that when the user taps it, the IDE can recognize which action is being triggered.
func configureSegmentItem () {
segmentedControl.tintColor = .darkGray
segmentedControl.isEnabled = true
segmentedControl.selectedSegmentIndex = 0
segmentedControl.addTarget(self, action: #selector(selectSegmentControl), for: .valueChanged)
let segmentItem: UIBarButtonItem = UIBarButtonItem(customView: segmentedControl)
self.navigationItem.rightBarButtonItem = segmentItem
}
Create the selectSegmentControl
action for website could go back and go forward, I’m using switch
the segmentedControl.selectedSegementIndex
to define the interger when is goBack
and goForward
.
@objc func selectSegmentControl (_ sender: UISegmentedControl) {
switch segmentedControl.selectedSegmentIndex {
// backBtn
case 0:
print("Case 0")
if webView.canGoBack {
webView.goBack()
print("webView go back")
}
// forwardBtn
case 1:
print("Case 1")
if webView.canGoForward {
webView.goForward()
print("webView go forward")
}
default:
break
}
}
Documentation:
The idea for the goBack
and goForward
buttons was inspired by Apple’s documentation on customizing your app’s navigation bar.
It’s a really great example that helps you to explore more customization options for the navigation bar.
Reference:
TableView:
- UITableView Tutorial with Custom Cells, XIB — 2023 iOS UIKit Swift 5
- Passing data by using storyboard without any segue.
- UITableView 介紹
- dequeueReusableCell 傳入 cell reuse id 的幾種寫法
How to add a segmented control to a navigation bar ? (iOS)
- How to set cornerRadius on a imageview inside a table view cell
- 放大版的 navigation bar
https://medium.com/彼得潘的-swift-ios-app-開發問題解答集/ios-11-的放大版-navigation-bar-85c05824a14c
- adding segmented control with in the Navigation Bar
- Customizing Your App’s Navigation Bar
WebKit:
- Show the webView by using Webkit framework.
- com.apple.WebKit.WebContent drops 113 error: Could not find specified service
Here’s my GitHub repository link: