iOS14開發-UIView

介紹

  • 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 完成?swift

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])
        }
    }
}
複製代碼

說明:tomcat

  1. titleForRow方法在代理方法裏而不是在數據源方法裏。
  2. 內容除了設置 String 類型,還能夠設置 UIView 類型,且一旦設置了 UIView,設置 String 的失效。
  3. 代理方法能夠設置內容的高度。

數據聯動

在某一列滾動的時候,從新設置聯動列的顯示數據,而後進行刷新操做。微信

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!
    }
}
複製代碼

數據源

數據再也不固定,而是由外界提供,多使用數組。markdown

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 中佈局,不須要使用數據源方法,但若是須要使用到代理方法,仍然須要在控制器中實現相應的方法。
  • 適用於基本不須要動態修改、佈局固定的頁面,如我的中心、設置等。
  • 微信「發現」界面案例。

UITableViewDiffableDataSource

在 iOS 13 中引入了新的 API — Diffable Data Source,它不只可以驅動 UITableView 和 UICollectionView,並且能夠更簡單高效的實現數據的刷新。app

核心知識

  1. UITableViewDiffableDataSource:建立 UITableView 數據源。
  2. NSDiffableDataSourceSnapshot:UITableView 的狀態。
  3. apply(_:animatingDifferences:):當要顯示或更新數據時,經過調用 NSDiffableDataSourceSnapshot 對象的 apply 方法將其提供給數據源,該方法將比較當前顯示的快照(渲染模型)和新快照以得到差別,最後以設定的動畫方式應用這些變化從而刷新界面。

案例

  • 建立數據源。
var dataSource: UITableViewDiffableDataSource<Section, City>!

override func viewDidLoad() {
    super.viewDidLoad()
    
    dataSource = UITableViewDiffableDataSource
        <Section, City>(tableView: tableView) { // 該閉包是tableView(_:cellForRowAtIndexPath:)方法的替代品
            (tableView: UITableView, indexPath: IndexPath,
            city: City) -> UITableViewCell? in
            let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
            cell.textLabel?.text = city.name
            return cell
    }
    // 刷新時的動畫
    dataSource.defaultRowAnimation = .fade
}
複製代碼
  • DataSourceSnapshot 負責變動後的數據源處理,其有 append、delete、move、insert 等方法。
enum Section: CaseIterable {
    case main
}

// 獲取NSDiffableDataSourceSnapshot
var snapshot = NSDiffableDataSourceSnapshot<Section, City>()
// 更改數據
snapshot.appendSections([.main])
snapshot.appendItems(filteredCities, toSection: .main)
// 更新
dataSource.apply(snapshot, animatingDifferences: true)
複製代碼
  • 爲了確保 Diff 生效,數據源的 Model 必須具備惟一 Identifier,且遵循 Hashable 協議。
struct City: Hashable {
    let identifier = UUID()    
    let name: String

    func hash(into hasher: inout Hasher) {
        hasher.combine(identifier)
    }

    static func ==(lhs: City, rhs: City) -> Bool {
        return lhs.identifier == rhs.identifier
    }

    func contains(query: String?) -> Bool {
        guard let query = query else { return true }
        guard !query.isEmpty else { return true }
        return name.contains(query)
    }
}
複製代碼

UICollectionView

  • 集合視圖,是 iOS 開發中最重要的 UI 控件之一。
  • 總體結構
    • 一個 UICollectionView 由 Header + 多個 Section + Footer 組成。
    • 一個 Section 由 Header + 多個 Item + Footer 組成。
    • 一個 Item 就是 UICollectionViewCell。
  • 相似 UITableView,須要提供數據源以顯示數據。
  • 支持 Diffable Data Source,類爲 UICollectionViewDiffableDataSource,使用方式相似 UITableViewDiffableDataSource。

UICollectionViewFlowLayout

與 UITableView 不一樣,UICollectionView 須要提供佈局參數,經常使用的有UICollectionViewFlowLayout,經過它能夠設置內容的大小、間距和方向等信息。async

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)")
    }
}
複製代碼

