UIView 会占用屏幕上一个
矩形
的空间。
主要处理两件事:画出矩形控件,并处理其中的事件。
UIView 是层级结构,UIView 只有一个父 View,但可以有多个子 View。子 View 的顺序和子 View 返回的数组中的位置有关(storyboard 中左侧的树形结构图中的先后顺序)。
UIView 可以直接在 storyboard 里面拖拽使用,也可以使用纯代码方式使用。
UILabel、UITextField、UIButton
UILabel
显示静态文本。
使用 storyboard:设置
Lines
为 0,然后在
Text
中用
option+回车
换行。
使用代码:
label.numberOfLines = 0
,设置文字的时候用
\n
换行。
UITextField
框内左边视图
textField.leftView = UIImageView(image: UIImage(systemName: "phone"))
textField.leftViewMode = .always
横线式输入框
class ViewController: UIViewController {
@IBOutlet var textfield: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
textfield.borderStyle = .none
textfield.setBottomBorder(UIColor.red, 1)
extension UITextField {
func setBottomBorder(_ color: UIColor, _ lineWidth: Int) {
let bottomBorder = CALayer()
let lineWidth = CGFloat(lineWidth)
bottomBorder.borderColor = color.cgColor
bottomBorder.frame = CGRect(x: 0, y: frame.size.height - lineWidth, width: frame.size.width, height: frame.size.height)
bottomBorder.borderWidth = lineWidth
layer.addSublayer(bottomBorder)
layer.masksToBounds = true
设置提示文字颜色
textField.attributedPlaceholder = NSAttributedString(string: "请输入信息", attributes: [.foregroundColor : UIColor.red])
UIButton
按钮,最重要的是点击事件。
使用 storyboard:设置 Lines Break 为Word Wrap
,然后在 title 中用option+回车
换行。
使用代码:titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
,设置文字的时候用\n
换行。
class ViewController: UIViewController {
@IBOutlet var username: UITextField!
@IBOutlet var password: UITextField!
@IBAction func loginBtnClicked(_ sender: Any) {
let uname = username.text
let upwd = password.text
print("用户名为:\(uname!), 密码为:\(upwd!)")
override func viewDidLoad() {
super.viewDidLoad()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
view.endEditing(true)
UITextView
多行文本输入框。
使用类似 UITextField。
内容可滚动。
UIImageView
Tom猫案例
class ViewController: UIViewController {
var imageArray: [UIImage] = [UIImage]()
@IBOutlet var tomcat: UIImageView!
@IBAction func drink(_ sender: Any) {
imageArray.removeAll()
var imgName = ""
for index in 0 ... 80 {
imgName = "drink_\(index).jpg"
let image = UIImage(named: imgName)
imageArray.append(image!)
tomcat.animationImages = imageArray
tomcat.animationDuration = 3.0
tomcat.animationRepeatCount = 1
tomcat.startAnimating()
override func viewDidLoad() {
super.viewDidLoad()
UISwitch、UISlider、UIStepper 、UISegmentControl
class ViewController: UIViewController {
@IBOutlet var light: UIImageView!
@IBOutlet var voice: UIImageView!
@IBOutlet var product: UILabel!
@IBOutlet var flower: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
@IBAction func valueChanged(_ sender: Any) {
let switchUI = sender as? UISwitch
if let switchUI = switchUI {
if switchUI.isOn {
light.image = UIImage(named: "light.png")
} else {
light.image = UIImage(named: "nomal.png")
let slider = sender as? UISlider
if let slider = slider {
if slider.value < 0.3 {
voice.image = UIImage(named: "low.png")
} else if slider.value < 0.7 {
voice.image = UIImage(named: "middle.png")
} else {
voice.image = UIImage(named: "high.png")
let stepper = sender as? UIStepper
if let stepper = stepper {
let value = stepper.value
if value < stepper.maximumValue {
product.text = "您购买了\(Int(value))件商品"
if value == stepper.minimumValue {
product.text = "您未购买任何商品"
let segment = sender as? UISegmentedControl
if let segment = segment {
if segment.selectedSegmentIndex == 0 {
flower.image = UIImage(named: "red.png")
} else if segment.selectedSegmentIndex == 1 {
flower.image = UIImage(named: "purple.png")
思考:汤姆猫和本案例,事件都是相同的,那么能否用一个 IBAction 完成?
UIActivityIndicatorView、UIProgressView
UIActivityIndicatorView:无进度的进度条。
UIProgressView:有进度的进度条。
class ViewController: UIViewController {
@IBOutlet var indicator: UIActivityIndicatorView!
@IBOutlet var progressView: UIProgressView!
override func viewDidLoad() {
super.viewDidLoad()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
indicator.stopAnimating()
UIView.animate(withDuration: 5.0) {
self.progressView.setProgress(1.0, animated: true)
UIDatePicker
日期选择器
class ViewController: UIViewController {
@IBOutlet var birthday: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
let datePicker = UIDatePicker(frame: CGRect(x: 0, y: 0, width: view.bounds.size.width, height: 300))
datePicker.datePickerMode = .dateAndTime
datePicker.addTarget(self, action: #selector(getBirthday), for: .valueChanged)
birthday.inputView = datePicker
@objc func getBirthday(datePicker: UIDatePicker) {
let date = datePicker.date
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy年MM月dd日 HH:mm:ss"
birthday.text = dateFormatter.string(from: date)
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
birthday.resignFirstResponder()
iOS 14 新增了卡片式日期选择器,且成为默认样式。如果需要显示成滚轮模式,需要手动设置:
datePicker.preferredDatePickerStyle = .wheels
注意:需要在 frame 之前设置。
给输入框的 inputView 设置 UIDatePicker。
UIPickerView
选择器控件
数据源(DataSource)
代理(Delegate)
可以通过代码和拽线的方式设置数据源和代理。
class ViewController: UIViewController {
let province: [String] = ["安徽", "浙江", "江苏", "山东", "河南", "湖北"]
let city: [String] = ["合肥", "杭州", "南京", "济南", "郑州", "武汉", "厦门", "长沙"]
override func viewDidLoad() {
super.viewDidLoad()
extension ViewController: UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 2
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 0 {
return province.count
} else {
return city.count
extension ViewController: UIPickerViewDelegate {
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if component == 0 {
return province[row]
} else {
return city[row]
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
return UIImageView(image: UIImage(systemName: "person"))
func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
return 44
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if component == 0 {
print(province[row])
} else {
print(city[row])
titleForRow
方法在代理方法里而不是在数据源方法里。
内容除了设置 String 类型,还可以设置 UIView 类型,且一旦设置了 UIView,设置 String 的失效。
代理方法可以设置内容的高度。
在某一列滚动的时候,重新设置联动列的显示数据,然后进行刷新操作。
UIScrollView、UIPageControl
UIScrollView
三个重要属性
contentSize:UIScrollView 滚动的范围。
contentOffset:UIScrollView 当前显示区域的顶点相对于内容左上角的偏移量(滚动到了什么位置)。
contentInset:ScrollView的内容相对于 UIScrollView 的上下左右的留白。
UIPageControl
页面指示器
一般配合 UIScrollView 分页使用。
class ViewController: UIViewController {
let bannerW = UIScreen.main.bounds.size.width
let bannerH = 260
var banner: UIScrollView!
var pageControl: UIPageControl!
override func viewDidLoad() {
super.viewDidLoad()
setupBanner()
setupPageControl()
func setupBanner() {
banner = UIScrollView(frame: CGRect(x: 0, y: 0, width: Int(bannerW), height: bannerH))
banner.showsHorizontalScrollIndicator = false
banner.bounces = false
banner.isPagingEnabled = true
banner.delegate = self
for i in 0 ... 4 {
let w = Int(bannerW) * i
let img = UIImageView(frame: CGRect(x: w, y: 0, width: Int(bannerW), height: bannerH))
img.image = UIImage(named: "img_\(i)")
banner.addSubview(img)
banner.contentSize = CGSize(width: bannerW * 5.0, height: 0)
view.addSubview(banner)
func setupPageControl() {
pageControl = UIPageControl(frame: CGRect(x: 0, y: 0, width: 200, height: 20))
pageControl.pageIndicatorTintColor = UIColor.red
pageControl.currentPageIndicatorTintColor = UIColor.cyan
pageControl.numberOfPages = 5
pageControl.center = CGPoint(x: bannerW * 0.5, y: 240.0)
pageControl.addTarget(self, action: #selector(pageIndicate), for: .valueChanged)
view.addSubview(pageControl)
@objc func pageIndicate(pageControl: UIPageControl) {
let currentIndex = pageControl.currentPage
banner.setContentOffset(CGPoint(x: Int(bannerW) * currentIndex, y: 0), animated: true)
extension ViewController: UIScrollViewDelegate {
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let contentOffset = scrollView.contentOffset
let index = contentOffset.x / bannerW
pageControl.currentPage = Int(index)
UITableView
表视图,是 iOS 开发中最重要的 UI 控件之一。
一个 UITableView 由 Header + 多个 Section + Footer 组成。
一个 Section 由 Header + 多个 Row + Footer 组成。
一个 Row 就是 UITableViewCell。
UITableViewCell结构
里面有一个contentView
,显示的内容放在上面。
contentView
里默认有 3 个控件:2 个UILabel
、1一个UIImageView
,并由此产生了四种不同的 UITableViewCell 的显示样式。
类似 PickerView,需要提供数据源以显示数据。
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
func tableView(_ tableView: UITableView, numberOfRowsInSection section
: Int) -> Int {
return 20
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "abc")
cell.textLabel?.text = "AAA"
cell.detailTextLabel?.text = "BBB"
cell.imageView?.image = UIImage(named: "setting_about_pic")
return cell
UITableViewCell重用
重用标识符
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// 纯代码实现复用
// 去重用池子中找cell
var cell = tableView.dequeueReusableCell(withIdentifier: "abc")
// 池子中没有就创建一个新的
if cell == nil {
cell = UITableViewCell(style: .subtitle, reuseIdentifier: "abc")
cell?.textLabel?.text = "AAA"
cell?.detailTextLabel?.text = "BBB"
cell?.imageView?.image = UIImage(named: "setting_about_pic")
return cell!
let cell = tableView.dequeueReusableCell(withIdentifier: "abc")
cell?.textLabel?.text = "AAA"
cell?.detailTextLabel?.text = "BBB"
cell?.imageView?.image = UIImage(named: "setting_about_pic")
return cell!
数据不再固定,而是由外界提供,多使用数组。
class ViewController: UIViewController {
var content: Array<String>?
var detailContent: Array<String>?
override func viewDidLoad() {
super.viewDidLoad()
content = ["iPhone 4", "iPhone 4s", "iPhone 5", "iPhone 5s", "iPhone 6", "iPhone 6 Plus", "iPhone 6s", "iPhone 6s Plus", "iPhone 7", "iPhone 7 Plus", "iPhone 8", "iPhone 8 Plus", "iPhone X", "iPhone Xs", "iPhone XR", "iPhone Xs Max", "iPhone 11", "iPhone 11 Pro", "iPhone 11 Pro Max", "iPhone 12 mini", "iPhone 12", "iPhone 12 Pro", "iPhone 12 Pro Max"]
detailContent = ["iPhone 4 - iOS 4", "iPhone 4s - iOS 5", "iPhone 5 - iOS 6", "iPhone 5s - iOS 7", "iPhone 6 - iOS 8", "iPhone 6 Plus - iOS 8", "iPhone 6s - iOS 9", "iPhone 6s Plus - iOS 9", "iPhone 7 - iOS 10", "iPhone 7 Plus - iOS 10", "iPhone 8 - iOS 11", "iPhone 8 Plus - iOS 11", "iPhone X - iOS 11", "iPhone Xs - iOS 12", "iPhone XR - iOS 12", "iPhone Xs Max - iOS 12", "iPhone 11 - iOS 13", "iPhone 11 Pro - iOS 13", "iPhone 11 Pro Max - iOS 13", "iPhone 12 mini - iOS 14", "iPhone 12 - iOS 14", "iPhone 12 Pro - iOS 14", "iPhone 12 Pro Max - iOS 14"]
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return content!.count
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ABC")
cell?.textLabel?.text = content?[indexPath.row]
cell?.detailTextLabel?.text = detailContent?[indexPath.row]
cell?.imageView?.image = UIImage(named: "iPhone")
return cell!
class ViewController: UIViewController {
var content: Array<String>?
var detailContent: Array<String>?
override func viewDidLoad() {
super.viewDidLoad()
content = ["iPhone 4", "iPhone 4s", "iPhone 5", "iPhone 5s", "iPhone 6", "iPhone 6 Plus", "iPhone 6s", "iPhone 6s Plus", "iPhone 7", "iPhone 7 Plus", "iPhone 8", "iPhone 8 Plus", "iPhone X", "iPhone Xs", "iPhone XR", "iPhone Xs Max", "iPhone 11", "iPhone 11 Pro", "iPhone 11 Pro Max", "iPhone 12 mini", "iPhone 12", "iPhone 12 Pro", "iPhone 12 Pro Max"]
detailContent = ["iPhone 4 - iOS 4", "iPhone 4s - iOS 5", "iPhone 5 - iOS 6", "iPhone 5s - iOS 7", "iPhone 6 - iOS 8", "iPhone 6 Plus - iOS 8", "iPhone 6s - iOS 9", "iPhone 6s Plus - iOS 9", "iPhone 7 - iOS 10", "iPhone 7 Plus - iOS 10", "iPhone 8 - iOS 11", "iPhone 8 Plus - iOS 11", "iPhone X - iOS 11", "iPhone Xs - iOS 12", "iPhone XR - iOS 12", "iPhone Xs Max - iOS 12", "iPhone 11 - iOS 13", "iPhone 11 Pro - iOS 13", "iPhone 11 Pro Max - iOS 13", "iPhone 12 mini - iOS 14", "iPhone 12 - iOS 14", "iPhone 12 Pro - iOS 14", "iPhone 12 Pro Max - iOS 14"]
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return content!.count
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ABC")
cell?.textLabel?.text = content?[indexPath.row]
cell?.detailTextLabel?.text = detailContent?[indexPath.row]
cell?.imageView?.image = UIImage(named: "iPhone")
return cell!
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "iPhone 大全"
func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return "iOS大全"
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let contentText = content?[indexPath.row]
let detailText = detailContent?[indexPath.row]
print("\(contentText!)--\(detailText!)")
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80.0
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 100.0
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 60.0
class ViewController: UIViewController {
@IBOutlet var tableView: UITableView!
var content: Array<String>?
var detailContent: Array<String>?
override func viewDidLoad() {
super.viewDidLoad()
content = ["iPhone 4", "iPhone 4s", "iPhone 5", "iPhone 5s", "iPhone 6", "iPhone 6 Plus", "iPhone 6s", "iPhone 6s Plus", "iPhone 7", "iPhone 7 Plus", "iPhone 8", "iPhone 8 Plus", "iPhone X", "iPhone Xs", "iPhone XR", "iPhone Xs Max", "iPhone 11", "iPhone 11 Pro", "iPhone 11 Pro Max", "iPhone 12 mini", "iPhone 12", "iPhone 12 Pro", "iPhone 12 Pro Max"]
detailContent = ["iPhone 4 - iOS 4", "iPhone 4s - iOS 5", "iPhone 5 - iOS 6", "iPhone 5s - iOS 7", "iPhone 6 - iOS 8", "iPhone 6 Plus - iOS 8", "iPhone 6s - iOS 9", "iPhone 6s Plus - iOS 9", "iPhone 7 - iOS 10", "iPhone 7 Plus - iOS 10", "iPhone 8 - iOS 11", "iPhone 8 Plus - iOS 11", "iPhone X - iOS 11", "iPhone Xs - iOS 12", "iPhone XR - iOS 12", "iPhone Xs Max - iOS 12", "iPhone 11 - iOS 13", "iPhone 11 Pro - iOS 13", "iPhone 11 Pro Max - iOS 13", "iPhone 12 mini - iOS 14", "iPhone 12 - iOS 14", "iPhone 12 Pro - iOS 14", "iPhone 12 Pro Max - iOS 14"]
@IBAction func edit(_ sender: Any) {
tableView.setEditing(true, animated: true)
@IBAction func done(_ sender: Any) {
tableView.setEditing(false, animated: true)
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return content!.count
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ABC")
cell?.textLabel?.text = content?[indexPath.row]
cell?.detailTextLabel?.text = detailContent?[indexPath.row]
cell?.imageView?.image = UIImage(named: "iPhone")
return cell!
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
content?.remove(at: indexPath.row)
detailContent?.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
} else if editingStyle == .insert {
content?.insert("iPhone 1", at: indexPath.row)
detailContent?.insert("iPhone 1 - iPhone OS", at: indexPath.row)
tableView.insertRows(at: [indexPath], with: .automatic)
func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? {
return "删除"
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .insert
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return true
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let contentText = content?[sourceIndexPath.row]
content?.remove(at: sourceIndexPath.row)
content?.insert(contentText!, at: destinationIndexPath.row)
let detailContentText = detailContent?[sourceIndexPath.row]
detailContent?.remove(at: sourceIndexPath.row)
detailContent?.insert(detailContentText!, at: destinationIndexPath.row)
class ViewController: UIViewController {
@IBOutlet var tableView: UITableView!
var sectionTitles: [String] = ["A", "C", "F", "G", "H", "M", "S", "T", "X", "Z"]
var contentsArray: [[String]] = [
["阿伟", "阿姨", "阿三"],
["陈晨", "成龙", "陈鑫", "陈丹"],
["芳仔", "房祖名", "方大同", "范伟"],
["郭靖", "郭美美", "过儿", "郭襄"],
["何仙姑", "和珅", "郝歌", "何仙姑"],
["马云", "毛不易"],
["孙周", "沈冰", "史磊"],
["陶也", "淘宝", "图腾"],
["项羽", "夏紫薇", "许巍", "许晴"],
["祝枝山", "周杰伦", "张柏芝"],
override func viewDidLoad() {
super.viewDidLoad()
tableView.sectionIndexBackgroundColor = UIColor.red
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return sectionTitles.count
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contentsArray[section].count
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "abc")
cell?.textLabel?.text = contentsArray[indexPath.section][indexPath.row]
return cell!
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionTitles[section]
extension ViewController: UITableViewDelegate {
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return sectionTitles
func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
print(title)
return index
自定义UITableViewCell
用 3 种自定义 Cell 的方式分别实现下面的案例:
iPhone 信息展示
class ViewController: UIViewController {
@IBOutlet var tableView: UITableView!
var content: Array<String> = ["iPhone 4", "iPhone 4s", "iPhone 5", "iPhone 5s", "iPhone 6", "iPhone 6 Plus", "iPhone 6s", "iPhone 6s Plus", "iPhone 7", "iPhone 7 Plus", "iPhone 8", "iPhone 8 Plus", "iPhone X", "iPhone Xs", "iPhone XR", "iPhone Xs Max", "iPhone 11", "iPhone 11 Pro", "iPhone 11 Pro Max", "iPhone 12 mini", "iPhone 12", "iPhone 12 Pro", "iPhone 12 Pro Max"]
var detailContent: Array<String> = ["iPhone 4 - iOS 4", "iPhone 4s - iOS 5", "iPhone 5 - iOS 6", "iPhone 5s - iOS 7", "iPhone 6 - iOS 8", "iPhone 6 Plus - iOS 8", "iPhone 6s - iOS 9", "iPhone 6s Plus - iOS 9", "iPhone 7 - iOS 10", "iPhone 7 Plus - iOS 10", "iPhone 8 - iOS 11", "iPhone 8 Plus - iOS 11", "iPhone X - iOS 11", "iPhone Xs - iOS 12", "iPhone XR - iOS 12", "iPhone Xs Max - iOS 12", "iPhone 11 - iOS 13", "iPhone 11 Pro - iOS 13", "iPhone 11 Pro Max - iOS 13", "iPhone 12 mini - iOS 14", "iPhone 12 - iOS 14", "iPhone 12 Pro - iOS 14", "iPhone 12 Pro Max - iOS 14"]
override func viewDidLoad() {
super.viewDidLoad()
let refresh = UIRefreshControl()
refresh.attributedTitle = NSAttributedString(string: "下拉刷新")
refresh.addTarget(self, action: #selector(loadData), for: .valueChanged)
tableView.refreshControl = refresh
@objc func loadData() {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.content.insert("iPhone 3GS", at: 0)
self.detailContent.insert("iPhone 3GS - iOS 3", at: 0)
self.tableView.reloadData()
if (self.tableView.refreshControl?.isRefreshing)! {
self.tableView.refreshControl?.endRefreshing()
extension ViewController: UITableViewDataSource {
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return content.count
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ABC")
cell?.textLabel?.text = content[indexPath.row]
cell?.detailTextLabel?.text = detailContent[indexPath.row]
cell?.imageView?.image = UIImage(named: "iPhone")
return cell!
静态单元格
需要使用 UITableViewController。
直接在 storyboard 中布局,不需要使用数据源方法,但如果需要使用到代理方法,仍然需要在控制器中实现相应的方法。
适用于基本不需要动态修改、布局固定的页面,如个人中心、设置等。
微信“发现”界面案例。
UICollectionView
集合视图,是 iOS 开发中最重要的 UI 控件之一。
一个 UICollectionView 由 Header + 多个 Section + Footer 组成。
一个 Section 由 Header + 多个 Item + Footer 组成。
一个 Item 就是 UICollectionViewCell。
类似 UITableView,需要提供数据源以显示数据。
支持 Diffable Data Source,类为 UICollectionViewDiffableDataSource,使用方式类似 UITableViewDiffableDataSource。
UICollectionViewFlowLayout
与 UITableView 不同,UICollectionView 需要提供布局参数,常用的有UICollectionViewFlowLayout
,通过它可以设置内容的大小、间距和方向等信息。
class ViewController: UIViewController {
@IBOutlet var collectionView: UICollectionView!
let screenW = UIScreen.main.bounds.size.width
override func viewDidLoad() {
super.viewDidLoad()
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: (screenW - 15.0) * 0.5, height: 212)
layout.minimumInteritemSpacing = 5.0
layout.minimumLineSpacing = 5.0
layout.sectionInset = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
layout.scrollDirection = .vertical
collectionView.collectionViewLayout = layout
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "abc", for: indexPath)
return cell
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("\(indexPath.row)")