网站开发深圳公司,做国外电影网站,网站续费贵是重新做个好还是续费,网站建设无广告本文译自#xff1a;How To Make a Custom Control in Swift 用户界面控件是所有应用程序重要的组成部分之一。它们以图形组件的方式呈现给用户#xff0c;用户可以通过它们与应用程序进行交互。苹果提供了一套控件#xff0c;例如 UITextField#xff0c;UIButton#xf… 本文译自How To Make a Custom Control in Swift 用户界面控件是所有应用程序重要的组成部分之一。它们以图形组件的方式呈现给用户用户可以通过它们与应用程序进行交互。苹果提供了一套控件例如 UITextFieldUIButtonUISwitch。通过工具箱中的这些已有控件我们可以创建各式各样的用户界面。 然而有时候你希望界面做得稍微的与众不同那么此时苹果提供的这些控件就无法满足你的需求。 自定义控件除了是自己构建二外与苹果提供的没什么差别。也就是说自定义控件不存在于 UIKit 框架。自定义控件跟苹果提供的标准控件一样应该是通用并且多功能的。你也会发现互联网上有一些积极的开发者乐意分享他们自定义的控件。 本文中你将实现一个自己的 RangeSlider 自定义控件。这个控件是一个两端都可以滑动的也就是说你可以通过该控件获得最小值和最大值。你将会接触到这样一些概念对现有控件的扩展设计和实现 自定义控件的 API甚至还能学到如何分享你的自定义控件到开发社区中。 注意本文截稿时我们还不会贴出关于 iOS 8 beta 版本的截图。所有文中涉及到的截图都是在iOS 8之前的版本中得到的不过结果非常类似。 目录 开始Images vs. CoreGraphics添加默认的控件属性添加交互逻辑添加触摸处理值改变的通知结合 Core Graphics 对控件进行修改处理控件属性的改变何去何从 开始 假设你在开发一个应用程序该程序提供搜索商品价格列表。通过这个假象的应用程序允许用户对搜索结果进行过滤以获得一定价格范围的商品。你可能会提供这样一个用户界面两个 UISlider 控件一个用于设置最低价格另外一个设置最高价格。然而这样的设计不能够让用户很好的感知价格的范围。要是能够提供一个 slider两端可以分别设置用于搜索的最高和最低的价格范围就更好了。 你可以通过创建一个 UIView 的子类然后为可视的价格范围定做一个 view。这对于应用程序内部来说是 ok的但是要想移植到别的程序中就需要花更多的精力了。 最好的办法是将构建一个新的尽可能通用的 UI 控件这样就能在任意的合适场合中重用。这也是自定义控件的本质。 启动 XcodeFile/New/Project选中 iOS/Application/Single View Application 模板然后点击 Next。在接下来的界面中输入 CustomSliderExample 当做工程名然后是 Organization Name 和 Organization Identifier然后一定要确保选中 Swift 语言iPhone 选中Use Core Data 不要选。 最后选择一个保存工程的地方并单击 Create。 首先我们需要做出决定的就是创建自定义控件需要继承自哪个类或者对哪个类进行扩展。 位了使自定义控件能够在应用程序中使用你的类必须是 UIView 的一个子类。 如果你注意观察苹果的 UIKit 参考会发现框架中的许多控件例如 UILabel 和 UIWebView 都是直接继承自 UIView 的。然而也有极少数例如 UIButton 和 UISwitch 是继承自 UIControl 的如下继承图所示 注意iOS 中 UI 组件的完整类继承图请看 UIKit Framework 参考。 UIControl 实现了 target-action 模式这是一种将变化通知订阅者的机制。UIControl 同样还有一些与控件状态相关的属性。在本文中的自定义空间中将使用到 target-action 模式所以从 UIControl 开始继承使用将是一个非常好的切入点。 在 Project Navigator 中右键单击 CustomSliderExample选择 New File…然后选择 iOS/Source/Cocoa Touch Class 模板并单击 Next。将类命名位 RangeSlider在 Subclass of 字段中输入 UIControl并确保语言是 Swift。然后单击 Next并在默认存储位置中 Create 出新的类。 虽然编码非常让人愉悦不过你可能也希望尽快看到自定义控件在屏幕中熏染出来的模样在写自定义控件相关的任何代码之前你应该先把这个控件添加到 view controller中这样就可以实时观察控件的演进程度。 打开 ViewController.swift用下面的内容替换之 1 import UIKit2 3 class ViewController: UIViewController {4 let rangeSlider RangeSlider(frame: CGRectZero)5 6 override func viewDidLoad() {7 super.viewDidLoad()8 9 rangeSlider.backgroundColor UIColor.redColor()
10 view.addSubview(rangeSlider)
11 }
12
13 override func viewDidLayoutSubviews() {
14 let margin: CGFloat 20.0
15 let width view.bounds.width - 2.0 * margin
16 rangeSlider.frame CGRect(x: margin, y: margin topLayoutGuide.length,
17 width: width, height: 31.0)
18 }
19 } 上面的代码根据指定的 frame 实例化了一个全新的控件然后将其添加到 view 中。为了在应用程序背景中凸显出控件我们将控件的背景色被设置位了红色。如果不把控件的背景色设置为红色那么控件中什么都没有可能会想控件去哪里了:] 编译并运行程序将看到如下类似界面 在开始给控件添加可视元素之前应该先定义几个属性用以在控件中记录下各种信息。这也是开始应用程序编程接口 (API) 的开始。 注意控件中定义的方法和属性是你决定用来暴露给别的开发者使用的。稍后你将看到 API 设计相关的内容现在只需要紧跟就行 添加默认的控件属性 打开 RangeSlider.swift用下面的代码替换之 1 import UIKit
2
3 class RangeSlider: UIControl {
4 var minimumValue 0.0
5 var maximumValue 1.0
6 var lowerValue 0.2
7 var upperValue 0.8
8 } 上面定义的四个属性用来描述控件的状态提供最大值和最小值以及有用户设置的 upper 和 lower 两个值。 好的控件设计应该提供一些默认的属性值否则将你的控件绘制到屏幕中时看起来会有点奇怪。 现在是时候开始做控件的交互元素了我们分别用两个 thumbs 表示高和低两个值并且让这两个 thumbs 能够滑动。 Images vs. CoreGraphics 在屏幕中渲染控件有两种方法 1、Images - 为控件构建不同的图片这些图片代表控件的各种元素。2、Core Graphics - 利用 layers 和 Core Graphics 组合起来熏染控件。 这两种方法都有利有弊下面来看看 Images - 利用图片来构建控件是最简单的一种方法 - 只要你知道如何绘制图片:] 如果你想要让开发者能够修改控件的外观那么你应该将这些图片以 UIImage 属性的方式暴露出去。 通过图片的方式来构建的控件给使用控件的人提供了非常大的灵活度。开发者可以改变每一个像素以及控件的详细外观不过这需要非常熟练的图形设计技能 - 并且通过代码非常难以对控件做出修改。 Core Graphics - 利用 Core Graphics 构建控件意味着你必须自己编写渲染控件的代码这就需要付出更多的代价。不过这种方法可以创建更加灵活的 API。 使用 Core Graphics可以把控件的所有特征都参数化例如颜色、边框厚度和弧度 - 几乎每一个可视元素都通过绘制完成这种方法运行开发者对控件做出任意调整以适配相应的需求。 本文中你将学到第二种技术 - 利用 Core Graphics 来熏染控件。 主要有趣的时苹果建议在他们提供的控件中使用图片。这可能是苹果知道每个控件的大小他们不希望程序中出现太多的定制。也就是说他们希望所有的应用程序都具有相似的外观和体验。 打开 RangeSlider.swift 将下面的 import 添加到文件的顶部也就是 import UIKit 下面 1 import QuartzCore 将下面的属性添加到 RangeSlider 中也就是我们刚刚定义的那行代码下面 1 let trackLayer CALayer()
2 let lowerThumbLayer CALayer()
3 let upperThumbLayer CALayer()
4
5 var thumbWidth: CGFloat {
6 return CGFloat(bounds.height)
7 } 这里有 3 个 layer - trackLayer, lowerThumbLayer, 和 upperThumbLayer - 用来熏染滑块控件的不同组件。thumbWidth 用来布局使用。 接下来就是控件默认的一些图形属性。 在 RangeSlider 类中添加一个 初始化方法以及一个 helper 方法 1 override init(frame: CGRect) {2 super.init(frame: frame)3 4 trackLayer.backgroundColor UIColor.blueColor().CGColor5 layer.addSublayer(trackLayer)6 7 lowerThumbLayer.backgroundColor UIColor.greenColor().CGColor8 layer.addSublayer(lowerThumbLayer)9
10 upperThumbLayer.backgroundColor UIColor.greenColor().CGColor
11 layer.addSublayer(upperThumbLayer)
12
13 updateLayerFrames()
14 }
15
16 required init(coder: NSCoder) {
17 super.init(coder: coder)
18 }
19
20 func updateLayerFrames() {
21 trackLayer.frame bounds.rectByInsetting(dx: 0.0, dy: bounds.height / 3)
22 trackLayer.setNeedsDisplay()
23
24 let lowerThumbCenter CGFloat(positionForValue(lowerValue))
25
26 lowerThumbLayer.frame CGRect(x: lowerThumbCenter - thumbWidth / 2.0, y: 0.0,
27 width: thumbWidth, height: thumbWidth)
28 lowerThumbLayer.setNeedsDisplay()
29
30 let upperThumbCenter CGFloat(positionForValue(upperValue))
31 upperThumbLayer.frame CGRect(x: upperThumbCenter - thumbWidth / 2.0, y: 0.0,
32 width: thumbWidth, height: thumbWidth)
33 upperThumbLayer.setNeedsDisplay()
34 }
35
36 func positionForValue(value: Double) - Double {
37 let widthDouble Double(thumbWidth)
38 return Double(bounds.width - thumbWidth) * (value - minimumValue) /
39 (maximumValue - minimumValue) Double(thumbWidth / 2.0)
40 } 初始化方法简单的创建了 3 个 layer并将它们以 children 的身份添加到控件的 root layer 中然后通过 updateLayerFrames 对这些 layer 的位置进行更新定位! :] 最后positionForValue 方法利用一个简单的比例对控件的最小和最大值的范围做了一个缩放将值映射到屏幕中确定的一个位置。 接下来override一下 frame通过将下面的代码添加到 RangeSlider.swift 中实现对属性的观察 1 override var frame: CGRect { 2 didSet { 3 updateLayerFrames() 4 } 5 } 当 frame 发生变化时属性观察者会更新 layer frame。这一步是必须的因为当控件初始化时传入的 frame 并不是最终的 frame就像 ViewController.swift 中的。 编译并运行程序可以看到滑块初具形状看起来如下图所示 还记得吗红色是整个控件的背景色。蓝色是滑块的轨迹绿色 thumb 是两个代表两端的值。 现在控件看起来有形状了不过几乎所有的控件都提供了相关方法让用户与之交互。 针对本文中的控件用户必须能够通过拖拽 2 个 thumb 来设置控件的范围。你将处理这些交互并通过控件更新 UI 和暴露的属性。 添加交互逻辑 本文的交互逻辑需要存储那个 thumb 被拖拽了并将效果反应到 UI 中。控件的 layer 是放置该逻辑的最佳位置。 跟之前一样在 Xcode 中创建一个新的 Cocoa Touch Class命名为 RangeSliderThumbLayer继承自 CALayer。 用下面的代码替换掉 RangeSliderThumbLayer.swift 文件中的内容 1 import UIKit
2 import QuartzCore
3
4 class RangeSliderThumbLayer: CALayer {
5 var highlighted false
6 weak var rangeSlider: RangeSlider?
7 } 上面的代码中简单的添加了两个属性一个表示这个 thumb 是否 高亮 (highlighted)另外一个引用回父 range slider。由于 RangeSlider 有两个 thumb layer所以将这里的引用设置位 weak避免循环引用。 打开 RangeSlider.swift修改一下 lowerThumbLayer 和 upperThumbLayer 两个属性的类型用下面的代码替换掉它们的定义 1 let lowerThumbLayer RangeSliderThumbLayer() 2 let upperThumbLayer RangeSliderThumbLayer() 还是在 RangeSlider.swift 中找到 init将下面的代码添加进去 1 lowerThumbLayer.rangeSlider self 2 upperThumbLayer.rangeSlider self 上面的代码简单的将 layer 的 rangeSlider 属性设置为 self。 编译并运行程序界面看起来没有什么变化。 现在你已经有了 slider 的thumb layer - RangeSliderThumbLayer然后需要给控件添加拖拽 thumb 的功能。 添加触摸处理 打开 RangeSlider.swift将下面这个属性添加进去 1 var previousLocation CGPoint() 这个属性用来跟踪记录用户的触摸位置。 那么你该如何来跟踪控件的各种触摸和 release 时间呢 UIControl 提供了一些方法来跟踪触摸。UIControl 的子类可以 override 这些方法以实现自己的交互逻辑。 在自定义控件中我们将 override 3 个 UIControl 关键的方法beginTrackingWithTouch, continueTrackingWithTouch 和 endTrackingWithTouch。 将下面的方法添加到 RangeSlider.swift 中 1 override func beginTrackingWithTouch(touch: UITouch!, withEvent event: UIEvent!) - Bool {2 previousLocation touch.locationInView(self)3 4 // Hit test the thumb layers5 if lowerThumbLayer.frame.contains(previousLocation) {6 lowerThumbLayer.highlighted true7 } else if upperThumbLayer.frame.contains(previousLocation) {8 upperThumbLayer.highlighted true9 }
10
11 return lowerThumbLayer.highlighted || upperThumbLayer.highlighted
12 } 当首次触摸控件时会调用上面的方法。 代码中首先将触摸事件的坐标转换到控件的坐标空间。然后检查每个 thumb是否触摸位置在其上面。方法中返回的值将决定 UIControl 是否继续跟踪触摸事件。 如果任意一个 thumb 被 highlighted 了就继续跟踪触摸事件。 现在有了初始的触摸事件我们需要处理用户在屏幕上移动的事件了。 将下面的方法添加到 RangeSlider.swift 中 1 func boundValue(value: Double, toLowerValue lowerValue: Double, upperValue: Double) - Double {2 return min(max(value, lowerValue), upperValue)3 }4 5 override func continueTrackingWithTouch(touch: UITouch!, withEvent event: UIEvent!) - Bool {6 let location touch.locationInView(self)7 8 // 1. Determine by how much the user has dragged9 let deltaLocation Double(location.x - previousLocation.x)
10 let deltaValue (maximumValue - minimumValue) * deltaLocation / Double(bounds.width - bounds.height)
11
12 previousLocation location
13
14 // 2. Update the values
15 if lowerThumbLayer.highlighted {
16 lowerValue deltaValue
17 lowerValue boundValue(lowerValue, toLowerValue: minimumValue, upperValue: upperValue)
18 } else if upperThumbLayer.highlighted {
19 upperValue deltaValue
20 upperValue boundValue(upperValue, toLowerValue: lowerValue, upperValue: maximumValue)
21 }
22
23 // 3. Update the UI
24 CATransaction.begin()
25 CATransaction.setDisableActions(true)
26
27 updateLayerFrames()
28
29 CATransaction.commit()
30
31 return true
32 } boundValue 会将传入的值控制在某个确定的范围。通过这个方法比嵌套调用 min/max 更容易理解。 下面我们根据注释来分析一下 continueTrackingWithTouch 方法都做了些什么 首先计算出位置增量这个值决定着用户手指移动的数值。然后根据控件的最大值和最小值对这个增量做转换。根据用户滑动滑块的距离修正一下 upper 或 lower 值。设置 CATransaction 中的 disabledActions。这样可以确保每个 layer 的frame 立即得到更新并且不会有动画效果。最后调用 updateLayerFrames 方法将 thumb 移动到正确的位置。至此已经编写了移动滑块的代码 - 不过我们还要处理触摸和拖拽事件的结束。 将下面方法添加到 RangeSlider.swift 中 1 override func endTrackingWithTouch(touch: UITouch!, withEvent event: UIEvent!) {
2 lowerThumbLayer.highlighted false
3 upperThumbLayer.highlighted false
4 } 上面的代码简单的将两个 thumb 还原位 non-highlighted 状态。 编译并运行程序尝试移动滑块现在你应该可以移动 thumb 了。 你可能注意到当在移动滑块时可以在控件之外的范围对其拖拽然后手指回到控件内也不会丢失跟踪。其实这在小屏幕的设备上是非常重要的一个功能。 值改变的通知 现在你已经有一个可以交互的控件了 - 用户可以对其进行操作以设置范围的大小值。但是如何才能把这些值的改变通知调用者控件有新的值了呢 这里有多种模式可以实现值改变的通知 NSNotificationKey-Value-Observing (KVO) delegate 模式target-action 模式等。有许多选择 面对这么多的通知方式那么我们该怎么选择呢 如果你研究过 UIKit 控件会发现它们并没有使用 NSNotification也不鼓励使用 KVO。所以为了保持与 UIKit 的一致性我们可以先排除这两种方法。另外的两种模式delegate 和 target-action 被广泛用于 UIKit 中。 Delegate 模式 - delegate 模式需要提供一个 protocol里面有一些用于通知的方法。控件中有一个属性一般命名位 delegate它可以是任意实现该协议的类。经典的一个示例就是 UITableView 提供了 UITableViewDelegate protocol。注意控件只接受单个 delegate 实例。一个 delegate 方法可以使用任意的参数所以可以给这样的方法传递尽可能多的信息。 Target-action 模式 - UIControl 基类已经提供了 target-action 模式。当控件状态发生了改变target 会获得相应 action 的通知该 action 是在 UIControlEvents 枚举值做定义的。我们可以给控件的 action 提供多个 target另外还可以创建自定义事件 (查阅 UIControlEventApplicationReserved)自定义事件的数量不得超过 4 个。控件 action 针对某个事件无法传送任意的信息所以当事件触发时不能用它来传递额外的信息。 这两种模式关键不同点如下 多播 (Multicast) - target-action 模式可以对改变事件进行多播通知而 delegate 模式只能绑定到单个 delegate 实例上。灵活 (Flexibility) - 在 delegate 模式中你可以定义自己的 protocol这就意味着你可以控制信息的传递量。而 target-action 是无法传递额外信息的客户端只能在收到事件后自行查询信息。我们的 slider 控件不会有大量的状态变化也不需要提供大量的通知。唯一真正改变的就是控件的 upper 和 lower 值。 基于这样的情况使用 target-action 模式是最好的。这也是为什么在本文开头的时候告诉你为什么这个控件要继承自 UIControl。 slider 的值是在 continueTrackingWithTouch:withEvent: 方法中进行更新的所以这个方法也是添加通知代码的地方。 打开 RangeSlider.swift定位到 continueTrackingWithTouch 方法然后将下面的代码添加到 return true 语句前面 1 sendActionsForControlEvents(.ValueChanged) 上面的这行代码就能将值改变事件通知给任意的订阅者 target。 现在我们应该对这个事件进行订阅并当事件来了以后作出相应的处理。 打开 ViewController.swift将下面这行代码添加到 viewDidLoad 尾部 1 rangeSlider.addTarget(self, action: rangeSliderValueChanged:, forControlEvents: .ValueChanged) 通过上面的代码每次 slider 发送 UIControlEventValueChanged action 时都会调用 rangeSliderValueChanged 方法。 将下面的代码添加到 ViewController.swift 中 1 func rangeSliderValueChanged(rangeSlider: RangeSlider) {
2 println(Range slider value changed: (\(rangeSlider.lowerValue) \(rangeSlider.upperValue)))
3 } 当 slider 值发生变化是上面这个方法简单的将 slider 的值打印出来。 编译并运行程序并移动一下 slider可以在控制台中看到控件的值如下所示 1 Range slider value changed: (0.217687089658687 0.68610299780487)
2 Range slider value changed: (0.217687089658687 0.677356642119739)
3 Range slider value changed: (0.217687089658687 0.661807535688662)
4 Range slider value changed: (0.217687089658687 0.64625847374385)
5 Range slider value changed: (0.217687089658687 0.631681214268632)
6 Range slider value changed: (0.217687089658687 0.621963056113908)
7 Range slider value changed: (0.217687089658687 0.619047604218864)
8 Range slider value changed: (0.217687089658687 0.61613215232382) 看到 控件五颜六色的你可能不高心它开起来就像水果沙拉一样 现在是时候给控件换换面目了 结合 Core Graphics 对控件进行修改 首先首选更新一下slider thumb 移动的轨迹图形。 跟之前一样给工程添加另外一个继承自 CALayer 的子类命名为 RangeSliderTrackLayer。 打开刚刚添加的文件 RangeSliderTrackLayer.swift然后用下面的内容替换之 1 import UIKit
2 import QuartzCore
3
4 class RangeSliderTrackLayer: CALayer {
5 weak var rangeSlider: RangeSlider?
6 } 上面的代码添加了一个到 slider 控件的引用跟之前 thumb layer 做的一样。 打开 RangeSlider.swift 文件找到 trackLayer 属性用刚刚创建的这个类对其实例化如下所示 1 let trackLayer RangeSliderTrackLayer() 接下来找到 init 并用下面的代码替换之 1 init(frame: CGRect) {2 super.init(frame: frame)3 4 trackLayer.rangeSlider self5 trackLayer.contentsScale UIScreen.mainScreen().scale6 layer.addSublayer(trackLayer)7 8 lowerThumbLayer.rangeSlider self9 lowerThumbLayer.contentsScale UIScreen.mainScreen().scale
10 layer.addSublayer(lowerThumbLayer)
11
12 upperThumbLayer.rangeSlider self
13 upperThumbLayer.contentsScale UIScreen.mainScreen().scale
14 layer.addSublayer(upperThumbLayer)
15 } 上面的代码确保新的 track layer 引用到 range slider - 并没有再用那可怕的颜色了然后将 contentsScale 因子设置位与设备的屏幕一样这样可以确保所有的内容在 retina 显示屏中没有问题。 下面还有一个事情需要做就是将 viewDidLoad 中的如下代码移除掉 1 rangeSlider.backgroundColor UIColor.redColor() 编译并运行程序看到什么了呢 什么东西都没有这是正确的 不要烦恼 - 我们只不过移除掉了在 layer 中花哨的测试颜色。控件依旧存在 - 只不过现在是白色的 由于许多开发者希望能够通过编码对控件做各种配置以使其外观能够效仿一些流行的程序所以我们给 slider 添加一些属性运行开发者对其外观做出一些定制。 打开 RangeSlider.swift将下面的属性添加到已有属性下面 1 var trackTintColor UIColor(white: 0.9, alpha: 1.0)
2 var trackHighlightTintColor UIColor(red: 0.0, green: 0.45, blue: 0.94, alpha: 1.0)
3 var thumbTintColor UIColor.whiteColor()
4
5 var curvaceousness : CGFloat 1.0 这些颜色属性的目的非常容易理解但是 curvaceousness这个属性在这里有点趣味 - 稍后你将发现其用途 接下来打来 RangeSliderTrackLayer.swift。 这个 layer 用来渲染两个 thumb 滑动的轨迹。目前它继承自 CALayer仅仅是绘制一个单一颜色。 为了绘制轨迹需要实现方法 drawInContext:并利用 Core Pgraphics APIs 来进行渲染。 注意要想深入学习 Core Graphics建议阅读 Core Graphics 101 教程。 将下面这个方法添加到 RangeSliderTrackLayer 中 1 override func drawInContext(ctx: CGContext!) {2 if let slider rangeSlider {3 // Clip4 let cornerRadius bounds.height * slider.curvaceousness / 2.05 let path UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)6 CGContextAddPath(ctx, path.CGPath)7 8 // Fill the track9 CGContextSetFillColorWithColor(ctx, slider.trackTintColor.CGColor)
10 CGContextAddPath(ctx, path.CGPath)
11 CGContextFillPath(ctx)
12
13 // Fill the highlighted range
14 CGContextSetFillColorWithColor(ctx, slider.trackHighlightTintColor.CGColor)
15 let lowerValuePosition CGFloat(slider.positionForValue(slider.lowerValue))
16 let upperValuePosition CGFloat(slider.positionForValue(slider.upperValue))
17 let rect CGRect(x: lowerValuePosition, y: 0.0, width: upperValuePosition - lowerValuePosition, height: bounds.height)
18 CGContextFillRect(ctx, rect)
19 }
20 } 一旦 track 形状确定控件的背景色就会被填充另外高亮范围也会被填充。 编译并运行程序会看到新的 track layer 被完美的渲染出来如下图所示 给暴露出来的属性设置不同的值观察一下它们是如何反应到控件渲染中的。 如果你对 curvaceousness 做什么的还存在疑惑那么试着修改一下它看看 接下来我们使用相同的方法来绘制 thumb layer。 打开 RangeSliderThumbLayer.swift然后将下面的方法添加到属性声明的下方 1 override func drawInContext(ctx: CGContext!) {2 if let slider rangeSlider {3 let thumbFrame bounds.rectByInsetting(dx: 2.0, dy: 2.0)4 let cornerRadius thumbFrame.height * slider.curvaceousness / 2.05 let thumbPath UIBezierPath(roundedRect: thumbFrame, cornerRadius: cornerRadius)6 7 // Fill - with a subtle shadow8 let shadowColor UIColor.grayColor()9 CGContextSetShadowWithColor(ctx, CGSize(width: 0.0, height: 1.0), 1.0, shadowColor.CGColor)
10 CGContextSetFillColorWithColor(ctx, slider.thumbTintColor.CGColor)
11 CGContextAddPath(ctx, thumbPath.CGPath)
12 CGContextFillPath(ctx)
13
14 // Outline
15 CGContextSetStrokeColorWithColor(ctx, shadowColor.CGColor)
16 CGContextSetLineWidth(ctx, 0.5)
17 CGContextAddPath(ctx, thumbPath.CGPath)
18 CGContextStrokePath(ctx)
19
20 if highlighted {
21 CGContextSetFillColorWithColor(ctx, UIColor(white: 0.0, alpha: 0.1).CGColor)
22 CGContextAddPath(ctx, thumbPath.CGPath)
23 CGContextFillPath(ctx)
24 }
25 }
26 } 一旦定义好了 thumb 的形状路径就会将其形状填充好。注意绘制微弱的阴影看起来的效果就是 thumb 上方的轨迹。接下来是绘制边框。最后如果 thumb 是高亮的 - 也就是被移动状态 - 那么就绘制微弱的灰色阴影效果。 在运行之前还有最后一件事情要做。按照下面的代码对 highlighted 属性的定义做出修改 1 var highlighted: Bool false { 2 didSet { 3 setNeedsDisplay() 4 } 5 } 这里定义了一个属性观察者这样当每次 highlighted 属性修改时相应的 layer 都会得到重绘。这会使得触摸事件发生时填充色发生轻微的变动。 再次编译并运行程序这下看起来会非常的有形状如下图所示 不难发现用 Core Graphics 来绘制控件是非常值得做的。使用 Core Graphics 可以做出比通过图片渲染方法更通用的控件。 处理控件属性的改变 那么到现在还有什么事情要做呢控件现在看起来已经非常的华丽了它的外观是通用的并且也支持 target-action 通知。 貌似已经做完了 思考一下如果当控件熏染之后如果通过代码对 slider 的属性做了修改会发生什么例如你希望修改一下 slider 的默认值或者修改一下 track highlight表示出一个有效范围。 目前还没有任何代码来观察属性的设置情况。我们需要将其添加到控件中。我们需要实现属性观察者来更新控件的 frame 或者重绘控件。打开 RangeSlider.swift按照下面的代码对属性的声明作出修改 1 var minimumValue: Double 0.0 {2 didSet {3 updateLayerFrames()4 }5 }6 7 var maximumValue: Double 1.0 {8 didSet {9 updateLayerFrames()
10 }
11 }
12
13 var lowerValue: Double 0.2 {
14 didSet {
15 updateLayerFrames()
16 }
17 }
18
19 var upperValue: Double 0.8 {
20 didSet {
21 updateLayerFrames()
22 }
23 }
24
25 var trackTintColor: UIColor UIColor(white: 0.9, alpha: 1.0) {
26 didSet {
27 trackLayer.setNeedsDisplay()
28 }
29 }
30
31 var trackHighlightTintColor: UIColor UIColor(red: 0.0, green: 0.45, blue: 0.94, alpha: 1.0) {
32 didSet {
33 trackLayer.setNeedsDisplay()
34 }
35 }
36
37 var thumbTintColor: UIColor UIColor.whiteColor() {
38 didSet {
39 lowerThumbLayer.setNeedsDisplay()
40 upperThumbLayer.setNeedsDisplay()
41 }
42 }
43
44 var curvaceousness: CGFloat 1.0 {
45 didSet {
46 trackLayer.setNeedsDisplay()
47 lowerThumbLayer.setNeedsDisplay()
48 upperThumbLayer.setNeedsDisplay()
49 }
50 } 一般情况我们需要根据依赖的属性调用 setNeedsDisplay 方法将对于的 layer 进行重新处理。setLayerFrames 方法会对控件的布局作出调整。 现在找到 updateLayerFrames然后将下面的代码添加到该方法的顶部 1 CATransaction.begin() 2 CATransaction.setDisableActions(true) 并将下面的代码添加到方法的尾部 1 CATransaction.commit() 上面的代码将整个 frame 的更新封装到一个事物处理中这样可以让界面重绘变得流畅。同样还明确的把 layer 中的动画禁用掉跟之前一样这样 layer frame 的更新会变得即时。 由于现在每当 upper 和 lower 值发生变动时 frame 会自动更新了所以找到 continueTrackingWithTouch 方法并将下面的代码删除掉 1 // 3. Update the UI
2 CATransaction.begin()
3 CATransaction.setDisableActions(true)
4
5 updateLayerFrames()
6
7 CATransaction.commit() 上面的这些代码就能够确保属性变化时能够反应到 slider 控件中。 为了确保代码无误我们需要写点测试 case 进行测试。 打开 ViewController.swift并将下面代码添加到 viewDidLoad: 尾部 1 let time dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_SEC))
2 dispatch_after(time, dispatch_get_main_queue()) {
3 self.rangeSlider.trackHighlightTintColor UIColor.redColor()
4 self.rangeSlider.curvaceousness 0.0
5 } 上面的代码会在暂停 1 秒钟之后对控件的一些属性做出更新。其中将 track highlight 的颜色修改为红色并修改了 slider 和 thumb 的形状。 编译并运行程序一秒钟之后你看到 slider 由 变为 很容易不是吗 上面刚刚添加到 view controller 中的代码演示了一个非常有趣而又经常被忽略的内容 - 对开发的自定义控件做充分的测试。当你在开发一个自定义控件时你需要负责对所有的属性和外观做出验证。这里有一个好的方法就是创建不同的按钮和滑块 (它们连接到控件的不同属性) 对控件做出测试。这样你就可以实时修改控件的属性并实时观察到它们的结果。 何去何从 现在我们的 range slider 控件已经完成开发并可以在程序中使用了你可以在这里下载到完整的工程方便的话给个小小的star...。 不过创建通用性自定义控件的一个关键好处就是你可以将其用于不同的工程 - 并且分享给别的开发者使用。 准备好了吗 实际上还没有。在分享自定义控件之前还有一些事情需要考虑 希望通过本文的学习你已经能愉悦的创建 slider 控件了可能你还希望构建自己的自定义控件。如果你做了可以在本文的评论中分享一下 - 我们非常想看到你的创作(分享了) 本文转载自(破船之家) 转载于:https://www.cnblogs.com/chenyihang/p/5640079.html