SnapKit,一个经典的Swift版的第三方库,专门用于项目的自动布局,目前在github上的stars就高达9340颗星,这是一个不小的数字,亦足以证明它存在的非凡意义和作用。作者认为,在iOS开发(swift)中,它是用于项目最优秀的自动布局的必选库之一,它的作者仍然是写Objective-C的第三方库Masonry的大牛 - @Robert Payne.
关于怎么安装就不说了,直接开搞
1. 实现一个宽高为100,居于当前视图的中心的视图布局
import UIKit
import SnapKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let testView = UIView()
testView.backgroundColor = UIColor.cyan
view.addSubview(testView)
testView.snp.makeConstraints { (make) in
make.width.equalTo(100) // 宽为100
make.height.equalTo(100) // 高为100
make.center.equalTo(view) // 位于当前视图的中心
}
}
}
或者可以使用简写方式
import UIKit
import SnapKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let testView = UIView()
testView.backgroundColor = UIColor.cyan
view.addSubview(testView)
testView.snp.makeConstraints { (make) in
make.width.height.equalTo(100) // 链式语法直接定义宽高
make.center.equalToSuperview() // 直接在父视图居中
}
}
}
import UIKit
import SnapKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 黑色视图作为父视图
let view1 = UIView()
view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
view1.center = view.center
view1.backgroundColor = UIColor.black
view.addSubview(view1)
// 测试视图
let view2 = UIView()
view2.backgroundColor = UIColor.magenta
view1.addSubview(view2)
view2.snp.makeConstraints { (make) in
make.top.equalToSuperview().offset(20) // 当前视图的顶部距离父视图的顶部:20(父视图顶部+20)
make.left.equalToSuperview().offset(20) // 当前视图的左边距离父视图的左边:20(父视图左边+20)
make.bottom.equalToSuperview().offset(-20) // 当前视图的底部距离父视图的底部:-20(父视图底部-20)
make.right.equalToSuperview().offset(-20) // 当前视图的右边距离父视图的右边:-20(父视图右边-20)
}
}
}
简写方式:
import UIKit
import SnapKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 黑色视图作为父视图
let view1 = UIView()
view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
view1.center = view.center
view1.backgroundColor = UIColor.black
view.addSubview(view1)
// 测试视图
let view2 = UIView()
view2.backgroundColor = UIColor.magenta
view1.addSubview(view2)
view2.snp.makeConstraints { (make) in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20))
}
}
}
import UIKit
import SnapKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 黑色视图作为父视图
let view1 = UIView()
view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
view1.center = view.center
view1.backgroundColor = UIColor.black
view.addSubview(view1)
// 测试视图
let view2 = UIView()
view2.backgroundColor = UIColor.magenta
view1.addSubview(view2)
view2.snp.makeConstraints { (make) in
// 让顶部距离view1的底部为10的距离
make.top.equalTo(view1.snp.bottom).offset(10)
// 设置宽、高
make.width.height.equalTo(100)
// 水平中心线<=view1的左边
make.centerX.lessThanOrEqualTo(view1.snp.leading)
}
}
}
从表中,我们知道,Snapkit的布局属性都是源自于系统的NSLayoutAttribute,那么,NSLayoutAttribute是个什么呢?其实,它在swift中是一个枚举,内部列举了很多布局属性诸如top、left、leading、centerX等,Snapkit的布局属性与它们都存在一一的对应关系。
几个比较难记或者说难操作的方法
import UIKit
import SnapKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 黑色视图作为父视图
let view1 = UIView()
view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
view1.center = view.center
view1.backgroundColor = UIColor.black
view.addSubview(view1)
// 测试视图
let view2 = UIView()
view2.backgroundColor = UIColor.magenta
view1.addSubview(view2)
view2.snp.makeConstraints { (make) in
// 让顶部距离view1的底部为10的距离
make.top.equalTo(view1.snp.bottom).offset(10)
// 设置宽、高
make.width.height.equalTo(100)
// 水平中心线<=view1的左边
make.left.greaterThanOrEqualTo(view1)
// 或者, 和上面一行代码一样的效果
// make.left.greaterThanOrEqualTo(view1.snp.left)
}
}
}
运行截图
其实,greaterThanOrEqualTo这个属性有点多余,比如上面这行代码 make.left.greaterThanOrEqualTo(view1) , 我们可以换成 make.left.equalToSuperview()或make.left.equalTo(view1.snp.left), 效果是一样的,也就是说,一般情况下 >= 或 <= 我们都可以直接用equalTo来代替!
// 黑色视图作为父视图
let view1 = UIView()
view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
view1.center = view.center
view1.backgroundColor = UIColor.black
view.addSubview(view1)
// 测试视图
let view2 = UIView()
view2.backgroundColor = UIColor.magenta
view1.addSubview(view2)
view2.snp.makeConstraints { (make) in
make.width.lessThanOrEqualTo(300)
make.width.greaterThanOrEqualTo(200)
make.height.equalTo(100)
make.center.equalToSuperview()
}
运行截图
很明显,最后的宽度是以make.width.greaterThanOrEqualTo(200)为标准的,也可以这样的,在同时使用两者的情况下,greaterThanOrEqualTo的优先级略比lessThanOrEqualTo的优先级高。值得一提的是, 在上面的例子中,如果我们只设置make.width.lessThanOrEqualTo(300),那么view2是不会显示出来的,因为view2不知道你要表达的是要显示多少,小于等于300,到底是100还是200呢?(这里指针对width和height)所以它不能确定这个约束的值,但是,如果我们单独设置make.width.greaterThanOrEqualTo(200),那么就和上面的效果一样,因为它会以200为标准布局约束
// 黑色视图作为父视图
let view1 = UIView()
view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
view1.center = view.center
view1.backgroundColor = UIColor.black
view.addSubview(view1)
// 测试视图
let view2 = UIView()
view2.backgroundColor = UIColor.magenta
view1.addSubview(view2)
view2.snp.makeConstraints { (make) in
make.left.lessThanOrEqualTo(20) // <= 父视图的左边+20
make.right.equalTo(-40) // = 父视图的右边-40
make.height.equalTo(100)
make.center.equalToSuperview()
}
make.left.equalToSuperview().offset(10)
make.left.equalTo(10)
make.left.equalTo(view1.snp.left).offset(10)
make.width.height.equalTo(100)
或
make.width.equalTo(100)
make.height.equalTo(100)
或
make.size.equalTo(CGSize(width: 100, height: 100))
// 黑色视图作为父视图
let view1 = UIView()
view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
view1.center = view.center
view1.backgroundColor = UIColor.black
view.addSubview(view1)
// 测试视图
let view2 = UIView()
view2.backgroundColor = UIColor.magenta
view1.addSubview(view2)
view2.snp.makeConstraints { (make) in
make.width.equalTo(100).priority(666)
make.width.equalTo(250).priority(999)
make.height.equalTo(111)
make.center.equalToSuperview()
}
运行截图
从上面我们可以知道, 我们设置了两个优先级:make.width.equalTo(100).priority(666) 和 make.width.equalTo(250).priority(999), 那运行结果是一个哪个为准呢?显然是以优先级为 999的为准,因为 priority(999)>priotity(666), 所以在使用Snapkit的过程中,有时我们可以使用优先级priority来设置我们的约束, 另外,值得一提的是,SnapKit的优先级最大值只能是 1000, 如果优先级的数值超过1000,则运行时就会Crash!这里要尤其注意
// 保存约束(引用约束)
var updateConstraint: Constraint?
override func viewDidLoad() {
super.viewDidLoad()
// 黑色视图作为父视图
let view1 = UIView()
view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
view1.center = view.center
view1.backgroundColor = UIColor.black
view.addSubview(view1)
// 测试视图
let view2 = UIView()
view2.backgroundColor = UIColor.magenta
view1.addSubview(view2)
view2.snp.makeConstraints { (make) in
make.width.height.equalTo(100) // 宽高为100
self.updateConstraint = make.top.left.equalTo(10).constraint // 距离父视图上、左为10
}
let updateButton = UIButton(type: .custom)
updateButton.backgroundColor = UIColor.brown
updateButton.frame = CGRect(x: 100, y: 80, width: 50, height: 30)
updateButton.setTitle("更新", for: .normal)
updateButton.addTarget(self, action: #selector(updateConstraintMethod), for: .touchUpInside)
view.addSubview(updateButton)
}
// 更新约束
func updateConstraintMethod() {
self.updateConstraint?.update(offset: 50) // 更新距离父视图上、左为50
}
说明1:如果你这是更新某个约束或某几个约束的常量值,你就可以使用updateConstraints而不是makeConstraints。
说明2:这个也是苹果推荐用来添加或更新约束的方式
说明3:这个方法可以调用多次,会相应setNeedsUpdateConstraints, 在控制器中,可以写在override func updateViewConstraints()方法里面(当然也可以写在你想要什么时候更新的点击事件里面)
import UIKit
import SnapKit
class ViewController: UIViewController {
lazy var blackView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
blackView.backgroundColor = UIColor.black
view.addSubview(blackView)
blackView.snp.makeConstraints { (make) in
// 四个约束确定位置和大小
make.width.equalTo(100)
make.height.equalTo(150)
make.top.equalTo(10)
make.centerX.equalToSuperview()
}
}
override func updateViewConstraints() {
blackView.snp.updateConstraints { (make) in
// 更新距离父视图顶部的约束(从 10 ---> 300 )
make.top.equalTo(300)
}
// 根据苹果,调用父类应该放在末尾
super.updateViewConstraints()
}
}
注意: 从上面的代码中我们很明确地知道, blackView通过width、height、top、centerX确定了它本身的大小和位置, 但是, 在 run 出来之后,它的top改变了距离, 从 10 变成了 300,其他三个约束保持不变
运行截图
显而易见, 除了top约束, 其他都没有改变! 也就是说,约束被更新(相当于系统升级一样,是一个道理)
现在,我们通过UIButton的点击事件来证明一下制作约束makeConstraints和updateConstraints具体的区别在哪里
lazy var blackView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
blackView.backgroundColor = UIColor.black
view.addSubview(blackView)
blackView.snp.makeConstraints { (make) in
make.width.equalTo(100)
make.height.equalTo(150)
make.top.equalTo(50)
make.centerX.equalToSuperview()
}
let btn = UIButton(type: .custom)
btn.backgroundColor = UIColor.brown
btn.frame = CGRect(x: 100, y: 200, width: 60, height: 30)
btn.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
view.addSubview(btn)
}
// 点击更新/制作约束
func buttonAction() {
blackView.snp.makeConstraints { (make) in
make.width.height.equalTo(20)
make.top.equalTo(300)
}
}
上面,我们知道,视图 blackView一开始是由四个约束确定位置和大小,在点击事情响应后,我们又给 blackView 制作(记住,是制作,而不是重做,两者有明确的区别)了3个约束,分别是 width、height、top, 那么这样做问题出现在哪里呢? 第一, 点击事情发生前(图1), 在点击事件发生后(见图2), 我们发现,blackView的width、height约束改变了,但是 top却没有改变,还是原来的距离父视图顶部 50 的距离, 原因在于,我们在原来的约束基础上又添加了多余的约束, 也就是说,约束从4个变成了7个(见图3左边constraints), 这样就产生了约束不明确,进而导致snapkit的警告(见图4), 这样布局显然是不可取的,在项目中这样做极其危险,甚至可能会导致异常奔溃!!!!
现在, 我们该将点击事件中的约束布局从makeConstraints改变成updateConstraints来试试两者有什么区别(下面只添加了点击事件的代码,其他事重复的就不多此一举了)
func buttonAction() {
// 注意这里是updateConstraints, 而不是makeConstraints
blackView.snp.updateConstraints { (make) in
make.width.height.equalTo(20)
make.top.equalTo(300)
}
print("这里试试snapkit有没有报警告")
}
发现没有,在将makeConstraints改变成updateConstraints之后,约束还是4个,snapkit没有报警告,点击事件中的width、height、top全部起了作用,而这就是两者的本质区别:makeConstraints是制作约束,在原来的基础上再添加另外的约束,也就是画蛇添足,约束增加,视图布局就有不确定性,从而有些约束起作用,有些不起作用(如上面的top),snapkit报警告!!!而updateConstraints是更新约束,改变原有约束,约束不会增加,没经过updateConstraints处理的保持原有约束,经过处理就更新约束,约束不会减少,snapkit不会产生警告,这是正常标准的更新约束的正确方式!!!
// 点击更新/制作约束
func buttonAction() {
// 注意这里是 remakeConstraints
blackView.snp.remakeConstraints { (make) in
make.width.height.equalTo(20)
make.top.equalTo(300)
}
print("这里试试snapkit有没有报警告")
}
我们看到, blackView重做了约束, 之前的约束不起任何作用,由于它在重做约束后只有 3 个约束分别是 width、height、top, 但是这里有一个问题,就是这 3 个约束只能确定大小,无法确定视图的位置, 所以在水平方向上或者左右缺少一个布局条件, 故 blackView整体视图的x紧靠左边(默认)! 另外我们发现, 在图(3)中,右上角出现了一个感叹号“!”, 那是因为告诉你缺少了一个约束条件:x-xcode-debug-views://7f81fcbc7900: runtime: Layout Issues: Horizontal position is ambiguous for UIView.
github地址
在项目开发的过程中,我们会使用到很多次的下拉刷新和下拉加载等操作。使用系统自带的UIRefreshController可以很方便的实现我们想要的功能。但是同样存在着很多比较优秀的下拉刷新框架,这里我们介绍的就是MJRefresh
MJRefresh是使用OC编写的第三方库,可以同时实现下拉刷新和上拉加载等操作。支持的组件有UIScrollView、UITableView、UICollectionView、UIWebView
使用的方式,如何安装,这里我们就不做介绍
给tableView添加刷新操作,每次刷新添加10条数据,并且完成Table的自动刷新
生成的数据会加载两秒钟,模拟网络请求
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var items:[String]!
var tableView:UITableView?
// 顶部刷新
let header = MJRefreshNormalHeader()
override func loadView() {
super.loadView()
}
override func viewDidLoad() {
super.viewDidLoad()
//随机生成一些初始化数据
refreshItemData()
//创建表视图
self.tableView = UITableView(frame: self.view.frame, style:.plain)
self.tableView!.delegate = self
self.tableView!.dataSource = self
//创建一个重用的单元格
self.tableView!.register(UITableViewCell.self,
forCellReuseIdentifier: "SwiftCell")
self.view.addSubview(self.tableView!)
//下拉刷新相关设置
header.setRefreshingTarget(self, refreshingAction: #selector(ViewController.headerRefresh))
self.tableView!.mj_header = header
}
//初始化数据
func refreshItemData() {
items = []
for _ in 0...9 {
items.append("条目\(Int(arc4random()%100))")
}
}
//顶部下拉刷新
@objc func headerRefresh(){
print("下拉刷新.")
sleep(2)
//重现生成数据
refreshItemData()
//重现加载表格数据
self.tableView!.reloadData()
//结束刷新
self.tableView!.mj_header.endRefreshing()
}
//在本例中,只有一个分区
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//返回表格行数(也就是返回控件数)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
//创建各单元显示内容(创建参数indexPath指定的单元)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
//为了提供表格显示性能,已创建完成的单元需重复使用
let identify:String = "SwiftCell"
//同一形式的单元格重复使用,在声明时已注册
let cell = tableView.dequeueReusableCell(withIdentifier: identify,
for: indexPath)
cell.accessoryType = .disclosureIndicator
cell.textLabel?.text = self.items[indexPath.row]
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var items:[String]!
var tableView:UITableView?
override func loadView() {
super.loadView()
}
override func viewDidLoad() {
super.viewDidLoad()
//随机生成一些初始化数据
refreshItemData()
//创建表视图
self.tableView = UITableView(frame: self.view.frame, style:.plain)
self.tableView!.delegate = self
self.tableView!.dataSource = self
//创建一个重用的单元格
self.tableView!.register(UITableViewCell.self,
forCellReuseIdentifier: "SwiftCell")
self.view.addSubview(self.tableView!)
//下拉刷新相关设置,使用闭包Block
self.tableView!.mj_header = MJRefreshNormalHeader(refreshingBlock: {
print("下拉刷新.")
sleep(2)
//重现生成数据
self.refreshItemData()
//重现加载表格数据
self.tableView!.reloadData()
//结束刷新
self.tableView!.mj_header.endRefreshing()
})
}
//初始化数据
func refreshItemData() {
items = []
for _ in 0...9 {
items.append("条目\(Int(arc4random()%100))")
}
}
//在本例中,只有一个分区
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//返回表格行数(也就是返回控件数)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
//创建各单元显示内容(创建参数indexPath指定的单元)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
//为了提供表格显示性能,已创建完成的单元需重复使用
let identify:String = "SwiftCell"
//同一形式的单元格重复使用,在声明时已注册
let cell = tableView.dequeueReusableCell(withIdentifier: identify,
for: indexPath)
cell.accessoryType = .disclosureIndicator
cell.textLabel?.text = self.items[indexPath.row]
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
header.lastUpdatedTimeLabel.isHidden = true
//隐藏时间
header.lastUpdatedTimeLabel.isHidden = true
//隐藏状态
header.stateLabel.isHidden = true
//修改文字
header.setTitle("下拉下拉下拉", for: .idle)
header.setTitle("松开松开松开", for: .pulling)
header.setTitle("刷新刷新刷新", for: .refreshing)
//修改字体
header.stateLabel.font = UIFont.systemFont(ofSize: 15)
header.lastUpdatedTimeLabel.font = UIFont.systemFont(ofSize: 13)
//修改文字颜色
header.stateLabel.textColor = UIColor.red
header.lastUpdatedTimeLabel.textColor = UIColor.blue
//下拉过程时的图片集合(根据下拉距离自动改变)
var idleImages = [UIImage]()
for i in 1...10 {
idleImages.append(UIImage(named:"idle\(i)")!)
}
header.setImages(idleImages, for: .idle) //idle1,idle2,idle3...idle10
//下拉到一定距离后,提示松开刷新的图片集合(定时自动改变)
var pullingImages = [UIImage]()
for i in 1...3 {
pullingImages.append(UIImage(named:"pulling\(i)")!)
}
header.setImages(pullingImages, for: .pulling)
//刷新状态下的图片集合(定时自动改变)
var refreshingImages = [UIImage]()
for i in 1...3 {
refreshingImages.append(UIImage(named:"refreshing\(i)")!)
}
header.setImages(refreshingImages, for: .refreshing)
//下面表示刷新图片在1秒钟的时间内播放一轮
header.setImages(refreshingImages, duration: 1, for: .refreshing)
//手动调用刷新效果
header.beginRefreshing()
初始化的时候自动生成20条数据用于表格默认显示
当点击最底部的“点击或上拉加载更多”,或者在列表底部继续上拉就会添加20条新数据进来(随机生成,生成数据的时候会等待2秒,模拟网络请求)
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var items:[String] = []
var tableView:UITableView?
// 底部加载
let footer = MJRefreshAutoNormalFooter()
override func loadView() {
super.loadView()
}
override func viewDidLoad() {
super.viewDidLoad()
//随机生成一些初始化数据
loadItemData()
//创建表视图
self.tableView = UITableView(frame: self.view.frame, style:.plain)
self.tableView!.delegate = self
self.tableView!.dataSource = self
//创建一个重用的单元格
self.tableView!.register(UITableViewCell.self,
forCellReuseIdentifier: "SwiftCell")
self.view.addSubview(self.tableView!)
//上刷新相关设置
footer.setRefreshingTarget(self, refreshingAction: #selector(ViewController.footerLoad))
//是否自动加载(默认为true,即表格滑到底部就自动加载)
footer.isAutomaticallyRefresh = false
self.tableView!.mj_footer = footer
}
//初始化数据
func loadItemData() {
for _ in 0...20 {
items.append("条目\(Int(arc4random()%100))")
}
}
//底部上拉加载
@objc func footerLoad(){
print("上拉加载.")
sleep(2)
//生成并添加数据
loadItemData()
//重现加载表格数据
self.tableView!.reloadData()
//结束刷新
self.tableView!.mj_footer.endRefreshing()
}
//在本例中,只有一个分区
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//返回表格行数(也就是返回控件数)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
//创建各单元显示内容(创建参数indexPath指定的单元)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
//为了提供表格显示性能,已创建完成的单元需重复使用
let identify:String = "SwiftCell"
//同一形式的单元格重复使用,在声明时已注册
let cell = tableView.dequeueReusableCell(withIdentifier: identify,
for: indexPath)
cell.accessoryType = .disclosureIndicator
cell.textLabel?.text = self.items[indexPath.row]
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var items:[String] = []
var tableView:UITableView?
override func loadView() {
super.loadView()
}
override func viewDidLoad() {
super.viewDidLoad()
//随机生成一些初始化数据
loadItemData()
//创建表视图
self.tableView = UITableView(frame: self.view.frame, style:.plain)
self.tableView!.delegate = self
self.tableView!.dataSource = self
//创建一个重用的单元格
self.tableView!.register(UITableViewCell.self,
forCellReuseIdentifier: "SwiftCell")
self.view.addSubview(self.tableView!)
//上拉加载相关设置,使用闭包Block
self.tableView!.mj_footer = MJRefreshAutoNormalFooter(refreshingBlock: {
print("上拉加载.")
sleep(2)
//生成并添加数据
self.loadItemData()
//重现加载表格数据
self.tableView!.reloadData()
//结束刷新
self.tableView!.mj_footer.endRefreshing()
})
}
//初始化数据
func loadItemData() {
for _ in 0...20 {
items.append("条目\(Int(arc4random()%100))")
}
}
//在本例中,只有一个分区
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//返回表格行数(也就是返回控件数)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
//创建各单元显示内容(创建参数indexPath指定的单元)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
//为了提供表格显示性能,已创建完成的单元需重复使用
let identify:String = "SwiftCell"
//同一形式的单元格重复使用,在声明时已注册
let cell = tableView.dequeueReusableCell(withIdentifier: identify,
for: indexPath)
cell.accessoryType = .disclosureIndicator
cell.textLabel?.text = self.items[indexPath.row]
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
MJRefresh上啦加载
- 自定义上啦刷新图标
同下拉组件里的刷新图标一样。上拉加载里的刷新图标我们也可以修改。通过设置一个图片数组,MJRefresh 就会自动播放这几张图片,形成动画
// 底部加载
let footer = MJRefreshAutoGifFooter()
var refreshingImages = [UIImage]()
for i in 1...3 {
refreshingImages.append(UIImage(named:"refreshing\(i)")!)
}
footer.setImages(refreshingImages, for: .refreshing)
//下面表示刷新图片在1秒钟的时间内播放一轮
footer.setImages(refreshingImages, duration: 1, for: .refreshing)
//刷新时不显示文字(其它情况下还是有提示文字的)
footer.isRefreshingTitleHidden = true
self.tableView!.mj_footer.endRefreshingWithNoMoreData()
设置为“全部加载”状态后,怎么上拉都不会触发加载事件。如果想恢复上拉加载功能,可以使用 resetNoMoreData() 方法进行重置。
self.tableView!.mj_footer.resetNoMoreData()
//修改文字
footer.setTitle("上拉上拉上拉", for: .idle)
footer.setTitle("加载加载加载", for: .refreshing)
footer.setTitle("没有没有更多数据了", for: .noMoreData)
//修改字体
footer.stateLabel.font = UIFont.systemFont(ofSize: 15)
//修改文字颜色
footer.stateLabel.textColor = UIColor.red
self.tableView!.mj_footer.isHidden = true
//自动回弹的上拉加载组件
let footer = MJRefreshBackNormalFooter()
// 底部加载
let footer = MJRefreshBackGifFooter()
//上拉过程时的图片集合(根据下拉距离自动改变)
var idleImages = [UIImage]()
for i in 1...10 {
idleImages.append(UIImage(named:"idle\(i)")!)
}
footer.setImages(idleImages, for: .idle) //idle1,idle2,idle3...idle10
//上拉到一定距离后,提示松开刷新的图片集合(定时自动改变)
var pullingImages = [UIImage]()
for i in 1...3 {
pullingImages.append(UIImage(named:"pulling\(i)")!)
}
footer.setImages(pullingImages, for: .pulling)
//刷新状态下的图片集合(定时自动改变)
var refreshingImages = [UIImage]()
for i in 1...3 {
refreshingImages.append(UIImage(named:"refreshing\(i)")!)
}
footer.setImages(refreshingImages, for: .refreshing)
(1)这个原来是 MJRefresh 提供的一个样例,我这里改成使用 Swift 实现。
(2)上拉组件视图中放置3个控件:UIActivityIndicatorView、UILabel、UISwitch。
(3)通常状态下开关是关闭的,到了“正在刷新”状态下开关会自动变成打开状态。
(4)UIActivityIndicatorView 活动指示器只有在“正在刷新”状态下会显示出来。
- 自定义组件的实现
class MJDIYAutoFooter: MJRefreshAutoFooter
{
var label:UILabel!
var s:UISwitch!
var loading:UIActivityIndicatorView!
//在这里做一些初始化配置(比如添加子控件)
override func prepare()
{
super.prepare()
// 设置控件的高度
self.mj_h = 50
// 添加label
self.label = UILabel()
self.label.textColor = UIColor(red:1.0, green:0.5, blue:0.0, alpha:1.0)
self.label.font = UIFont.boldSystemFont(ofSize: 16)
self.label.textAlignment = .center
self.addSubview(self.label)
// 打酱油的开关
self.s = UISwitch()
self.addSubview(self.s)
// loading
self.loading = UIActivityIndicatorView(activityIndicatorStyle: .gray)
self.addSubview(self.loading)
}
//在这里设置子控件的位置和尺寸
override func placeSubviews()
{
super.placeSubviews()
self.label.frame = self.bounds
self.s.center = CGPoint(x:self.mj_w - 20, y:self.mj_h - 20)
self.loading.center = CGPoint(x:30, y:self.mj_h * 0.5)
}
//监听控件的刷新状态
override var state: MJRefreshState{
didSet
{
switch (state) {
case .idle:
self.label.text = "赶紧上拉吖(开关是打酱油滴)"
self.loading.stopAnimating()
self.s.setOn(false, animated:true)
break
case .refreshing:
self.s.setOn(true, animated:true)
self.label.text = "加载数据中(开关是打酱油滴)"
self.loading.startAnimating()
break
case .noMoreData:
self.label.text = "木有数据了(开关是打酱油滴)"
self.s.setOn(false, animated:true)
self.loading.stopAnimating()
break
default:
break
}
}
}
//监听scrollView的contentOffset改变
override func scrollViewContentOffsetDidChange(_ change: [AnyHashable : Any]!) {
super.scrollViewContentOffsetDidChange(change)
}
//监听scrollView的contentSize改变
override func scrollViewContentSizeDidChange(_ change: [AnyHashable : Any]!) {
super.scrollViewContentSizeDidChange(change)
}
//监听scrollView的拖拽状态改变
override func scrollViewPanStateDidChange(_ change: [AnyHashable : Any]!) {
super.scrollViewPanStateDidChange(change)
}
}
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var items:[String] = []
var tableView:UITableView?
// 底部加载
let footer = MJDIYAutoFooter()
override func loadView() {
super.loadView()
}
override func viewDidLoad() {
super.viewDidLoad()
//随机生成一些初始化数据
loadItemData()
//创建表视图
self.tableView = UITableView(frame: self.view.frame, style:.plain)
self.tableView!.delegate = self
self.tableView!.dataSource = self
//创建一个重用的单元格
self.tableView!.register(UITableViewCell.self,
forCellReuseIdentifier: "SwiftCell")
self.view.addSubview(self.tableView!)
//上刷新相关设置
footer.setRefreshingTarget(self, refreshingAction: #selector(ViewController.footerLoad))
//是否自动加载(默认为true,即表格滑到底部就自动加载)
footer.isAutomaticallyRefresh = false
self.tableView!.mj_footer = footer
}
//初始化数据
func loadItemData() {
for _ in 0...20 {
items.append("条目\(Int(arc4random()%100))")
}
}
//底部上拉加载
@objc func footerLoad(){
print("上拉加载.")
sleep(2)
//生成并添加数据
loadItemData()
//重现加载表格数据
self.tableView!.reloadData()
//结束刷新
self.tableView!.mj_footer.endRefreshing()
}
//在本例中,只有一个分区
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//返回表格行数(也就是返回控件数)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
//创建各单元显示内容(创建参数indexPath指定的单元)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
//为了提供表格显示性能,已创建完成的单元需重复使用
let identify:String = "SwiftCell"
//同一形式的单元格重复使用,在声明时已注册
let cell = tableView.dequeueReusableCell(withIdentifier: identify,
for: indexPath)
cell.accessoryType = .disclosureIndicator
cell.textLabel?.text = self.items[indexPath.row]
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
github地址
LLCyckeScrollView是一款功能比较强大的轮播图插件,这个使用的方式就不在显示,直接开始主要内容
我们在使用的时候一般会将轮播图作为一个组件添加到页面中,所以我这里直接使用一个闭包来实现
let banner = LLCycleScrollView.llCycleScrollViewWithFrame(CGRect.init(x:0,y:banner.lly + 205,width:UIScreen.main.bound.size.width,height:200))
//设置是否自动滚动
banner.autoScroll = true
//是否无限循环
banner.infiniteLoop = true
//滚动时间间隔
banner.autoScrollTimeInterval = 3.0
//等待图片显示时的占位图片
bannerDemo.placeHolderImage = #UIImage
// 如果没有数据的时候,使用的封面图
bannerDemo.coverImage = #UIImage
// 设置图片显示方式=UIImageView的ContentMode
bannerDemo.imageViewContentMode = .scaleToFill
// 设置滚动方向( vertical || horizontal )
bannerDemo.scrollDirection = .vertical
// 设置当前PageControl的样式 (.none, .system, .fill, .pill, .snake)
bannerDemo.customPageControlStyle = .snake
// 非.system的状态下,设置PageControl的tintColor
bannerDemo.customPageControlInActiveTintColor = UIColor.red
// 设置.system系统的UIPageControl当前显示的颜色
bannerDemo.pageControlCurrentPageColor = UIColor.white
// 非.system的状态下,设置PageControl的间距(默认为8.0)
bannerDemo.customPageControlIndicatorPadding = 8.0
// 设置PageControl的位置 (.left, .right 默认为.center)
bannerDemo.pageControlPosition = .center
// 背景色
bannerDemo.collectionViewBackgroundColor
//将数据添加到view中
view.addSubview(banner)
// 模拟网络图片获取
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(2)) {
bannerDemo.imagePaths = imagesURLStrings
}
github地址
我们在进行开发的过程中,肯定会遇到自定义tableView的cell的情况,那么每次我们在使用cell的时候都需要设置一些表示类的内容,那么这样也相当增加我们的工作量。那么Reusable就是Swift下的一个通过protocol extension结合泛型来进行优雅的解决方案。(一听就知道是官方的话,反正意思就是说好用)
首先需要让我们的Cell申明为Reusable或者是NibReusable
//如果cell定义在xib中,声明NibReusable
class MyCustomCell: UITableViewCell, NibReusable { }
//如果cell是基于纯代码的,声明Reusable
class MyCustomCell: UITableViewCell, Reusable { }
其次,在tableView或者CollectionView中使用register方法
tableView.registerReusableCell(MyCustomCell)
最后在使用的时候,直接上就可以了
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: MyCustomCell = tableView.dequeueReusableCell(indexPath: indexPath)
… // configure the cell, which is already of the expected MyCustomCell type
return cell
}
UIView对象声明NibLoadable协议
利用MyCustomView.loadFromNib()
就可以从“MyCustomView.xib”中实例化返回MyCustomView的实例对象
UIViewController对象声明StoryboardBased
或者StoryboardSceneBased
协议。
利用YourCustomViewController.instantiate()
就可以从Storyboard中实例化返回实例对象
使用自己的类名,从新做一个标识
先定义一个静态变量,如下
protocol Reusable: class {
static var reuseIdentifier: String { get }
}
extension Reusable {
static var reuseIdentifier: String {
// I like to use the class's name as an identifier
// so this makes a decent default value.
return String(Self)
}
}
接着我们在给tableView写一个自定义获取reuse cell的方法
func dequeueReusableCell<T: Reusable>(indexPath indexPath: NSIndexPath) -> T {
return self.dequeueReusableCellWithIdentifier(T.reuseIdentifier, forIndexPath: indexPath) as! T
}
注意这个泛型参数,这个泛型是根据返回值的类型来确定的。所以返回的cell必须实现Reusable协议。我们将这类型里的那个静态变量T.reuseIdentifier作为Identifier。
我们当然还要同时改造register方法。
public extension UITableView {
final func registerReusableCell<T: UITableViewCell where T: Reusable>(cellType: T.Type) {
self.registerClass(cellType.self, forCellReuseIdentifier: cellType.reuseIdentifier)
}
}
这个时候我们忽然意识到,还有registerNib
没有解决。
思路也是相似的,给协议再增加一个返回nib对象的静态变量呗。就像这样:
protocol Reusable: class {
static var reuseIdentifier: String { get }
static var nib: UINib? { get }
}
实现是这样子的
static var nib: UINib {
return UINib(nibName: String(self), bundle: NSBundle(forClass: self))
}
但是这里再往深一点想,其实载入nib和reuseIdentifier是两件事,因为我们有时也会从xib获取其他UIView的对象。protocol也提供了组合的特性。所以我们可以把获取nib单独拆出来。
public protocol NibLoadable: class {
/// The nib file to use to load a new instance of the View designed in a XIB
static var nib: UINib { get }
}
public protocol NibReusable: Reusable, NibLoadable {}
最后一步
final func registerReusableCell(cellType: T.Type) {
self.registerNib(cellType.nib, forCellReuseIdentifier: cellType.reuseIdentifier)
}
同样可以再写一个UIView的扩展,根据类型获取实例
public extension NibLoadable where Self: UIView {
static func loadFromNib() -> Self {
guard let view = nib.instantiateWithOwner(nil, options: nil).first as? Self else {
fatalError("The nib \(nib) expected its root view to be of type \(self)")
}
return view
}
}
github地址
MBProgressHUD简单来说就是显示一个弹窗,然后这个弹窗可以是正在加载,也可以是提示信息等。
@IBAction func TextDialogBtn(sender: AnyObject) {
showTextDialog()
}
@IBAction func ProgressDialogBtn1(sender: AnyObject) {
showProgressDialog1()
}
@IBAction func ProgressDialogBtn2(sender: AnyObject) {
showProgressDialog2()
}
@IBAction func CustomDialogBtn(sender: AnyObject) {
showCustomDialog()
}
@IBAction func AllTextDialogBtn(sender: AnyObject) {
showAllTextDialog()
}
var HUD : MBProgressHUD?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
//文本提示框
func showTextDialog(){
//初始化对话框,置于当前的View当中
HUD = MBProgressHUD(view: self.view)
self.view.addSubview(HUD!)
//如果设置此属性,则当前view置于后台
HUD?.dimBackground = true
//设置对话框文字
HUD?.labelText = "请稍等"
HUD?.showAnimated(true, whileExecutingBlock: {
sleep(3)
}, completionBlock: {
self.HUD?.removeFromSuperview()
self.HUD = nil
})
}
//框型进度提示
func showProgressDialog1(){
//初始化对话框,置于当前的View当中
HUD = MBProgressHUD(view: self.view)
self.view.addSubview(HUD!)
//如果设置此属性,则当前view置于后台
HUD?.dimBackground = true
//设置对话框文字
HUD?.labelText = "正在加载"
//设置模式为进度框形的
HUD?.mode = MBProgressHUDMode.Determinate
HUD?.showAnimated(true, whileExecutingBlock: {
var progress : Float = 0.0
while(progress < 1.0){
progress += 0.01
self.HUD?.progress = progress
usleep(50000)
}
}, completionBlock: {
self.HUD?.removeFromSuperview()
self.HUD = nil
})
}
//进度条提示
func showProgressDialog2(){
//初始化对话框,置于当前的View当中
HUD = MBProgressHUD(view: self.view)
self.view.addSubview(HUD!)
//如果设置此属性,则当前view置于后台
HUD?.dimBackground = true
//设置对话框文字
HUD?.labelText = "正在加载"
//设置模式为进度条
HUD?.mode = MBProgressHUDMode.DeterminateHorizontalBar
HUD?.showAnimated(true, whileExecutingBlock: {
var progress : Float = 0.0
while(progress < 1.0){
progress += 0.01
self.HUD?.progress = progress
usleep(50000)
}
}, completionBlock: {
self.HUD?.removeFromSuperview()
self.HUD = nil
})
}
//自定义提示
func showCustomDialog(){
//初始化对话框,置于当前的View当中
HUD = MBProgressHUD(view: self.view)
self.view.addSubview(HUD!)
//如果设置此属性,则当前view置于后台
HUD?.dimBackground = true
//设置对话框文字
HUD?.labelText = "操作成功"
//设置模式为自定义
HUD?.mode = MBProgressHUDMode.CustomView
HUD?.customView = UIImageView(image: UIImage(named: "37x-Checkmark-1"))
HUD?.showAnimated(true, whileExecutingBlock: {
sleep(2)
}, completionBlock: {
self.HUD?.removeFromSuperview()
self.HUD = nil
})
}
//纯文本提示
func showAllTextDialog(){
//初始化对话框,置于当前的View当中
HUD = MBProgressHUD(view: self.view)
self.view.addSubview(HUD!)
//如果设置此属性,则当前view置于后台
HUD?.dimBackground = true
//设置模式为纯文本提示
HUD?.mode = MBProgressHUDMode.Text
//设置对话框文字
HUD?.labelText = "操作成功"
//指定距离中心点的X轴和Y轴的偏移量,如果不指定则在屏幕中间显示
// HUD?.yOffset = 150.0
// HUD?.xOffset = 150.0
HUD?.showAnimated(true, whileExecutingBlock: {
sleep(2)
}, completionBlock: {
self.HUD?.removeFromSuperview()
self.HUD = nil
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
github地址
github地址
github地址
github地址
KingFisher是一款专门针对下载和缓存图片的框架,通过使用KingFisher可以非常合理的对缓存进行管理
使用的方式非常简单
let url = URL(string: "url_of_your_image")
imageView.kf.setImage(with: url)
github地址
github地址
github地址
Swift自动布局SnapKit
MJRefresh
MJRefresh
Swift强大的轮播图
Reusable
MBProgressHUD
HMSegmentedControl
IQKeyboardManager
DZNEmptyDataSet
KingFisher
HandyJson
Moya网络抽象层
[Swift让人眼前一亮的初始化方式–Then][https://segmentfault.com/a/1190000006992493]