BartonTang的博客


  • 首页

  • 关于

  • 归档

  • 标签

更好的模式,更轻量级的UITableView

发表于 2016-10-06 |

更好的模式,更轻量级的UITableView

在软件开发过程中我们经常会运用各类设计模式来使得代码节藕,让代码看起来更整洁,Gof的23种设计模式,值得一看。其他先不谈,先聊聊经常遇到包括去面试的时候面试官会和你聊的MVC等。

Model-View-Controller

MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。
stop,不想copy了,大家直接看百度吧。😄
MVC百度介绍

对MVC的延伸扩展有MVVM,MVP大家自行百度吧,不论神马设计模式,请记住切忌不要过分依赖和相信他,有的时候回归本我或许能带给大家更好的效果。

文章本天成,妙手偶得之
设计亦如此

大而空的介绍那些不想讲了,大家有没发现在包含UITableView的ViewController的时候,必须去实现一堆protocol。一个字:烦,而且这好多重复的代码,每个UITableView其实都大同小异,有多少个cell,每个cell的样式是怎么样的,然后cell里的内容根据model来显示等等。而且这里面会有个小问题,就是view和model有一个交互。。。必须解耦~!因为在一个大的比较良好的工程里面,很多view是应该能够被重用的而不应该是依赖某些特定的Model。如果把绘制的这部分代码写在viewcontroller里,又会造成一个很烦的问题,一旦数据有所更改,你就得去翻这个viewcontroller,尤其是protocol方法多了后简直是找的有点忧桑~!那么我们就应该对这个UITableView进行一个瘦身计划。

更轻量级的UITableView

首先我们发现一个tableview无非显示的就是一个数组里的数据,每个数组元素对应的是某个model的实例。那么这个datasource应该是可以抽出来的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/// DataSource 接口类
class ArrayDataSource: NSObject, UITableViewDataSource {
var items: [AnyObject] = []
var cellIdentifier = ""
var configBlock: (AnyObject, AnyObject) -> Void = {_,_ in }
override init() {
super.init()
}
func initWithItems(list: [AnyObject], cellIdentifier: String, configCell: (AnyObject, AnyObject) -> Void ) -> ArrayDataSource {
self.items = list
self.cellIdentifier = cellIdentifier
self.configBlock = configCell
return self
}
func itemAtIndexPath(indexPath: NSIndexPath) -> AnyObject {
return self.items[indexPath.row]
}
// MARK: DataSource delegate
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(self.cellIdentifier, forIndexPath: indexPath)
let item = self.itemAtIndexPath(indexPath)
self.configBlock(cell, item)
return cell
}
}