UICollectionViewCompositionalLayout

在 iOS 13 中 UICollectionView 推出了一種新的組合佈局 UICollectionViewCompositionalLayout,這是一次全新的升級。ide

介紹

UICollectionViewCompositionalLayout 是在已有的 Item 和 Section 的基礎上,增長了一個 Group 的概念。多個 Item 組成一個 Group ,多個 Group 組成一個 Section,所以層級關係從裏到外變爲:Item -> Group -> Section -> Layout佈局

核心知識

NSCollectionLayoutSize

決定了一個元素的大小。表達一個元素的 Size 有三種方法:

  • fractional:表示一個元素相對於他的父視圖的比例。(Item 的父視圖是 Group,Group 的父視圖是 Section) 。
// 佔據Group寬和高各25%
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.25), heightDimension: .fractionalHeight(0.25))
複製代碼
  • absolute:表示將元素的寬或者高寫成固定一個值。
let widthDimension = NSCollectionLayoutDimension.absolute(200)
let heightDimension = NSCollectionLayoutDimension.absolute(200)
複製代碼
  • estimated:表示預估高度。通常用於自適應大小,會根據自動佈局決定元素的大小。
let widthDimension = NSCollectionLayoutDimension.estimated(200)
let heightDimension = NSCollectionLayoutDimension.estimated(200)
複製代碼
NSCollectionLayoutItem

描述一個 Item 的佈局,定義以下:

class NSCollectionLayoutItem {
    convenience init(layoutSize: NSCollectionLayoutSize)
    var contentInsets: NSDirectionalEdgeInsets
}
複製代碼
NSCollectionLayoutGroup
  • Group 是新引入的組成佈局的基本單元,它有三種形式:
    • 水平(horizontal)
    • 垂直(vertical)
    • 自定義(custom)
  • Group 的大小頁須要經過 NSCollectionLayoutSize 決定。若是是自定義佈局,須要傳入一個 NSCollectionLayoutGroupCustomItemProvider 來決定這個 Group 中 Item 的佈局方式。
  • 定義以下:
class NSCollectionLayoutGroup: NSCollectionLayoutItem { 
    class func horizontal(layoutSize: NSCollectionLayoutSize, subitems: [NSCollectionLayoutItem]) -> Self class func vertical(layoutSize: NSCollectionLayoutSize, subitems: [NSCollectionLayoutItem]) -> Self class func custom(layoutSize: NSCollectionLayoutSize, itemProvider: NSCollectionLayoutGroupCustomItemProvider) -> Self } 複製代碼
NSCollectionLayoutSection

描述一個 Section 的佈局,定義以下:

class NSCollectionLayoutSection {
    convenience init(layoutGroup: NSCollectionLayoutGroup) 
    var contentInsets: NSDirectionalEdgeInsets
}
複製代碼

使用步驟

  1. 建立 Item 的 NSCollectionLayoutSize,而後建立 NSCollectionLayoutItem。
  2. 建立 Group 的 NSCollectionLayoutSize,而後根據 Item 建立 NSCollectionLayoutGroup。
  3. 根據 Group 建立 NSCollectionLayoutSection。
  4. 根據 Section 建立 UICollectionViewCompositionalLayout。

由裏而外,由小到大地建立佈局,而後組合。

案例

func generateLayout() -> UICollectionViewCompositionalLayout {
    // 1. 構造Item的NSCollectionLayoutSize
    let itemSize = NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(0.25),
        heightDimension: .fractionalHeight(1.0))
    // 2. 構造NSCollectionLayoutItem
    let item = NSCollectionLayoutItem(layoutSize: itemSize)
    // 3. 構造Group的NSCollectionLayoutSize
    let groupSize = NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(1.0),
        heightDimension: .fractionalWidth(0.1))
    // 4. 構造NSCollectionLayoutGroup
    let group = NSCollectionLayoutGroup.horizontal(
        layoutSize: groupSize,
        subitems: [item])
    // 5. 構造NSCollectionLayoutSection
    let section = NSCollectionLayoutSection(group: group)
    // 6. 構造UICollectionViewCompositionalLayout
    let layout = UICollectionViewCompositionalLayout(section: section)

    return layout
}
複製代碼

