实现自定义控制

本章将会实现对FoodTracker APP的评级控制,当你完成时,你的APP看起来像这样:编程

学习目标swift

在课程结束时,你将可以:数组

建立并关联自定义源代码文件和在storyboard中的元素
定义一个自定义类
在实现自定义类的初始化
使用的UIView做为容器
了解如何以编程方式显示views app

建立一个自定义Viewless

为了能评级一个菜谱,用户须要一个控制,让他们能选择给想要菜谱多少星星数量。有许多方法实现这个,但咱们会专一于涉及建立一个自定义view,用过在代码中定义,并使用storyboard。实现的效果以下:ide

 

评级控件将让用户为一个菜谱选择0-5个星星。当用户点击一个星星时,全部被填充的星星就是目前的星星数。填充的星星数量就是评级的数量,空心的星星就不是。为了开始设置这个UI,交互和控制行为,咱们要建立一个UIView的子类。函数

建立UIView的子类步骤以下工具

1.选择File>New>File(或Command+N)布局

2.在对话框的左边,选择iOS下方的Source学习

3.选择Cocoa Touch Class,而后点击下一步

4.在Class标签后,输入RatingControl

5.Subclass of标签后,选择UIView

6.肯定语言选择的是Swift

 

7.点击Next。

保存的位置是默认项目目录,Group选择默认的是你APP的名字,FoodTracker

在Targets字段,你的APP是被选择的,App的Test是未选择的

8.保留这些默认值,而后点击Create

Xcode会建立一个RatingControl类的文件:RatingControl.swift,RatingControl是一个自定义的UIView的子类

9.在RatingControl.swift中,删除注释,来到类中,就像一个白板同样

import UIKit
 
class RatingControl: UIView {
    
}

你一般建立一个view有两种方式,一种是经过View的初始化frame来手动添加View到你的UI中,另外一种是容许view,在storyboard中加载。每个方法对应一个初始程序:对于第一种,是使用init(frame:),第二种,使用init(coder:)。回想一下initializer方法,用于准备一个类的实例,它涉及到为每一个属性设置初始值并执行一些其余设置。

由于咱们这里会在storyboard中使用init方法,因此咱们覆盖子类init(coder:)的实现,

下面是覆盖初始化程序的步骤

1.在RatingControl.swift中的class下面,添加注释

// MARK: Initialization

2.在注释的下方,输入init。代码完成功能会出现

3.选择第二个方法,即init(coder:),而后按下Return

init(coder aDecoder: NSCoder!) {
}

4.你会发现有个错误,但能修复它,会自动导入required关键字

每一个实现了initializer的UIView的子类,必须包含一个init(coder:)的实现。Swift编译器知道这个,并提供自动修复工具,来改变你的代码,对于你代码中的错误,它提供一个潜在的解决方法

5.在init方法中,添加父类的初始化程序

super.init(coder: aDecoder)

最终 init(coder:)的方法,以下

required init(coder aDecoder: NSCoder!) {
    super.init(coder: aDecoder)
}

显示自定义View

为了显示你的自定义View,你须要添加一个View到你的UI中并在view的代码中创建一个链接。显示View的步骤以下:

1.打开你的storyboard

2.在storyboard中,使用Object library找到View对象,并拖动到storyboard场景中,它在image view的下方

3.选中View,打开Size inspector

 

4.在Intrinsic Size标签旁,选择Placeholder

5.在Intrinsic Size内输入44的Height 和240的Width,按下Return,而后界面以下:

6.在View选中的状况下,选择Identity inspector

7.在Identity inspector中,找到Class 标签,选择RatingControl

添加按钮到View中 

此刻,你获取到了自定义的UIView子类,名为RatingControl。接下来咱们须要为这个View添加按钮,来容许用户选择一个评级。先从简单的开始,获取一个红色的按钮显示在你的view中。步骤以下:

1.在init(coder:)内,添加如下代码来建立一个红色的按钮

let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
button.backgroundColor = UIColor.redColor()

