1. 程式人生 > >Swift:我的第三個Demo

Swift:我的第三個Demo

這裡Demo工作量是我目前做的做大的,相應的知識點有

1 頁面UI佈局

2 delegate委託模式的實現

3 Alamofire網路請求

4 JSON初體驗

5 自定義TableViewCell

6 NavigationViewController初體驗

7 Kingfisher載入圖片的使用

8 TableView的介面自定義,如何重新整理介面

 

程式碼如下

檔名:Appdelegate.swift

//
//  AppDelegate.swift
//  RxSwiftTest
//
//  Created by travey on 2018/11/5.
//  Copyright © 2018年 ZhouShijie. All rights reserved.
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow? // 系統自帶的window檢視,需要給他配置根檢視

    // 這個函式是程式的入口
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        window = UIWindow(frame: UIScreen.main.bounds) // 建立一個window的例項
        let navigationViewController = UINavigationController.init(rootViewController: ViewController()) // 建立一個UINavigationController的例項,並且初始化他的根檢視是ViewController型別的一個例項,UINavigationController是UIViewcontroller的子類
        window?.backgroundColor = .white // 設定window的背景顏色為白色
        window?.rootViewController = navigationViewController // 設定window的根檢視為剛才定義的navigationViewController
        window?.makeKeyAndVisible() // 這句話必須加
        return true
    }

    func applicationWillResignActive(_ application: UIApplication) {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
    }

    func applicationDidEnterBackground(_ application: UIApplication) {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    }

    func applicationWillEnterForeground(_ application: UIApplication) {
        // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
    }

    func applicationDidBecomeActive(_ application: UIApplication) {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    }

    func applicationWillTerminate(_ application: UIApplication) {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    }


}

檔名: ViewController.swift


//  ViewController.swift
//  RxSwiftTest
//
//  Created by travey on 2018/11/5.
//  Copyright © 2018年 ZhouShijie. All rights reserved.


import UIKit
import RxSwift
import RxCocoa
import SnapKit
import RxDataSources
import Kingfisher
import SwiftyJSON
import Alamofire

// 定義一個圖書的類,這個是資料來源的資料結構型別,三個變數都是字串
class Book: NSObject {
    var title = "" // 主標題
    var subtitle = "" // 副標題
    var image = "" // 圖片的URL字串
}

class ViewController: UIViewController {
    
    // 定義各種空間
    var addbtn: UIBarButtonItem! // 新增按鈕,這裡沒有實現相應的相應
    var refreshbtn: UIBarButtonItem! // 重新整理按鈕
    var tableView: UITableView! // 表單
    var canEditbtn: UIBarButtonItem! // 表單是否能編輯按鈕
    var originalNumberOfImage: Int! // 顯示圖書的原本個數

    let URLString = "https://api.douban.com/v2/book/search" // 網路請求的URL
    public var books = [Book]() // 圖書陣列的初始化
    