这个地方就把绘制的数量和cell的样式做了一个抽象。我们再看看具体的UITableView 的datasource的delegate实现的地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var list: [MoneyDataInfo] = []
var arrayDataSource: ArrayDataSource?
let cellStoryboardName = "xxTableViewCell"
/**
初始化tableView
*/
func setupTableView() -> Void {
self.tableView.delegate = self
self.tableView.tableFooterView = UIView()
self.tableView.mj_header = MJRefreshNormalHeader(refreshingTarget: self, refreshingAction: #selector(requestAccountRecordList))
self.tableView.mj_header.beginRefreshing()
let nib = UINib(nibName: cellStoryboardName, bundle: NSBundle.mainBundle())
self.tableView.registerNib(nib, forCellReuseIdentifier: cellStoryboardName)
// Adapter
self.setupAdapter()
}
/**
构建适配器
*/
func setupAdapter() {
// Adapter
self.arrayDataSource = ArrayDataSource()
self.arrayDataSource?.initWithItems(self.list, cellIdentifier: cellStoryboardName, configCell: { (cell: AnyObject, data: AnyObject) in
let tmpCell = cell as! MoneyInfoCellTableViewCell
let tmpData = data as! MoneyDataInfo
tmpCell.configForData(tmpData)
})
self.tableView.dataSource = self.arrayDataSource
}

这样子,只需要在数据发生变化的时候调用一下self.setupAdapter()这个方法即可。

那么就到了具体cell的绘制的地方。how to do it?
我们应该把model传入到cell里去然后逐步绘制么,可以这样子,但并不科学,我们应该好好利用分类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
extension MoneyInfoCellTableViewCell {
/**
根据数据填充
- parameter data: MoneyDataInfo
*/
func configForData(data: MoneyDataInfo) -> Void {
self.selectionStyle = .None
...
// 这个地方根据data来进行对应的绘制
}
}

为什么这么做,这样子文件写的更多了,但有明显的几个好处,这里的tableviewcell可以重复利用,不耦合具体的数据,当数据有修改的时候,只需要修改这个分类的这个config方法即可,无需再去把viewcontroller折腾一遍,代码更加的清晰,条理性更好!这里抛弃掉了一些大而空的东西,只着力于解决一些具体的事务。
无论什么所谓权威的东西都不要过分迷信和执着,都要去尝试它并自己去作出合理的判断。还是用那句话作结尾:
文章本天成,妙手偶得之
设计亦如此

参考文献:
objc.cn https://objccn.io/issue-1-1/

iOS工程第三方代码包管理

发表于 2016-09-11 |

iOS工程第三方代码包管理

在我们的实际开发过程中,多多少少会去引用别人成熟的代码库,自己完全造轮子这是不现实的。问题来了,怎么把这些第三方代码引入到自己的工程,以前咧,直接拖代码呗,恩~but这得多蛋疼呢~合适的管理代码包工具是很必要的。
现有的在github上的项目主要支持的工具就是Cocoapods,还有一个就是Carthage。Cocoapods的教程非常之多,各位自行面向百度编程,面向谷歌编程 or RTFM。

Carthage

Carthage的github主页为 https://github.com/Carthage/Carthage.git

CocoaPods会直接创建和修改项目的workspace配置,而Carthage的特点是灵活,耦合度不高,集成时不需要集成相应的project,不需要创建workspace,只要依赖打包好的framework文件就可以了。

安装使用Carthage

1
sudo brew install carthage

安装完成后,执行carthage version
即可看到carthage的版本

接下来就是通过carthage管理第三方库。
cd到你的项目位置,然后使用touch Carfile,创建一个Carfile文件。
使用Xcode打开Carfile,编辑你需要加入的第三方库,要看清楚该第三方库是否支持Carfile哦,一般比较正式的包都会支持的。
在文件中加入:

github “Alamofire/Alamofire” ~> 3.0

版本含义:

1
2
3
4
5
~> 3.0 表示使用版本3.0以上但是低于4.0的最新版本,如3.5, 3.9
>== 3.0 表示使用3.0版本
>= 3.0表示使用3.0或更高的版本

如果你没有指明版本号,则会自动使用最新的版本。
写好后保存文件,执行以下命令:

1
carthage update --platform iOS

carthage会为你下载和编译所需要的第三方库,当命令执行完毕,在你的项目文件夹中会创建一个名为Carthage的文件夹

在 ~/Carthage/Build/iOS里会出现xxx.framework文件已经为你创建好了。
下图就是构件好的framwork显示:

然后把framwork拖入到以下位置,并且构建一个run script,如图所示:

最终你就可以在代码中直接import Alamofire 使用了~~

深坑

这里说个深坑:情景是升级了下Xcode。恩,git 拉项目代码下来,非常nice,模拟器运行的很好,烧真机吧~shit~!闪退~!你会发现报错,framework链接问题,没错,你看到的是链接问题~!模拟器很好,真机很蛋疼。开始以为是Cocoapods的问题,后来换成了Carthage,最后各种排查,这些问题在Stack Overflow上有写但都不适用我的情况!看到有人说可能是证书配置,我在想没动过证书呀,进去钥匙串看一下,发现为什么之前所有的证书都报告来源于非正常的证书颁发机构,最终发现苹果的Apple Worldwide Developer Relations Certification Authority 的证书没有了,应该是更新Xcode的时候被干掉了。。。恶心啊~~!

最终下载Apple Worldwide Developer Relations Certification Authority证书,打包真机,非常nice~~!!世界终于清静,该睡觉了~!

iOS 使用MJRefresh增加UITableView的上拉刷新,加载更多数据

发表于 2016-09-01 |

iOS 使用MJRefresh增加UITableView的上拉刷新,加载更多数据

UITableView经常会加载很多很多数据,这样子我们就得做个类似分页的效果。MJRefresh能够很方便的让我们去实现这个效果。首先使用pod集成MJRefresh模块。

1
pod 'MJRefresh'

集成好后,直接贴代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import Foundation
import IBAnimatable
import MJRefresh
class TradeInfoViewController: AppViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet weak var tableView: UITableView!
private var totalNumber = 12
private let sectionNumber = 5
private let sectionSpace: CGFloat = 40
var data = [1, 2]
override func viewDidLoad() {
super.viewDidLoad()
self.setNavigationTitle(Text_TradeInfo)
self.setNavigationBarColor(BlueColor)
self.addBackItemBtn()
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.tableFooterView = UIView()
self.tableView.mj_footer = MJRefreshAutoNormalFooter(refreshingTarget: self, refreshingAction: #selector(loadMoreData))
self.tableView.mj_footer.backgroundColor = LightGrayColor
self.tableView.mj_footer.height = 44
}
/**
上拉刷新,加载更多数据
*/
func loadMoreData() {
DDLogError("--------------------loadMoreData")
// TODO::
let test_number = 7
if data.count >= test_number {
self.tableView.mj_footer.endRefreshingWithNoMoreData()
return
}
self.tableView.mj_footer.endRefreshing()
data.append(3)
data.append(4)
data.append(5)
self.tableView.reloadData()
}
// MARK: tableView delegate
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return data.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = NSBundle.mainBundle().loadNibNamed("TradeInfoTableViewCell", owner: self, options: nil).last as! TradeInfoTableViewCell
return cell
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 60
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "2016年8月"
}
// 设置头部间距
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return sectionSpace
}
}

