• 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
            // 设置层的frame
            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
    
  • 设置提示文字颜色
  • // 使用NSAttributedString
    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 = ""
            // 1.加载drink的动画图片
            for index in 0 ... 80 {
                // drink_XX.jpg
                imgName = "drink_\(index).jpg"
                // 通过名字构造一张图片
                let image = UIImage(named: imgName)
                imageArray.append(image!)
            // 2.让图片进行动画的播放
            // 图片数组
            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()
        // sender 谁触发这个事件 就将谁传进来
        @IBAction func valueChanged(_ sender: Any) {
            // UISwitch
            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")
            // UISlider
            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")
            // UIStepper
            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 = "您未购买任何商品"
            // UISegmentedControl
            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动画
            // 动画执行的时间
            // 动画执行的操作
            UIView.animate(withDuration: 5.0) {
                // 千万不要直接设置progress,因为这样是不会有动画效果的
                // self.progressView.progress = 1.0
                // 必须要用带animated参数的方法来进行设置 才会有动画
                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发生valueChanged事件时 会调用target的action方法
            datePicker.addTarget(self, action: #selector(getBirthday), for: .valueChanged)
            birthday.inputView = datePicker
        @objc func getBirthday(datePicker: UIDatePicker) {
            // 获取日期
            let date = datePicker.date
            // 日期格式化
            // 2018.10.17 2018/10/17 2018-10-17 2018年10月17日
            let dateFormatter = DateFormatter()
            // 24小时制,hh小写12小时制
            dateFormatter.dateFormat = "yyyy年MM月dd日 HH:mm:ss"
            // 赋值给birthday
            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 {
        // 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 {
        // UIPickerViewDelegate
        // 该方法会调用多次 根据numberOfRowsInComponent的返回值决定
        // 每一次调用就应该返回一个数据 它会自动从第0行开始设置title
        // 6行 0 1 2 3 4 5
        func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
            if component == 0 {
                return province[row]
            } else {
                return city[row]
        // 设置UIView
        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()
            // 设置UIScrollView
            setupBanner()
            // 设置UIPageControl
            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 {
                // x坐标
                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)
            // 设置contentSize
            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) {
            // 获取contentOffset
            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")
            // default 只显示textLabel和imageView
            // subtitle value1 三个都显示
            // value2 只显示textLabel和detailTextLabel
            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!
            // SB方式实现复用
            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 {
            // SB方式实现复用
            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 {
            // SB方式实现复用
            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 {
        // Section头部
        func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
            return "iPhone 大全"
        // Section尾部
        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
        // Section头部高
        func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
            return 100.0
        // Section尾部高
        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 {
            // SB方式实现复用
            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 {
                // 1.删除数组中对应的数据
                content?.remove(at: indexPath.row)
                detailContent?.remove(at: indexPath.row)
                // 2.TableView显示的那一样删除
                tableView.deleteRows(at: [indexPath], with: .automatic)
            } else if editingStyle == .insert {
                // 1.增加一条数据
                content?.insert("iPhone 1", at: indexPath.row)
                detailContent?.insert("iPhone 1 - iPhone OS", at: indexPath.row)
                // 2.增加一行
                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)
            // 一定要返回index 否则 点击索引不会自动滚动到指定位置
            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()
            // 创建UIRefreshControl
            let refresh = UIRefreshControl()
            // 设置显示的标题
            refresh.attributedTitle = NSAttributedString(string: "下拉刷新")
            // 设置下拉事件
            refresh.addTarget(self, action: #selector(loadData), for: .valueChanged)
            // 放到tableView的头部
            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 {
            // SB方式实现复用
            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()        
            // 设置item大小
            layout.itemSize = CGSize(width: (screenW - 15.0) * 0.5, height: 212)
            // item之间的间距
            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)")