    override func viewDidLoad() {
        
        // 建立三個按鈕的例項,這裡的按鈕是UIBarButtonItem而不是UIButton型別
        // 這三個不需要用view.addSubview的方法,切記!
        // UIBarButtonItem型別的觸發時間預設就是點一下就觸發,可以理解為TouchUpSide
        addbtn = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.add, target: self, action: nil) // 這個新增按鈕就是做個擺設,這裡用到了系統提供的預設圖示
        canEditbtn = UIBarButtonItem(title: "編輯", style: .plain, target: self, action: #selector(clickEditbtn(sender:))) // 自定義圖示,可以自己設定title,同時設定觸發時間
        refreshbtn = UIBarButtonItem(title: "重新整理", style: .plain, target: self, action: #selector(clickRefreshbtn(sender:))) // 同上,也設定了觸發時時間
        
        // 為了佈局,要設定兩個按鈕之間的間隙,以及最右邊的按鈕和右邊界之間的空隙
        let gap = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
        gap.width = 15 // gap指兩個按鈕之間的間隙
        let spacer = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
        spacer.width = -10 // spacer指最後邊的addbtn和右邊界之間的空隙
        
        // 將四個控制元件放置到導航欄上
        self.navigationItem.rightBarButtonItems  = [spacer, addbtn, gap, canEditbtn] //  這個的順序是從右往左,因此在螢幕上顯示的是逆序,即先顯示canEditbtn,以此類推
        self.navigationItem.leftBarButtonItem = refreshbtn
        
        // 建立表單
        tableView = UITableView(frame: view.frame)
        // 註冊cell,標記為"cell"
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        view.addSubview(tableView) // 這個要用這種方式新增哦
        
        // 設定表單代理
        tableView.delegate = self // UITableView類裡面有一個delegate變數,要遵循UITableViewDelegate協議,下面的dataSource是一樣的
        tableView.dataSource = self
        
        // 完成網路請求,這裡的Alamofire第三方庫是個難點,要重點掌握!
        // 先request申請資料,有五個引數,這裡省略了頭部和編碼方式,剩下的分別是URLString這個字串,方法為get方法,因為是get方法,因此引數是存在URL中的,引數parameters的鍵值對需要和搞後端的同學商量,一般是通過介面文件來實現,這樣我就把Alamofire的request請求講的差不多了
        // validate是驗證函式,先不管他
        // 剩下的就是響應資料了,responseJSON返回的是JSON資料,有三個引數,前兩個分別是佇列和可選項,這裡省略了,第三個引數是一個閉包,因此可以用結尾閉包的方式,並且可以省略前面的圓括號,所以responseJSON後面直接接了一個大括號,閉包傳進來的引數是resp,即相應,如果出錯就列印錯誤,否則就把他的結果的value先儲存給臨時變數value,再將這個臨時變數value轉成JSON型別,這個JSON型別是一個字典,我們取key為"books"型別的,取完以後還是一個JSON,JSON中有一個計算屬性array,可以將JSON轉化為array型別,之後我們就可以去根據JSON的key來取對應的value啦,是不是很方便呢?
        // 注意,這裡的網路請求是一個非同步的過程,這個請求不是跑在主執行緒的,而是在另外一個執行緒,非同步執行,因此如果我們想獲取books陣列最終的值,得在這個請求的最後儲存,不能在請求外面儲存,否則會返回0,0顯然是個錯誤的結果
        Alamofire.request(URLString, method: .get, parameters: ["tag": "Swift", "count": 10]).validate().responseJSON { (resp) in
            if let error = resp.result.error {
                print(error)
            } else if let value = resp.result.value, let jsonArray = JSON(value)["books"].array {
                // 把數組裡面的內容一個一個存到books中
                for json in jsonArray {
                    let book = Book()
                    book.title = json["title"].string ?? ""
                    book.subtitle = json["subtitle"].string ?? ""
                    book.image = json["image"].string ?? ""
                    self.books.append(book)
                }
                //存完以後books就有10個數,我們重載入一下
                self.tableView.reloadData()
                // 這裡記錄了數組裡原本元素的個數
                self.originalNumberOfImage = self.books.count
            }
        } // 請求結束
    } //viewDidLoad結束

    //viewDidLoad結束之後,別忘了剛才定義的按鈕還有相應時間,我們在class的內部還要實現它們
    
    // 編輯按鈕的點選時間
    @objc func clickEditbtn(sender: UIBarButtonItem) {
        if sender.title == "編輯" {
            tableView.setEditing(true, animated: true) // 設定表單是否可以編輯,初始值為編輯,因此這裡是可以編輯
            sender.title = "完成" // 對應的要改為完成
            print("現在表單可以編輯了")
        } else {
            tableView.setEditing(false, animated: true) // 下面同理
            sender.title = "編輯"
            print("現在表單完成編輯")
        }
    }

    @objc func clickRefreshbtn(sender: UIBarButtonItem) { // 讓表單迴歸原始頁面
        while books.count != originalNumberOfImage {
            books.removeLast()
        }
        tableView.reloadData() // 並且過載,這個過載就相當於把DataSource和Delegate裡面的方法又重新跑了一遍,因為我此時book更新了,因此相應的count啊什麼的也同步更新,所以表單自然就重新整理載入了一遍
    }
}

// 委託模式下,繼承UITable類中的UITableViewDelegate協議,並且重寫函式
extension ViewController: UITableViewDelegate {
    
    // 返回一層cell的高度
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UIScreen.main.bounds.height / 8
    }
    
    // 指定某一行的編輯型別
    func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
        if indexPath.row == books.count - 1 { // 只有最後一行可以新增
            return .insert
        }
        return .delete // 其餘可以刪除
    }
    
}

// 委託模式下,繼承UITable類中的UITableViewDataSource協議,並且重寫函式
extension ViewController: UITableViewDataSource {
    