其他地方都不谈,这个是project里截取出来的一段testcase,虽然看起来多,但重要的都在self.tableView.mj_footer的初始化 and loadMoreData这个函数。大家自行参照撸码~MJRefresh很多人吐槽,但我觉得还不错~暂时还没进大坑~~~

beego orm 操作(mysql)

发表于 2016-08-22 |

beego orm 操作(mysql)

继续对beego的orm操作的学习。
首先我们对之前的代码做一下改动,以下是main.go,其实逻辑不需要做任何更改,我只需要对注册数据库的地方做个小修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main
import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/orm"
_ "github.com/go-sql-driver/mysql"
_ "github.com/mattn/go-sqlite3"
_ "quickstart/routers"
)
var dbuser string = "root" //数据库用户名
var dbpassword string = "123456" //数据库密码
var db string = "ormtest" //数据库名字
//自动建表
func createTable() {
name := "default" //数据库别名
force := false //不强制建数据库
verbose := true //打印建表过程
err := orm.RunSyncdb(name, force, verbose) //建表
if err != nil {
beego.Error(err)
}
}
func init() {
// 注册sqlite3 Driver
// orm.RegisterDataBase("default", "sqlite3", "data.db")
//
//
// 注册mysql Driver
orm.RegisterDriver("mysql", orm.DRMySQL)
// 构造conn连接 用户名:密码@数据库地址+名称?字符集
conn := dbuser + ":" + dbpassword + "@/" + db + "?charset=utf8"
beego.Info(conn)
//注册数据库连接
orm.RegisterDataBase("default", "mysql", conn)
createTable()
}
func main() {
// orm.RegisterDriver("mysql", orm.DRMySQL)
// orm.RegisterDataBase("default", "mysql", "root:root@/orm_test?charset=utf8")
//
beego.Run()
}

从上面我们可以看到只需要对Driver进行替换即可~!换成对应的数据库驱动即可。
下面看下最终的几个主要执行效果:

创建db

插入数据

更新数据

url跟上次的是一样的~~!
最新完整的工程地址:
https://github.com/bartontang/beego_orm_test

beego orm 操作(sqlite)

发表于 2016-08-07 |

beego orm 操作(sqlite)

继续对beego的学习,之前对beego的安装和简单的http请求有了一个大致的了解,接下来就是对数据库的一些操作。

首先我们先定义一个models,在文件夹/models下定义models.go,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package models
import (
"fmt"
"github.com/astaxie/beego"
"github.com/astaxie/beego/orm"
)
type UserBT struct {
Id int64 `orm:"pk;auto"` //主键,自动增长
Name string
}
func NewUser(name string) (*UserBT, error) {
if name == "" {
beego.Error("user name is emptry")
return nil, fmt.Errorf("user name is emptry")
}
return &UserBT{Name: name}, nil
}
func init() {
orm.RegisterModel(new(UserBT))
}

这里定义了一个struct,里面有两个成员变量,一个是Id,自增长,还有个是名字Name。

好,现在我们得对main.go进行一下改造,需要在初始化的时候去注册db并创建db表,假如已经存在表了,我们需要不重复创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main
import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/orm"
_ "github.com/mattn/go-sqlite3"
_ "quickstart/routers"
)
//自动建表
func createTable() {
name := "default" //数据库别名
force := false //不强制建数据库
verbose := true //打印建表过程
err := orm.RunSyncdb(name, force, verbose) //建表
if err != nil {
beego.Error(err)
}
}
func init() {
orm.RegisterDataBase("default", "sqlite3", "data.db")
createTable()
}
func main() {
beego.Run()
}

这样子我们就创建了一个叫做data.db的sqlite数据库文件。
我们可以先run一下server并查看db是否创建成功,并多次启动server看看db是否是只创建一次,cd到$GOPATH/src/project目录下,运行bee run

