『FoodTracker』(三)
今天写的文章是说明如何增加一个用来评分的 button ,button的外观是五颗星星,评几分就点第几颗星星就行。
代码是前几天写的,今天才写了博客。开始吧。
一个新的.swift 文件来控制评分部分
新建一个 .swift文件,起名为 RatingControl.swift Subclass of
创建视图有两种主要方法,一种是通过代码写初始化一个视图,另一种就是通过Storyboard
,这次试用第一种方法:
在代码中做个注释:
然后写代码,like this:
init?(coder aDecoder: NSCoder) {
}
这时候Xcode会告诉你有个错误,直接点击 error 而后 fix-it
之后代码变成了这样:
required init?(coder aDecoder: NSCoder) {
}
然后再加一行代码,让你的代码看上去像是这样:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
显示自定义视图
打开 Storyboard ,拖一个 view 对象到 stack 中,放在 image view 下面
然后选中 view 对象的尺寸检查器(Size inspector),在尺寸检查器最下面的地方,选择Placeholder
,其中高度填写 44,宽度填写 240
接下来,选择 view 对象的身份检查器(Identity inspector),将其第一行的 Class 选为RatingControl
给视图中增加 Button
创建一个 button
在初始化函数(init?(coder:)
)中,写代码:
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
button.backgroundColor = UIColor.blueColor()
第一行定义了 button 并经行了初始化,这里是一个正方形,我们是要用星星评分,所以这个代码等会会改
第二行是为了让 button 显示个颜色,方便观察,这行代码等会要删掉
然后再加一行代码,用来把创建的按钮加到视图中去
addSubview(button)
现在你的代码看上去应该是这样的:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
button.backgroundColor = UIColor.redColor()
addSubview(button)
}
为了告诉堆栈视图(stack view)怎么去布置 button ,我们要提供给他一个button需要占据的尺寸,代码这样写:
override func intrinsicContentSize() -> CGSize {
return CGSize(width: 240, height: 44)
}
现在可以运行一下你的程序了,正确的样子应该是图片视图下面出现了一个 蓝色的正方形
现在,给 button 加 action
给 button 加上一个动作很简单,我们举个例子:当我们点击 button 时,就会输出『 Button pressed 👍』
做法很简单:
func ratingButtonTapped(button: UIButton) {
print("Button pressed 👍")
动作写好了,现在让 button 知道自己有这么一个动作要执行
button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)), forControlEvents: .TouchDown) //第二个参数是要采用哪个action方法,第三个参数是针对哪种事件,".TouchDown"指的是『按下去』这个事件
addTarget()
这个方法的作用是将一个操作对象和方法与控件相连接。
现在你的代码是这样的:
//MARK :Initialization
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
button.backgroundColor = UIColor.redColor()
button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)), forControlEvents: .TouchDown)
addSubview(button)
}
override func intrinsicContentSize() -> CGSize {
return CGSize(width: 240, height: 44)
}
//// MARK: Button Action
func ratingButtonTapped(button: UIButton) {
print("Button pressed 👍")
}
现在运行你的程序,点击蓝色小方块,你就可以从 Xcode 的控制台看到 『Button pressed 👍』,点多少下就显示多少行哦
添加评分属性
开始写一些属性啦:
// MARK: Properties
var rating = 0
var ratingButtons = [UIButton]()
到此为止我们有了一个button ,但是我们要做一个五分制的评分系统,所以我们要多弄几个 button,这个很容易,加个 for-in
循环就行:
你之前的代码是这样的:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
button.backgroundColor = UIColor.redColor()
button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)), forControlEvents: .TouchDown)
addSubview(button)
}
现在增加一个循环:
for _ in 0..<5 {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
button.backgroundColor = UIColor.redColor()
button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)), forControlEvents: .TouchDown)
addSubview(button)
}
然后在 addSubview(button)
上面加一行
这一行是用来保证你在新加了 button 后能保证你的分数和 button 数能对得上
现在你的 init
看上去是这样的:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
for _ in 0..<5 {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
button.backgroundColor = UIColor.redColor()
button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)), forControlEvents: .TouchDown)
ratingButtons += [button]
addSubview(button)
}
}
按钮的布局
在 RatingControl.swift 文件里, init?(coder:)
方法下面,加一行注释MARK: Initialization
并写下代码:
override func layoutSubviews() {
}
在这个方法中,加上几行代码
var button Frame = CGRect(x:0 ,y:0, width:44 , height: 44)
//设置每一个评分星星的起点
for(index, button) in ratingButtons.enumerate() {
buttonFrame.origin.x = CGFloat(index * (44 + 5))
button.frame = buttonFrame
}
现在,layoutSubviews() 方法看上去是这样的:
override func layoutSubviews() {
var buttonFrame = CGRect(x: 0, y: 0, width: 44, height: 44)
// Offset each button's origin by the length of the button plus spacing.
for (index, button) in ratingButtons.enumerate() {
buttonFrame.origin.x = CGFloat(index * (44 + 5))
button.frame = buttonFrame
}
}
现在可以运行一下你的程序了,一切顺利的话,你会看到有五个蓝色正方形
给星星按钮间距和星星按钮的数量加上属性
注意:如果你在星星的个数和星星间的距离使用了『5』这个值,这就有点糟糕了,要知道在代码中分散的写死某一个值并不是个好主意,以后要是想改的话,会非常难过。
所以呢,我们应该声明两个值:星星的数量和星星间的距离。
增加属性
在 RatingControl.swift 文件里,class RatingControl(){}
中,加上下面的代码
// MARK: Properties
var rating = 0
var ratingButtons = [UIButton]()
在上面的属性后面再加上:
这样就固定了星星之间的距离
在 layoutSubViews
中,把你之前写死的那些值都换掉
之前是: buttonFrame.origin.x = CGFloat(index * (44 + 5))
现在换成:buttonFrame.origin.x = CGFloat(index * (44 + spacing))
当然,别忘了把星星数量的值也用变量替换一下:
在写属性的地方加上一行代码:let starCount = 5
然后修改 init?{} 方法中的代码
之前是:for _ in 0..<5 {
现在换成:for _ in 0 ..< starCount {
现在运行一下程序,一切都和之前看上去一样
声明一个常量用于表示星星形状按钮的尺寸
你应该还记得星星形状按钮的尺寸是 44 吧,这是一个写死的值,现在需要改变这个糟糕的情况。
在layoutSubviews()
方法中,加一行代码:
// 用frame 的高度去设置正方形按钮的边长
let buttonSize = Int(frame.size.height)
这样一来,按钮的尺寸就更加灵活了
现在把代码中的 "44"替换成 buttonSize
替换后应该是这样的:
var buttonFrame = CGRect(x: 0, y: 0, width: buttonSize, height: buttonSize)
//从第二个开始,每个都往后挪"spacing"的长度
for (index, button) in ratingButtons.enumerate() {
buttonFrame.origin.x = CGFloat(index * (buttonSize + spacing))
button.frame = buttonFrame
}
现在在intrinsicContentSize()
方法中设置一下评分按钮整体在 stack view 中占据的尺寸
let buttonSize = Int(frame.size.height)
let width = (buttonSize * starCount) + (spacing * (starCount - 1))
return CGRact(width:width, height:buttonSize)
接下来把 init?{}方法中的第一行代码改成下面的样子:
因为你已经在layoutSubviews() {}
方法中设置好 button 的尺寸了。
现在你的 layoutSubviews() {}
方法看上去是这个样子的:
override func layoutSubviews() {
let buttonSize = Int(frame.size.height)
var buttonFrame = CGRect(x: 0, y: 0, width: buttonSize, height: buttonSize)
//从第二个开始,每个都往后挪 5
for (index, button) in ratingButtons.enumerate() {
buttonFrame.origin.x = CGFloat(index * (buttonSize + spacing))
button.frame = buttonFrame
}
updateButtonsSelectionStates()
}
你的 intrinsicContentSize() ->CGSize{}
方法看上去是这样的
override func intrinsicContentSize() -> CGSize {
let buttonSize = Int(frame.size.height)
let width = buttonSize * stars + spacing * (starCount - 1)
return CGSize(width: width, height: buttonSize)
}
你的 init?(){} 函数看上去是这样的:
//MARK :Initialization
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
for _ in 0 ..< starCount{
let button = UIButton()
button.backgroudColor = UIColor.blueColor()
button.adjustsImageWhenHighlighted = false //用来确保不会有一个意外的高亮
button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)), forControlEvents: .TouchDown) //第二个参数是action是什么效果,第三个参数是针对哪种事件, .TouchDown 指的是『按下去』这个事件
ratingButtons += [button]
addSubview(button) //将我创建的「button」添加到屏幕上
}
}
把 Button 现在的方块外观换成星星
在这,你需要两个星星的图标,一个空心的,一个实心的,自己画一个或者去下载吧,记得两种星星要能重合。
把图片加入到你的项目里
获得这两个星星的图片后,把它们放到 Assets.xcassets 中去,并分别起名为:emptyStar 和 filledStar
并且把他们拖到 "2x"这个地方来,这个大小比较适合 iphone 5s 和 6s 的尺寸。
为了看上去更整齐,在Assets.xcassets的列表中,右击,选择 New Floder ,并且给这个文件夹命名为 "Rating image",然后把两个星星图标都拖进去。
把星星图标用于 button
在 RatingControl.swift
文件的 init?(coder:){}
方法中,加入两行代码:
let filledStarImage = UIImage(named: "filledStar")
let emptyStarImage = UIImage(named: "emptyStar")
然后加 for-in 循环中加入下列代码:
button.setImage(emptyStarImage, forState: .Normal)
button.setImage(filledStarImage, forState: .Selected)
button.setImage(filledStarImage, forState: [.Highlighted, .Selected])
并删掉下面这行之前我们用来给按钮赋予颜色的代码:
然后增加一行代码确保不会出现意料之外的高亮效果:
现在,init?(coder:){}
方法是这个样子的:
//MARK :Initialization
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let filledStarImage = UIImage(named: "filledStar")
let emptyStarImage = UIImage(named: "emptyStar")
for _ in 0 ..< stars {
let button = UIButton()
button.setImage(emptyStarImage, forState: .Normal)
button.setImage(filledStarImage, forState: .Selected)
button.setImage(filledStarImage, forState: [.Highlighted, .Selected])
button.adjustsImageWhenHighlighted = false //用来确保不会有一个意外的高亮
button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)), forControlEvents: .TouchDown) //第二个参数是action是什么效果,第三个参数是针对哪种事件, .TouchDown 指的是『按下去』这个事件
ratingButtons += [button]
addSubview(button) //将我创建的「button」添加到屏幕上
}
}
执行 Button action 的动作
在RatingControl.swift
, 找到 ratingButtonTapped(_:)
方法:
func ratingButtonTapped(button: UIButton) {
print("Button pressed 👍")
}
然后把 print("Button pressed 👍")
这行代码换成下列代码:
在updateButtonSelectionStates()
方法中加一个 for-in
循环
for (index, button) in ratingButtons.enumerate() {
button.selected = index < rating //按钮值小于额定值时,选择按钮的值
}
在ratingButtonTapped(_:)
方法中的rating = ratingButtons.indexOf(button)! + 1
下面,加一行代码
func ratingButtonTapped(button:UIButton) {
rating = ratingButtons.indexOf(button)! + 1
updateButtonsSelectionStates()
}
在 layoutSubviews()
方法中的最后, 加上 to updateButtonSelectionStates()
override func layoutSubviews() {
let buttonSize = Int(frame.size.height)
var buttonFrame = CGRect(x: 0, y: 0, width: buttonSize, height: buttonSize)
//从第二个开始,每个都往后挪 5
for (index, button) in ratingButtons.enumerate() {
buttonFrame.origin.x = CGFloat(index * (buttonSize + spacing))
button.frame = buttonFrame
}
updateButtonsSelectionStates()
}
在 // MARK: Properties
部分,找到
把它改成:
var rating = 0 {
didSet {
setNeedsLayout()
}
}
现在你的updateButtonSelectionStates()
方法看上去是这样的:
func updateButtonSelectionStates() {
for (index, button) in ratingButtons.enumerate() {
button.selected = index < rating//按钮值小于额定值时,选择按钮的值
}
}
把代码和视图联系起来
把 Storyboard 中的按钮部分 通过 control-drag 连接到 ViewController.swift 名字定位 "ratingControl" 类型为"RatingControl"
调整
现在可以自由的把你的视图调整一下了,喜欢居中就把 imageView 和 button Label 都在居中一下
在 ViewController.swift文件中, 删了setDefaultLabelText(_:)
方法
@IBAction func setDefaultLabelText(sender: UIButton) {
mealNameLabel.text = "Default Text"
}
本部分结束,现在可以运行一下程序啦!
这个程序可以从这个链接下载到