NSCollectionLayoutBoundarySupplementaryItem

附加視圖,通常用於設置頭部和尾部 View。

// 頭部大小
let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))
// 頭部
let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: "header", alignment: .top)
// 尾部大小
let footerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))
// 尾部
let footer = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: footerSize, elementKind: "footer", alignment: .bottom)
// pinToVisibleBounds決定是否懸停
header.pinToVisibleBounds = true
// 設置Section的頭尾
section.boundarySupplementaryItems = [header, footer]
複製代碼

附加視圖使用以前須要註冊SupplementaryView,後面會進行講解。

NSCollectionLayoutAnchor

在 Item 中,可能須要給其加上小紅點或者未讀消息數等附加視圖,在 UICollectionViewCompositionalLayout 中,能夠經過 NSCollectionLayoutSupplementaryItem 和 NSCollectionLayoutAnchor 這兩個類來實現這樣的需求。

  • 實現一個UICollectionReusableView。
class BadgeView: UICollectionReusableView {
    static let reuseIdentifier = "badge"
    let imageView = UIImageView(image: UIImage(systemName: "heart.fill"))
    
    override init(frame: CGRect) {
        super.init(frame: frame)
       
        configure()
    }
}
複製代碼
  • 註冊SupplementaryView。
collectionView.register(
    BadgeView.self,
    forSupplementaryViewOfKind: "badge",
    withReuseIdentifier: BadgeView.reuseIdentifier)
複製代碼
  • 設置SupplementaryView。
dataSource.supplementaryViewProvider = {
    (collectionView: UICollectionView, kind: String, indexPath: IndexPath)
        -> UICollectionReusableView? in
    if let badgeView = collectionView.dequeueReusableSupplementaryView(
        ofKind: kind,
        withReuseIdentifier: BadgeView.reuseIdentifier,
        for: indexPath) as? BadgeView {
        return badgeView
    } else {
        fatalError("Cannot create Supplementary")
    }
}
複製代碼
  • 設置Badge。
// Badge位置
let badgeAnchor = NSCollectionLayoutAnchor(edges: [.top, .trailing],
fractionalOffset: CGPoint(x: 0.5, y: -0.5))

// Badge大小
let badgeSize = NSCollectionLayoutSize(widthDimension: .absolute(16),
heightDimension: .absolute(16))

// Badge
let badge = NSCollectionLayoutSupplementaryItem(layoutSize: badgeSize, elementKind: "badge", containerAnchor: badgeAnchor)

// 附加Badge
let item = NSCollectionLayoutItem(layoutSize: itemSize, supplementaryItems: [badge])
複製代碼

Lists in UICollectionView

iOS 14 中 UICollectionView 的功能得以繼續加強,能夠在必定程度上替換 UITableView。

建立UICollectionView

爲 UICollectionView 配置 List 式的佈局,還能夠配置滑動菜單。

extension ViewController {
    // 建立列表式UICollectionView
    func makeCollectionView() -> UICollectionView {
        var config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
        // 右側滑動刪除
        config.trailingSwipeActionsConfigurationProvider = { indexPath in
            // 找到刪除的內容
            guard let city = self.dataSource.itemIdentifier(for: indexPath) else { return nil }
            return UISwipeActionsConfiguration(
                actions: [UIContextualAction(
                    style: .destructive,
                    title: "Delete",
                    handler: { [weak self] _, _, completion in
                        // 調用刪除數據
                        self?.deleteCity(city: city, indexPath: indexPath)
                        self?.updateList()
                        // 最後必定要調用completion
                        completion(true)
                    }
                )]
            )
        }

        // 左側滑動添加
        config.leadingSwipeActionsConfigurationProvider = {  indexPath in                   
            return UISwipeActionsConfiguration(
                actions: [UIContextualAction(
                    style: .normal,
                    title: "Add",
                    handler: { [weak self] _, _, completion in
                         // 調用增長數據
                        self?.addCity(city: City(name: "蕪湖"), indexPath: indexPath)
                        self?.updateList()
                        
                        completion(true)
                    }
                )]
            )
        }
        // 列表佈局
        let layout = UICollectionViewCompositionalLayout.list(using: config)
        
        return UICollectionView(frame: view.frame, collectionViewLayout: layout)
    }
}
複製代碼