你使用的是redColor(),因此按钮为红色。若是你喜欢,你能够改为其余颜色如blueColor() 或者greenColor()

2.接着添加下一行

addSubview(button)

addSubview()表示,把Button添加到你建立的RatingControl View中

你的 init(coder:)完整代码,应该以下所示:

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

检查站:执行你的APP,你应该能看到一个View中有一个红色的正方形。这个红色的正方形就是你添加的按钮

你须要这个按钮,最终还有其余按钮,在View中执行点击动做。这个动做你用来改变菜谱的评级。

下面让咱们添加一个动做到按钮中

1.在RatingControl.swift类中大括号}上方,添加以下代码:

 

// MARK: Button Action

 

2.在注释下方添加以下代码:

func ratingButtonTapped(button: UIButton) {
    print("Button pressed  ")
}

如今,咱们使用 print()函数来检查ratingButtonTapped动做是否如预期那样连接到按钮上。这个函数打印一个消息,会输出在Xcode的控制台。控制台是一个有效的调试机制。

3.找到init(coder:) initializer:

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

4.在 addSubview(button)上方,添加以下代码:

button.addTarget(self, action: "ratingButtonTapped:", forControlEvents: .TouchDown)

你已经熟悉目标 - 动做( target-action )模式了,由于你已经屡次用它在storyboard中连接代码和元素的动做方法。上面,咱们在作一样的事情,只不过你在代码中建立链接。你要附加的动做ratingButtonTapped:到button 对象,每当.TouchDown事件发生时将被触发。这个事件代表用户在按钮中已按下。设置目标self,在这种状况下是RatingControl类,由于的动做定义在这里。
须要注意的是,由于你没有使用界面生成器,你不须要定义你的操做方法为IBAction属性;你就像定义其余方法同样定义动做。

最终代码看起来以下:

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: "ratingButtonTapped:", forControlEvents: .TouchDown)
    addSubview(button)
}

检查站:运行你的应用程序。当你单击红色正方形,你应该在控制台中看到了“Button pressed”的消息。

是时候想一想为了展现评级,RatingControl类须要一些什么信息了。你须要保持并记录评级的值0-5,也就是用户点击来设置的评级按钮。你可使用Int来展现评级的值,而且这些按钮做为UIButton对象数组

添加评级属性 

1.RatingControl.swift中, 找到class声明的这行:

 

class RatingControl: UIView {

 

2.在这行代码下面,添加以下代码:

// MARK: Properties
 
var rating = 0
var ratingButtons = [UIButton]()

此时,你在View中有一个按钮,但你须要5个这样的按钮。为了建立一整个按钮集,咱们使用for-in循环。一个for-in循环遍历一个序列,如数字范围,屡次执行一组代码。如今咱们就用它来建立一个按钮,循环建立五个。

建立5个按钮

1.在RatingControl.swift中,找到init(coder:):

 

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: "ratingButtonTapped:", forControlEvents: .TouchDown)
    addSubview(button)
}

 

2.添加for-in循环代码

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: "ratingButtonTapped:", forControlEvents: .TouchDown)
    addSubview(button)
}

你能够全选他们,而后按下Control+I来缩进。(..<)这个Swift语法中的操做符,不会包含<后的数字,也就是说循环(0..<5)就是0,1,2,3,4。你可使用下划线(_)表示通配符,意思是你不须要知道当前执行的循环迭代。

3.在addSubview(button)上方添加以下代码:

 

ratingButtons += [button]

 

你建立的每一个按钮,须要把它存放在ratingButtons数组中,由于咱们将要用它它们。

完整的init(coder:)函数,代码以下:

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: "ratingButtonTapped:", forControlEvents: .TouchDown)
        ratingButtons += [button]
        addSubview(button)
    }
}

检查站:运行你的应用程序。你会注意到它看起来像有只有一个按钮。这是由于for-in循环只是堆叠在彼此顶部的按钮。你须要调整按钮的位置布局。

 

布局方法名为layoutSubviews的方法,这个方法在UIView类中已定义。layoutSubviews会由系统在合适的时候调用,给UIView的子类一个能够自行实现准确布局的地方。你须要在重写这个方法来把按钮放置在合适的地方