我们可以看到这里将创建表的过程进行了打印,然后我们会在$GOPATH/src/project目录下看到一个data.db文件,nice,数据库文件创建好了!

接下来我们需要往db里头插入一些数据,更新一些数据,根据条件查询等操作,就是我们常说的增删查改。
我们先对Routue.go文件先做一下修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package routers
import (
"github.com/astaxie/beego"
"quickstart/controllers"
)
func init() {
beego.Router("/", &controllers.MainController{})
beego.Router("/test", &controllers.TestController{})
beego.Router("/test/:id:int", &controllers.TestController{}, "get:GetInfo")
beego.Router("/test/:id:int/:name:string", &controllers.TestController{}, "get:UpdateInfo")
beego.Router("/testmysql", &controllers.TestMySQLController{})
}

为了做个testcase,我们对之前的testcontroller.go文件做一下简单的修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package controllers
import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/orm"
"quickstart/models"
"strconv"
)
type TestController struct {
beego.Controller
}
/**
* Get请求
* @param 每次请求对数据库插入一条新的user
* @return {[type]} [description]
*/
func (c *TestController) Get() {
c.Data["json"] = map[string]interface{}{"rc": 1,
"msg": "success",
"data": false,
}
o := orm.NewOrm()
o.Using("default")
// 插入一条User数据而且名字都赋值为barton
user, err := models.NewUser("barton")
if err == nil {
beego.Debug(o.Insert(user))
}
c.ServeJSON()
}
/**
* 获取User数据
* @param id唯一标识符(自增长)
* @return {[type]} [description]
*/
func (c *TestController) GetInfo() {
id := c.Ctx.Input.Param(":id")
beego.Trace("check id = " + id)
o := orm.NewOrm()
user := new(models.UserBT)
user.Id, _ = strconv.ParseInt(id, 10, 64)
err := o.Read(user)
if err != nil {
beego.Error(err)
c.Data["json"] = map[string]interface{}{"rc": 1,
"msg": "false",
"data": "can not find id = " + id + "from table user",
}
} else {
c.Data["json"] = map[string]interface{}{"rc": 1,
"msg": "success",
"data": map[string]interface{}{
"userId": user.Id,
"userName": user.Name,
},
}
}
c.ServeJSON()
}
/**
* 更新User数据
* @param id唯一标识符(自增长),name 想更新的新的名字
* @return {[type]} [description]
*/
func (c *TestController) UpdateInfo() {
id := c.Ctx.Input.Param(":id")
newName := c.Ctx.Input.Param(":name")
beego.Trace("check id = " + id)
beego.Trace("update name = " + newName)
o := orm.NewOrm()
user := new(models.UserBT)
// 对id做int64的类型转换
user.Id, _ = strconv.ParseInt(id, 10, 64)
// 查找Id == id 的数据
err1 := o.Read(user)
// 查找出错并没找到对应的user数据
if err1 != nil {
beego.Error(err1)
c.Data["json"] = map[string]interface{}{"rc": 1,
"msg": "false",
"data": "can not find id = " + id + "from table user",
}
} else {
user.Name = newName
// 对查找的数据user进行name的更新
_, err2 := o.Update(user, "name")
if err2 != nil {
// 更新失败
beego.Error(err2)
c.Data["json"] = map[string]interface{}{"rc": 1,
"msg": "false",
"data": "update name id = " + id + "from table user",
}
} else {
// 更新成功
c.Data["json"] = map[string]interface{}{"rc": 1,
"msg": "true",
"data": map[string]interface{}{
"userId": user.Id,
"userName": user.Name,
},
}
}
}
c.ServeJSON()
}
func (c *TestController) Post() {
account := c.GetString("account")
password := c.GetString("password")
beego.Info(account)
c.Data["json"] = map[string]interface{}{"rc": 1,
"msg": "success",
"data": map[string]interface{}{
"account": account,
"password": password,
},
}
c.ServeJSON()
}

OK,代码的简单改造已经完成,现在让我们来看看效果吧!
请求localhost:8080/test 插入一条数据

看红色线条块,我们插入了一个Id=12的数据
请求localhost:8080/test/12 查询Id=12的数据

没有问题,那么假设查询的是个Id=999,就是db中木有的数据会发生什么呢?我们测试下,请求localhost:8080/test/999

我们从这可以看到err被捕获并通过相应的处理json进行了返回。

我们试试更新,请求localhost:8080/test/12/tang 更新Id=12的数据将name重新赋值为tang,并将重新获取Id=12的数据将数据用json返回

我们看到更新成功了~!
下面是完整的工程地址:
https://github.com/bartontang/beego_orm_test

1234
Barton

Barton

18 日志
3 标签
GitHub
© 2016 — 2018 Barton