註冊Cell

能夠像 UITableView 同樣,填充 Cell 的內容。

extension ViewController {
    // 註冊Cell
    func makeCellRegistration() -> UICollectionView.CellRegistration<CityCollectionViewCell, City> {
        UICollectionView.CellRegistration { cell, _, city in
            // 自定義Cell顯示的內容
            cell.cityLabel.text = city.name
            // AccessoryView
            cell.accessories = [.disclosureIndicator()]
        }
    }
}
複製代碼

配置數據源

extension ViewController {
    // 配置數據源
    func makeDataSource() -> UICollectionViewDiffableDataSource<Section, City> {
        UICollectionViewDiffableDataSource<Section, City>(
            collectionView: collectionView,
            cellProvider: { view, indexPath, item in                
                view.dequeueConfiguredReusableCell(
                    using: self.makeCellRegistration(),
                    for: indexPath,
                    item: item
                )
            }
        )
    }
}
複製代碼

更新數據

enum Section: CaseIterable {
    case first
    case second
}

extension ViewController {
    func updateList() {
        var snapshot = NSDiffableDataSourceSnapshot<Section, City>()
        // 添加兩個分組
        snapshot.appendSections(Section.allCases)
        // 分別往兩個分組添加數據
        snapshot.appendItems(firstCities, toSection: .first)
        snapshot.appendItems(secondCities, toSection: .second)
        dataSource.apply(snapshot)
    }
}
複製代碼

使用

class ViewController: UIViewController {
    // 建立UICollectionView
    private lazy var collectionView = makeCollectionView()
    // 建立DataSource
    private lazy var dataSource = makeDataSource()
    let cityNames = ["北京", "南京", "西安", "杭州", "蕪湖"]
    // 第一組
    var firstCities: [City] = []
    // 第二組
    var secondCities: [City] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        for name in cityNames {
            firstCities.append(City(name: name))
            secondCities.append(City(name: name))
        }
        // CollectionView關聯DataSource 
        collectionView.dataSource = dataSource
        view.addSubview(collectionView)
        // 第一次進來刷新
        updateList()
    }
}



// 增長與刪除數據
extension ViewController {
    // 刪除數據
    func deleteCity(city: City, indexPath: IndexPath) {
        if indexPath.section == 0 {
            firstCities.remove(at: firstCities.firstIndex(of: city)!)
        } else {
            secondCities.remove(at: secondCities.firstIndex(of: city)!)
        }
    }

    // 增長數據
    func addCity(city: City, indexPath: IndexPath) {
        if indexPath.section == 0 {
            firstCities.append(city)
        } else {
            secondCities.append(city)
        }
    }
}
複製代碼

純代碼

使用步驟

// 1.建立UIView
let subView = UIView()
// 2.設置frame
subView.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
// 1和2能夠合併
// let subView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))

// 3.設置其餘屬性
subView.backgroundColor = .red
// 4.UIControl能夠添加事件
...
// 5.添加到父View
view.addSubview(subView)
複製代碼

添加事件

  • iOS 14 以前使用 Target-Action 方式添加事件。