写布局按钮的代码

1.在RatingControl.swift中的init(coder:)函数下方,添加一个方法

override func layoutSubviews() {
}

 你可使用代码完成功能迅速添加方法

2.在方法中,添加以下代码

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
}

这个代码建立了一个框,使用for-in循环遍历全部的框(frame),enumerate()方法返回一个集合,包含ratingButtons数组中的元素和索引。这个集合包含一个元组,每一个元组包含一个索引和一个按钮。正好咱们须要用到索引来计算按钮的位置。你的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 enumerate(ratingButtons) {
        buttonFrame.origin.x = CGFloat(index * (44 + 5))
        button.frame = buttonFrame
    }
}

检查站:运行你的应用程序。如今,按钮应该是并排的了。点击任何按钮,能够在控制台收到信息。

 

声明一个常量表示按钮大小

注意咱们使用了44这个值在代码中,这通常来讲是很差的作好,咱们使用了硬编码。若是你想要一个稍微大点的按钮,你就必须在每一个44出现的地方去修改,这样很麻烦,相反咱们使用一个常量,来表示按钮的大小,这样其余地方引用这个常量,咱们要修改按钮大小的时候,只须要修改这个常量便可。如今咱们能够经过检索容器View的高度,来调整我按钮的大小。

声明一个常量为保存按钮的尺寸

1.在layoutSubviews()方法中,添加以下代码:

 

// Set the button's width and height to a square the size of the frame's height.
let buttonSize = Int(frame.size.height)

 

这使的布局更灵活

2.改变方法中,把44变成buttonSize:

var buttonFrame = CGRect(x: 0, y: 0, width: buttonSize, height: buttonSize)
 
// Offset each button's origin by the length of the button plus spacing.
for (index, button) in ratingButtons.enumerate() {
    buttonFrame.origin.x = CGFloat(index * (buttonSize + 5))
    button.frame = buttonFrame
}

3.在init(coder:)初始化函数中,改变循环中的第一行let button = UIButton()

 

let button = UIButton()

由于咱们layoutSubviews()方法中设置了按钮的frames ,因此这咱们你再也不须要在建立按钮时,进行设置尺寸。

你的layoutSubviews()方法如今看起来应该是这样:

override func layoutSubviews() {
    // Set the button's width and height to a square the size of the frame's height.
    let buttonSize = Int(frame.size.height)
    var buttonFrame = CGRect(x: 0, y: 0, width: buttonSize, height: buttonSize)
    
    // Offset each button's origin by the length of the button plus some spacing.
    for (index, button) in enumerate(ratingButtons) {
        buttonFrame.origin.x = CGFloat(index * (buttonSize + 5))
        button.frame = buttonFrame
    }
}

init(coder:)方法中,如今看起来应该是这样:

required init(coder aDecoder: NSCoder!) {
    super.init(coder: aDecoder)
    
    for _ in 0..<5 {
        let button = UIButton()
        button.backgroundColor = UIColor.redColor()
        button.addTarget(self, action: "ratingButtonTapped:", forControlEvents: .TouchDown)
        ratingButtons += [button]
        addSubview(button)
    }
}

检查点:运行你的应用程序。一切都应该和之前同样工做。按钮应该是并排的。点击任何按钮,能够在控制台收到信息。

添加Star图像到Buttons中

接下来咱们将添加星星到按钮中

 

上面图片,你能够直接右键下载

 

添加图片到项目中

1.打开项目导航,选择Images.xcassets ,这个目录,这一系列的操做,在上一章咱们已经作过了,你是否还记得呢?

2.在底部的左下角,点击(+)按钮,而后选择New Folder(上一章是直接选择New Image Set,由于咱们这里有两个星星图片,因此咱们使用文件夹,方便分类)

 

3.双击这个文件夹名字而后重命名为Rating Images

4.选择文件夹,点击(+)选择New Image Set。

5.双击image而后重命名为emptyStar