    // 返回對應的section有多少行
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return books.count // 這裡和資料來源實時更新
    }
    
    // 表單的某一行是否可編輯(編輯型別有三個,分別是.none .insert .delete)
    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }
    
    // 返回一個UITableViewCell型別的一行
    // 這裡又是一個難點,我們不用預設的cell,而是自定義一個MyTableViewCell
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var cell: MyTableViewCell? = tableView.dequeueReusableCell(withIdentifier: "cell") as? MyTableViewCell // 先從緩衝池裡面取一個,型別為MyTableViewCell
        // 建立一個MyTableViewCell型別的物件,因為MyTableViewCell是繼承了TableViewCell,因此可以有同樣的初始化函式
        cell = MyTableViewCell(style: .default, reuseIdentifier: "cell")
        // 下面的操作是根據當前行,從books裡面取對應的元素,然後放置到cell裡面,最後返回cell
        let book = books[indexPath.row]
        cell?.title.text = book.title
        cell?.subTitle.text = book.subtitle
        // 這裡用到了第三方圖片載入庫Kingfisher,佔位符要有哦
        cell?.iconImageView?.kf.setImage(with: URL(string: book.image), placeholder: "place.jpg" as? Placeholder)
        return cell!
    }
    
    // 允許某一行完成拖動
    func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
        return true
    }
    
    // 實現插入和刪除的具體方法,輸出的第二個引數是UITableViewCell.EditingStyle型別的,有.delete,.none和.insert三種,需要根據相應的型別實現不同的功能
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        // 如果是刪除,就要從資料來源books中移除相應的元素,然後過載
        if editingStyle == .delete {
            books.remove(at: indexPath.row)
            tableView.reloadData()
            //tableView.deleteRows(at: [indexPath], with: UITableView.RowAnimation.left)
            print("已經刪除")
        } else if editingStyle == .insert {
            // 如果是插入,就有點難理解了,這裡我們需要跳到另外一個頁面,另外一個頁面讓你輸入主標題,副標題和圖片網址,點選按鈕之後跳回原來頁面,會發現新增了一頁,更新的內容對應剛才輸入的
            // 因為push的時候需要傳入VC,因此需要建一個另外一個頁面的VC
            // 這裡的頁面之間傳參我們仍然使用委託模式的方式,另外一個頁面名字叫InsertNewRow,這個頁面要傳參給本頁面,因此需要在InsertNewRow頁面中定義一個協議,自己持有一個delegate,並且遵循該協議,這個協議中的方法只有有一個引數要傳入InsertNewRow本身。在ViewController頁面中,實現了該協議,並且讓InsertNewRow
            let vc = InsertNewRow()
            vc.delegate = self // 設定vc的代理為自己,自己也遵守協議,並且實現了該協議
            self.navigationController?.pushViewController(vc, animated: true)
        }
    }

    
    // 返回section的個數
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
}

extension ViewController: InsertNewRowDelegate {
    func insertNewRow(_ insertNewRow: InsertNewRow, didCompleteWithTitle title: String, didCompleteWithSubtitle subTitle: String, didCompleteWithImage imageURL: String) {
        let book = Book()
        book.title = title
        book.subtitle = subTitle
        book.image = imageURL
        print("aaa \(book.image)")
        self.books.append(book)
        self.tableView.reloadData()
    }
}

檔名:InsertNewRow.swift

//
//  InsertNewRow.swift
//  RxSwiftTest
//
//  Created by travey on 2018/11/8.
//  Copyright © 2018 ZhouShijie. All rights reserved.
//

import Foundation
import UIKit
import SnapKit

// 委託模式必須要有的協議
protocol InsertNewRowDelegate {
    func insertNewRow(_ insertNewRow: InsertNewRow, didCompleteWithTitle title: String, didCompleteWithSubtitle subTitle: String, didCompleteWithImage imageURL: String)
}

class InsertNewRow: UIViewController {
    
    var delegate: InsertNewRowDelegate? // 這個可以理解delegate是沒有型別的,但是delegate一定要實現對應協議裡面的方法

    // 下面是UI佈局
    lazy var label1: UILabel! = {
        let label1 = UILabel()
        label1.text = "請輸入主標題"
        return label1
    }()
    
    lazy var label2: UILabel! = {
        let label2 = UILabel()
        label2.text = "請輸入副標題"
        return label2
    }()
    
    lazy var label3: UILabel! = {
        let label3 = UILabel()
        label3.text = "請輸入圖片網址"
        return label3
    }()
    
    lazy var inputTitle: UITextField! = {
        let inputTitle = UITextField()
        inputTitle.textColor = UIColor.black
        inputTitle.layer.borderWidth = 1
        return inputTitle
    }()
    
    lazy var inputSubtitle: UITextField! = {
        let inputSubtitle = UITextField()
        inputSubtitle.textColor = UIColor.black
        inputSubtitle.layer.borderWidth = 1
        return inputSubtitle
    }()
    
    lazy var inputImageURL: UITextField! = {
        let inputImageURL = UITextField()
        inputImageURL.textColor = UIColor.black
        inputImageURL.layer.borderWidth = 1
        inputImageURL.placeholder = "https://avatar.csdn.net/0/5/E/1_shijie97.jpg"
        return inputImageURL
    }()
    