// UITextField
let textField = UITextField()
textField.addTarget(self, action: #selector(handlerEvent), for: .editingChanged)

@objc func handlerEvent(_ sender: UITextField) {
    print(sender.text)
}

// UIButton
let button = UIButton()
button.addTarget(self, action: #selector(handlerEvent), for: .touchUpInside)

@objc func handlerEvent(_ sender: UIButton) {
    print("按鈕點擊")
}

// UISwitch
let swi = UISwitch()
swi.addTarget(self, action: #selector(handlerEvent), for: .valueChanged)

@objc func handlerEvent(_ sender: UISwitch) {
    print(sender.isOn)
}

// UISlider
let slider = UISlider()
slider.addTarget(self, action: #selector(handlerEvent), for: .valueChanged)

@objc func handlerEvent(_ sender: UISlider) {
    print(sender.value)
}

// UIStepper
let stepper = UIStepper()
stepper.addTarget(self, action: #selector(handlerEvent), for: .valueChanged)

@objc func handlerEvent(_ sender: UISlider) {
    print(sender.value)
}

// UISegmentedControl
let segmentedControl = UISegmentedControl()
segmentedControl.addTarget(self, action: #selector(handlerEvent), for: .valueChanged)

@objc func handlerEvent(_ sender: UISegmentedControl) {
    print(sender.selectedSegmentIndex)
}

// UIPageControl
let pageControl = UIPageControl()
pageControl.addTarget(self, action: #selector(handlerEvent), for: .valueChanged)

@objc func handlerEvent(_ sender: UIPageControl) {
    print(sender.currentPage)
}

// UIDatePicker
let datepicker = UIDatePicker()
datepicker.addTarget(self, action: #selector(handlerEvent), for: .valueChanged)

@objc func handlerEvent(_ sender: UIDatePicker) {
    print(sender.date)
}

// UIRefreshControl
let tableView = UITableView(frame: UIScreen.main.bounds)
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(handlerEvent), for: .valueChanged)
tableView.refreshControl = refreshControl

@objc func handlerEvent(_ sender: UIRefreshControl) {
    print(sender.isRefreshing)
}
複製代碼
  • iOS 14 支持 Action 回調的方式添加事件。
// UITextField
let textField = UITextField()
textField.addAction(
    UIAction { action in
        let textField = action.sender as! UITextField
        print(textField.text)
    },
    for: .editingChanged
)

// UIButton
// 方式一
let button = UIButton(primaryAction: UIAction { _ in
    print("按鈕點擊")
})
// 方式二
let btn = UIButton()
btn.addAction(
    UIAction { _ in
        print("按鈕點擊")
    },
    for: .touchUpInside
)

// UISwitch
let swi = UISwitch()
swi.addAction(
    UIAction { action in
        let swi = action.sender as! UISwitch
        print(swi.isOn)
    },
    for: .valueChanged
)

// UISlider
let slider = UISlider()
slider.addAction(
    UIAction { action in
        let slider = action.sender as! UISlider
        print(slider.value)
    },
    for: .valueChanged
)

// UIStepper
let stepper = UIStepper()
stepper.addAction(
    UIAction { action in
        let stepper = action.sender as! UIStepper
        print(stepper.value)
    },
    for: .valueChanged
)

// UISegmentedControl
let segmentedControl = UISegmentedControl()
segmentedControl.addAction(
    UIAction { action in
        let segmentedControl = action.sender as! UISegmentedControl
        print(segmentedControl.selectedSegmentIndex)
    },
    for: .valueChanged
)

// UIPageControl
let pageControl = UIPageControl()
pageControl.addAction(
    UIAction { action in
        let pageControl = action.sender as! UIPageControl
        print(pageControl.currentPage)
    },
    for: .valueChanged
)

// UIDatePicker
let datepicker = UIDatePicker()
datepicker.addAction(
    UIAction { action in
        let datepicker = action.sender as! UIDatePicker
        print(datepicker.date)
    },
    for: .valueChanged
)

// UIRefreshControl
let tableView = UITableView(frame: UIScreen.main.bounds)

let refreshControl = UIRefreshControl()
refreshControl.addAction(
    UIAction { action in
        let refreshControl = action.sender as! UIRefreshControl
        print(refreshControl.isRefreshing)
    },
    for: .valueChanged
)
tableView.refreshControl = refreshControl
複製代碼
相關文章
相關標籤/搜索