6.在电脑中,选择空的星星图片。

7.拖动到2x槽中

8.重复4-7步骤,把emptyStar换成filledStar,拖动填充的星星图片到2x槽中

最后你的asset目录应该以下所示:

下一步,咱们会在适当的时间,经过写代码来设置Button的图像

为按钮设置图片

1.打开RatingControl.swift

2.在init(coder:)中,循环以前,添加以下代码:

 

let filledStarImage = UIImage(named: "filledStar")
let emptyStarImage = UIImage(named: "emptyStar")

 

3.在循环中的最后一行添加以下代码:

button.setImage(emptyStarImage, forState: .Normal)
button.setImage(filledStarImage, forState: .Selected)
button.setImage(filledStarImage, forState: [.Highlighted, .Selected])

根据不一样的状态,你设置两个不一样的图像,因此你能够看到,当按钮被选中。当按钮处于未选中状态(Normal)空星星的图像出现 。当按钮处于选中状态(Selected状态),实星星的图像出现。当用户是在敲击按钮时,按钮突出显示(.Selected和.Highlighted状态)。

4.删除设置背景色为红色的,这行代码

button.backgroundColor = UIColor.redColor()

5.添加如下这行代码:

button.adjustsImageWhenHighlighted = false

这是为了确保图像不会在状态变化过程当中显示高亮。

如今的init(coder:)函数看起来应该以下:

required init(coder aDecoder: NSCoder!) {
    super.init(coder: aDecoder)
    
    let emptyStarImage = UIImage(named: "emptyStar")
    let filledStarImage = UIImage(named: "filledStar")
    
    for _ in 0..<5 {
        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: "ratingButtonTapped:", forControlEvents: .TouchDown)
        ratingButtons += [button]
        addSubview(button)
    }
}

检查站:运行你的应用程序。您应该看到星星,而不是红色的按钮。但你的按钮不改变图像呢。咱们下面会解决这个问题。

实现按钮的动做

用户须要可以经过点击星星来选择一个评级,因此咱们须要更换先前ratingButtonTapped()方法中的内容。

实现评级动做

1.RatingControl.swift中,找到ratingButtonTapped(_:)方法:

 

func ratingButtonTapped(button: UIButton) {
    print("Button pressed 👍")
}

 

2.替换print语句为下面的代码:

rating = ratingButtons.indexOf(button)! + 1

indexOf(_:)方法尝试找到按钮数组中已选择的按钮,而后返回这个按钮的索引。这个方法返回可选的Int,由于搜索的实例可能不存在于你的集合中。然而,由于仅仅只在会你建立并添加到数组中的一个按钮才会触发动做,你能搜索按钮并返回有效的索引。在这种状况下你能使用强制解包操做符(!)来访问潜在的索引值。你索引的值加1表示这个评级。由于数组下标是从0(0-4)开始的,咱们须要评级为1-5。

3.在RatingControl.swift中,(})前面,添加以下代码

func updateButtonSelectionStates() {
}

这个帮助方法,你将用于和更新按钮的选择状态

4.在updateButtonSelectionStates()方法内,添加以下for-in循环:

for (index, button) in enumerate(ratingButtons) {
    // If the index of a button is less than the rating, that button should be selected.
    button.selected = index < rating
}

这段代码经过按钮数组来遍历设置每个按钮的状态,根据数组中的索引是否小于评级来判断按钮是否选中。若是index < ratin的值为true,那么这个按钮的状态就是已选择的,同时使之显示为填充的星星图片。不然,其余的按钮为为选中状态,显示为空心的星星。若是你的Swift版本找不到indexOf方法,那么给你一个提示使用一个全局函数(寻找)能够解决这个问题

5.在ratingButtonTapped()方法中,添加一个调用方法为updateButtonSelectionStates(),在方法实现的最后一行

 

func ratingButtonTapped(button: UIButton) {
    rating = ratingButtons.indexOf(button)! + 1
    
    updateButtonSelectionStates()
}

6.在layoutSubviews方法()中,也添加updateButtonSelectionStates()方法,到方法实现的最后一行