    lazy var btn: UIButton! = {
        let btn = UIButton()
        btn.layer.borderWidth = 1
        btn.setTitleColor(UIColor.black, for: .normal)
        btn.addTarget(self, action: #selector(confirmAction(sender:)), for: .touchUpInside)
        btn.setTitle("確定", for: .normal)
        return btn
    }()
    
    override func viewDidLoad() {
        
        // 新增控制元件
        view.addSubview(inputTitle)
        view.addSubview(inputSubtitle)
        view.addSubview(inputImageURL)
        view.addSubview(label1)
        view.addSubview(label2)
        view.addSubview(label3)
        view.addSubview(btn)
        
        //約束
        makeConstraints()
    }
    
    func makeConstraints() {
        // 下面是約束
        label1.snp.makeConstraints { (make) in
            make.height.equalTo(50)
            make.width.equalTo(200)
            make.centerX.equalToSuperview()
            make.top.equalTo(view.snp.top).offset(100)
        }
        inputTitle.snp.makeConstraints { (make) in
            make.height.equalTo(50)
            make.width.equalTo(200)
            make.centerX.equalToSuperview()
            make.top.equalTo(label1.snp.bottom).offset(20)
        }
        label2.snp.makeConstraints { (make) in
            make.height.equalTo(50)
            make.width.equalTo(200)
            make.centerX.equalToSuperview()
            make.top.equalTo(inputTitle.snp.bottom).offset(20)
        }
        inputSubtitle.snp.makeConstraints { (make) in
            make.height.equalTo(50)
            make.width.equalTo(200)
            make.centerX.equalToSuperview()
            make.top.equalTo(label2.snp.bottom).offset(20)
        }
        label3.snp.makeConstraints { (make) in
            make.height.equalTo(50)
            make.width.equalTo(200)
            make.centerX.equalToSuperview()
            make.top.equalTo(inputSubtitle.snp.bottom).offset(20)
        }
        inputImageURL.snp.makeConstraints { (make) in
            make.height.equalTo(50)
            make.width.equalTo(200)
            make.centerX.equalToSuperview()
            make.top.equalTo(label3.snp.bottom).offset(20)
        }
        btn.snp.makeConstraints { (make) in
            make.height.equalTo(50)
            make.width.equalTo(80)
            make.centerX.equalToSuperview()
            make.top.equalTo(inputImageURL.snp.bottom).offset(20)
        }
    }
    
    // 點選按鈕發生的事件,點選按鈕之後要把引數傳到傳過去,這個引數被ViewController裡面的遵循協議的方法使用了,是把引數加到books數組裡面,完成了頁面傳參的工作
    @objc func confirmAction(sender: UIButton) {
        // 這裡的self.delegate就是我們的ViewController,因為在VC中我設定了vc.delegate = self,因此這句話相當於是另外一個頁面的例項呼叫了這個方法
        self.delegate?.insertNewRow(self, didCompleteWithTitle: inputTitle.text ?? "", didCompleteWithSubtitle: inputSubtitle.text ?? "", didCompleteWithImage: inputImageURL.text ?? "")
        self.navigationController?.popViewController(animated: true) // 返回原來頁面
    }
}

檔名:MyTableViewCell.swift

//
//  MyTableViewCell.swift
//  RxSwiftTest
//
//  Created by travey on 2018/11/8.
//  Copyright © 2018 ZhouShijie. All rights reserved.
//

import UIKit
import SnapKit
import Foundation


class MyTableViewCell: UITableViewCell {

    var iconImageView: UIImageView!
    var title: UILabel!
    var subTitle: UILabel!
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        iconImageView = UIImageView()
        title = UILabel()
        subTitle = UILabel()
        
//        title.font = UIFont.systemFont(ofSize: 14)
        title.adjustsFontSizeToFitWidth = true // 這句話可以自動實現label的字型和cell高度保持比例
        title.textColor = UIColor.black
        title.layer.borderWidth = 0
        
//        subTitle.font = UIFont.systemFont(ofSize: 14)
        subTitle.adjustsFontSizeToFitWidth = true
        subTitle.textColor = UIColor.black
        subTitle.layer.borderWidth = 0
        subTitle.numberOfLines = 2
        
        self.contentView.addSubview(iconImageView)
        self.contentView.addSubview(title)
        self.contentView.addSubview(subTitle)
        
        iconImageView.snp.makeConstraints { (make) in
            make.height.equalToSuperview()
            make.width.equalTo(iconImageView.snp.height)
            make.centerY.equalToSuperview()
            make.right.equalToSuperview()
        }
        title.snp.makeConstraints { (make) in
            make.left.equalToSuperview()
            make.right.lessThanOrEqualTo(iconImageView.snp.left)
            make.height.equalToSuperview().multipliedBy(0.5)
            make.height.equalToSuperview()
            make.top.equalToSuperview()
            
        }
        subTitle.snp.makeConstraints { (make) in
            if subTitle.text != ""{
                make.height.equalToSuperview().multipliedBy(0.5)
                make.left.equalToSuperview()
                make.right.lessThanOrEqualTo(iconImageView.snp.left)
                make.bottom.equalToSuperview()
            }
        }
    }
    
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}