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
    }

}

 

猜你喜欢

转载自blog.csdn.net/shijie97/article/details/83897771