override func layoutSubviews() {
    // Set the button's width and height to a square the size of the frame's height.
    let buttonSize = Int(frame.size.height)
    var buttonFrame = CGRect(x: 0, y: 0, width: buttonSize, height: buttonSize)
    
    // Offset each button's origin by the length of the button plus some spacing.
    for (index, button) in enumerate(ratingButtons) {
        buttonFrame.origin.x = CGFloat(index * (buttonSize + 5))
        button.frame = buttonFrame
    }
    updateButtonSelectionStates()
}

当载入view时,更新按钮的选择状态很重要,不仅是当评级改变时。

7.在 // MARK: Properties,找到rating属性

var rating = 0

8.更新rating属性,包含这个观察者:

var rating = 0 {
didSet {
    setNeedsLayout()
}
}

一个属性观察者,用来观察和响应属性值的变化。一个属性的值每一次被设置时,属性观察者会被调用,可用于值改变先后当即执行一些工做。didSet属性观察者是在属性值被设置后哦当即调用。在这里,咱们调用了setNeedsLayout()方法,表示每次评级改变时,出发一个布局更新。确保UI会一直准确的显示评级属性。

如今咱们的updateButtonSelectionStates()看起来应该是这样:

func updateButtonSelectionStates() {
    for (index, button) in enumerate(ratingButtons) {
        // If the index of a button is less than the rating, that button shouldn't be selected.
        button.selected = index < rating
    }
}

检查站:运行你的应用程序。您应该看到五颗星,并能点击一个改变评级。点击第三颗星的评级更改成3,如。

添加星星的间距和数量属性

确保你不会有任何的硬编码,建立评级星星的数量和间距属性。这样,若是你须要改变这些值,你只须要在一个地方修改

使评级控件的属性,来界面构造器中可查

1.在 RatingControl.swift中,找到 // MARK: Properties

// MARK: Properties
 
var rating = 0
var ratingButtons = [UIButton]()

2.在已存在的属性下方,添加以下代码:

var spacing = 5

这个属性是用于你按钮的间距

3.在layoutSubviews中,替换原来间距的常量,改成使用spacing属性:

buttonFrame.origin.x = CGFloat(index * (buttonSize + spacing))

4.在spacing属性的下方,添加另外一个属性:

var stars = 5

这个属性用来控制你显示星星的数量

5.在 init(coder:)中替换先前的代码:

for _ in 0..<stars {

检查点:运行你的应用程序。一切都应该看起来跟之前彻底同样。

链接Rating Control到View Controller

咱们最后须要作的事情就是为rating control,在ViewController中设置一个引用

链接到一个rating control outlet到ViewController.swift

1.打开你的storyboard

2.打开assistant editor

3.选择rating control

4.按住Control从画布中拖动rating control到代码中,而后在photoImageView下方,松开

5.在弹出的对话框中,Name字段旁输入ratingControl。其余不变,而后点击Connect

清理项目

你即将完成菜谱的场景UI,但首先你须要作一些清理工做。相比先前的章节,如今,FoodTracker应用程序实现更先进的特性和不一样的用户界面,你要删除你不须要的部分。你还可让元素居中,以平衡的UI元素。

清理UI

1.返回到standard editor

 

2.打开你的storyboard

3.选择 Set Default Label Text按钮,而后按下Delete键删除它。

 

4.选择Select View(若是你不是在Xcode7下,没有Stack View的,后面能够略过不看)

5.打开Attributes inspector

6.在Attributes inspector,找到Alignment字段,而后选择Center,stack view会水平居中

下面咱们须要移除按钮点击响应的动做方法

清理代码

1.打开ViewController.swift

2.在ViewController.swift中,删除setDefaultLabelText()方法

后面的章节咱们会修改mealNameLabel的outlet

检查站:运行你的应用程序。一切都应该像之前同样工做,但设置默认标签文本按钮消失了,而元素水平居中。点击任何按钮,依然会有消息显示在控制台。

相关文章
相关标签/搜索