『FoodTracker』(三)

今天写的文章是说明如何增加一个用来评分的 button ,button的外观是五颗星星,评几分就点第几颗星星就行。
代码是前几天写的,今天才写了博客。开始吧。

一个新的.swift 文件来控制评分部分

新建一个 .swift文件,起名为 RatingControl.swift Subclass of
UIView
创建视图有两种主要方法,一种是通过代码写初始化一个视图,另一种就是通过Storyboard,这次试用第一种方法:
在代码中做个注释:
// MARK: Initialization
然后写代码,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) 上面加一行
ratingButtons +=
这一行是用来保证你在新加了 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]()

在上面的属性后面再加上:
let spacing = 5
这样就固定了星星之间的距离
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?{}方法中的第一行代码改成下面的样子:
let button = UIButton()
因为你已经在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])

并删掉下面这行之前我们用来给按钮赋予颜色的代码:
button.backgroundColor = UIColor.blueColor()
然后增加一行代码确保不会出现意料之外的高亮效果:
button.adjustsImageWhenHighlighted = false
现在,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 👍")这行代码换成下列代码:
rating = ratingButtons.indexOf(button)! + 1

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
把它改成:

 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"
}

本部分结束,现在可以运行一下程序啦!
这个程序可以从这个链接下载到

Comments
Write a Comment