版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2018.11.19 星期一 |
前言
iOS中有關(guān)視圖控件用戶能看到的都在UIKit框架里面,用戶交互也是通過UIKit進(jìn)行的羔味。感興趣的參考上面幾篇文章燕垃。
1. UIKit框架(一) —— UIKit動(dòng)力學(xué)和移動(dòng)效果(一)
2. UIKit框架(二) —— UIKit動(dòng)力學(xué)和移動(dòng)效果(二)
3. UIKit框架(三) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(一)
4. UIKit框架(四) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(二)
開始
首先看一下寫作環(huán)境
Swift 4.2, iOS 12, Xcode 10
用戶界面控件是任何應(yīng)用程序中最重要的構(gòu)建塊之一。它們充當(dāng)圖形組件,允許用戶查看應(yīng)用程序并與之交互卵牍。 Apple提供了一組控件,例如UITextField
沦泌,UIButton
和UISwitch
糊昙。有了這個(gè)預(yù)先存在的控件工具箱,您可以創(chuàng)建各種各樣的用戶界面谢谦。
iOS自定義控件只不過是您自己創(chuàng)建的控件释牺。自定義控件,如標(biāo)準(zhǔn)控件回挽,應(yīng)該是通用的没咙。你會(huì)發(fā)現(xiàn)有一個(gè)活躍且充滿活力的開發(fā)者社區(qū),他們喜歡分享他們的iOS自定義控件創(chuàng)作千劈,前提是他們都是這些東西祭刚。
在本教程中,您將實(shí)現(xiàn)RangeSlider
iOS自定義控件墙牌。此控件類似于雙端滑塊涡驮,可讓您選擇最小值和最大值。您將涉及擴(kuò)展現(xiàn)有控件喜滨,設(shè)計(jì)和實(shí)現(xiàn)控件API以及如何與開發(fā)社區(qū)共享新控件等概念捉捅。
是時(shí)候開始定制了!
假設(shè)您正在開發(fā)一個(gè)搜索待售物業(yè)列表的應(yīng)用程序虽风。 這個(gè)虛構(gòu)的應(yīng)用程序允許用戶過濾搜索結(jié)果棒口,使其落在特定的價(jià)格范圍內(nèi)。
您可以提供一個(gè)界面焰情,向用戶顯示一對(duì)UISlider
控件陌凳,一個(gè)用于設(shè)置最高價(jià)格剥懒,另一個(gè)用于設(shè)置最低價(jià)格内舟。 但是,此界面無法幫助用戶查看價(jià)格范圍初橘。 使用兩個(gè)指示器呈現(xiàn)單個(gè)滑塊以指示其搜索條件的高價(jià)和低價(jià)范圍會(huì)更好验游。
您可以通過繼承UIView
并創(chuàng)建一個(gè)定制視圖來可視化價(jià)格范圍來構(gòu)建此范圍滑塊。 對(duì)于您的應(yīng)用程序的上下文來說這沒什么問題 - 但將它移植到其他應(yīng)用程序會(huì)很困難保檐。
將這個(gè)新組件盡可能地設(shè)為通用是一個(gè)更好的主意耕蝉,以便您以后可以重用它。 這是自定義控件的本質(zhì)夜只。
創(chuàng)建iOS自定義控件時(shí)需要做出的第一個(gè)決定是將現(xiàn)有類子類化或擴(kuò)展以制作新控件垒在。
您的類必須是UIView
子類,才能在應(yīng)用程序的UI中使用它扔亥。
如果你檢查Apple的UIKit引用场躯,你會(huì)看到許多框架控件谈为,比如UILabel
和UIWebView
,直接子類UIView
踢关。 但是伞鲫,有一些,如UIButton
和UISwitch
签舞,它們是UIControl
的子類秕脓,如下面的層次結(jié)構(gòu)所示:
注意:有關(guān)UI組件的完整類層次結(jié)構(gòu),請(qǐng)查看 UIKit Framework Reference儒搭。
在本教程中吠架,您將繼承UIControl
的子類。
在Xcode中打開啟動(dòng)項(xiàng)目搂鲫。 您將滑塊控制代碼放在RangeSlider.swift
中诵肛。 但是,在為控件編寫任何代碼之前默穴,請(qǐng)將其添加到視圖控制器中怔檩,以便您可以觀察其演變。
打開ViewController.swift
并使用以下內(nèi)容替換其內(nèi)容:
import UIKit
class ViewController: UIViewController {
let rangeSlider = RangeSlider(frame: .zero)
override func viewDidLoad() {
super.viewDidLoad()
rangeSlider.backgroundColor = .red
view.addSubview(rangeSlider)
}
override func viewDidLayoutSubviews() {
let margin: CGFloat = 20
let width = view.bounds.width - 2 * margin
let height: CGFloat = 30
rangeSlider.frame = CGRect(x: 0, y: 0,
width: width, height: height)
rangeSlider.center = view.center
}
}
此代碼在給定框架中創(chuàng)建控件的實(shí)例蓄诽,并將其添加到視圖中薛训。 它還將背景顏色設(shè)置為紅色,以便控件在屏幕上可見仑氛。
構(gòu)建并運(yùn)行您的應(yīng)用程序乙埃。 您應(yīng)該看到類似于以下內(nèi)容的內(nèi)容:
在將可視元素添加到控件之前,您需要一些屬性來跟蹤控件的狀態(tài)锯岖。 這將構(gòu)成控件的應(yīng)用程序編程接口(簡(jiǎn)稱API)的開始介袜。
注意:您的控件的API定義了您決定向?qū)⑹褂媚目丶钠渌_發(fā)人員公開的方法和屬性。
Adding Default Control Properties - 添加默認(rèn)控件屬性
打開RangeSlider.swift
并用以下代碼替換代碼:
import UIKit
class RangeSlider: UIControl {
var minimumValue: CGFloat = 0
var maximumValue: CGFloat = 1
var lowerValue: CGFloat = 0.2
var upperValue: CGFloat = 0.8
}
您只需要這四個(gè)屬性來描述此控件的狀態(tài)出吹。 您可以提供范圍的最大值和最小值遇伞,以及用戶設(shè)置的上限和下限值。
精心設(shè)計(jì)的控件應(yīng)該定義一些默認(rèn)的屬性值捶牢,否則當(dāng)它在屏幕上繪制時(shí)你的控件看起來很奇怪鸠珠。
現(xiàn)在是時(shí)候處理控件的交互元素了:即用指示器表示高值和低值以及指示器將滑動(dòng)的軌道。
CoreGraphics vs. Images
您可以在屏幕上呈現(xiàn)控件的兩種主要方法:
- 1)
CoreGraphics
:使用圖層和CoreGraphics
的組合渲染您的控件秋麸。 - 2)
Images
:創(chuàng)建代表控件各種元素的圖像渐排。
每種技術(shù)都有利弊,如下所述:
-
Core Graphics:使用
Core Graphics
構(gòu)建控件意味著您必須自己編寫渲染代碼灸蟆,這需要更多的努力驯耻。但是,此技術(shù)允許您創(chuàng)建更靈活的API。
使用Core Graphics
可缚,您可以參數(shù)化控件的每個(gè)功能孽水,例如顏色,邊框粗細(xì)和曲率 - 幾乎所有用于繪制控件的視覺元素城看!
-
Images:使用圖像構(gòu)建控件可能是創(chuàng)建控件的最簡(jiǎn)單選項(xiàng)女气。如果您希望其他開發(fā)人員能夠更改控件的外觀,則需要將這些圖像公開為
UIImage
屬性测柠。
使用images
為使用您的控件的開發(fā)人員提供了最大的靈活性炼鞠。開發(fā)人員可以更改控件外觀的每個(gè)像素和每個(gè)細(xì)節(jié),但這需要出色的圖形設(shè)計(jì)技能 - 并且很難從代碼中修改控件轰胁。
在本教程中谒主,您將使用兩者。您將使用images
渲染指示器和CoreGraphics
來渲染軌道圖層赃阀。
注意:有趣的是霎肯,Apple也傾向于選擇在其控件中使用
images
。這很可能是因?yàn)樗麄冎烂總€(gè)控件的大小榛斯,并且不希望允許過多的自定義观游。畢竟,他們希望所有應(yīng)用程序最終都具有類似的外觀驮俗。
Adding thumbs to your custom control - 將指示器添加到自定義控件
打開RangeSlider.swift
并添加以下屬性懂缕,就在上面定義的屬性之后:
var thumbImage = #imageLiteral(resourceName: "Oval")
private let trackLayer = CALayer()
private let lowerThumbImageView = UIImageView()
private let upperThumbImageView = UIImageView()
trackLayer
,lowerThumbImageView
和upperThumbImageView
用于渲染滑塊控件的各種組件王凑。
仍然在RangeSlider
中鞍时,添加一個(gè)初始化器:
override init(frame: CGRect) {
super.init(frame: frame)
trackLayer.backgroundColor = UIColor.blue.cgColor
layer.addSublayer(trackLayer)
lowerThumbImageView.image = thumbImage
addSubview(lowerThumbImageView)
upperThumbImageView.image = thumbImage
addSubview(upperThumbImageView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
此初始化程序?qū)D層和視圖添加到控件臭墨。
要查看添加的元素,您需要設(shè)置其frame桑孩。 在初始化器之后添加以下代碼:
// 1
private func updateLayerFrames() {
trackLayer.frame = bounds.insetBy(dx: 0.0, dy: bounds.height / 3)
trackLayer.setNeedsDisplay()
lowerThumbImageView.frame = CGRect(origin: thumbOriginForValue(lowerValue),
size: thumbImage.size)
upperThumbImageView.frame = CGRect(origin: thumbOriginForValue(upperValue),
size: thumbImage.size)
}
// 2
func positionForValue(_ value: CGFloat) -> CGFloat {
return bounds.width * value
}
// 3
private func thumbOriginForValue(_ value: CGFloat) -> CGPoint {
let x = positionForValue(value) - thumbImage.size.width / 2.0
return CGPoint(x: x, y: (bounds.height - thumbImage.size.height) / 2.0)
}
以下是這些方法的內(nèi)容:
- 1) 在第一種方法中肋层,您將
trackLayer
居中并使用thumbOriginForValue(_ :)
計(jì)算指示器的位置原叮。 - 2) 此方法將給定值縮放到邊界的上下文楣颠。
- 3) 最后瓢阴,
thumbOriginForValue(_ :)
返回位置,以便指示器在給定縮放值的情況下居中瓣戚。
將以下代碼添加到init(frame :)
的末尾以調(diào)用更新方法:
updateLayerFrames()
接下來端圈,通過將以下內(nèi)容添加到類的頂部來重寫frame
并實(shí)現(xiàn)屬性觀察器:
override var frame: CGRect {
didSet {
updateLayerFrames()
}
}
屬性觀察者在幀更改時(shí)更新圖層幀焦读。 當(dāng)使用不像ViewController.swift
中的最終frame那樣初始化控件時(shí)子库,這是必需的。
構(gòu)建并運(yùn)行您的應(yīng)用程序矗晃。 你的滑塊開始成型仑嗅!
紅色是整個(gè)控件的背景色;藍(lán)色是滑塊的軌道顏色;藍(lán)色圓圈是上下兩個(gè)值的兩個(gè)指示器仓技。
您的控件開始在視覺上形成鸵贬,但您無法與它進(jìn)行交互!
為了您的控制脖捻,用戶必須能夠拖動(dòng)每個(gè)指示器以設(shè)置所需的控件范圍阔逼。 您將處理這些交互并更新UI和控件公開的屬性。
Adding Touch Handlers - 添加Touch處理
打開RangeSlider.swift
并添加以下屬性以及其他屬性:
private var previousLocation = CGPoint()
您可以使用此屬性來跟蹤觸摸位置地沮。
您如何跟蹤控件的各種觸摸和釋放事件嗜浮?
UIControl
提供了幾種跟蹤觸摸的方法。 UIControl的子類可以重寫這些方法以添加自己的交互邏輯摩疑。
在你的自定義控件中危融,你將重寫UIControl
的三個(gè)關(guān)鍵方法:beginTracking(_:with :)
,continueTracking(_:with :)
和endTracking(_:with :)
雷袋。
將以下代碼添加到RangeSlider.swift
文件的末尾:
extension RangeSlider {
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
// 1
previousLocation = touch.location(in: self)
// 2
if lowerThumbImageView.frame.contains(previousLocation) {
lowerThumbImageView.isHighlighted = true
} else if upperThumbImageView.frame.contains(previousLocation) {
upperThumbImageView.isHighlighted = true
}
// 3
return lowerThumbImageView.isHighlighted || upperThumbImageView.isHighlighted
}
}
當(dāng)用戶第一次觸摸控件時(shí)吉殃,iOS會(huì)調(diào)用此方法。 以下是它的工作原理:
- 1) 首先楷怒,它將觸摸事件轉(zhuǎn)換為控件的坐標(biāo)空間蛋勺。
- 2) 接下來,它會(huì)檢查每個(gè)指示器視圖以查看觸摸是否在其frame內(nèi)鸠删。
- 3) 返回值通知
UIControl
超類是否應(yīng)跟蹤后續(xù)觸摸迫卢。 如果突出顯示任一指示器,則繼續(xù)跟蹤觸摸事件
現(xiàn)在您已經(jīng)有了初始觸摸事件冶共,當(dāng)用戶的手指在屏幕上移動(dòng)時(shí)乾蛤,您需要處理事件。
在beginTracking(_:with :)
之后添加以下方法:
override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let location = touch.location(in: self)
// 1
let deltaLocation = location.x - previousLocation.x
let deltaValue = (maximumValue - minimumValue) * deltaLocation / bounds.width
previousLocation = location
// 2
if lowerThumbImageView.isHighlighted {
lowerValue += deltaValue
lowerValue = boundValue(lowerValue, toLowerValue: minimumValue,
upperValue: upperValue)
} else if upperThumbImageView.isHighlighted {
upperValue += deltaValue
upperValue = boundValue(upperValue, toLowerValue: lowerValue,
upperValue: maximumValue)
}
// 3
CATransaction.begin()
CATransaction.setDisableActions(true)
updateLayerFrames()
CATransaction.commit()
return true
}
// 4
private func boundValue(_ value: CGFloat, toLowerValue lowerValue: CGFloat,
upperValue: CGFloat) -> CGFloat {
return min(max(value, lowerValue), upperValue)
}
這是代碼細(xì)分:
- 1) 首先捅僵,計(jì)算增量位置家卖,該位置確定用戶手指行進(jìn)的點(diǎn)數(shù)。 然后庙楚,您可以根據(jù)控件的最小值和最大值將其轉(zhuǎn)換為縮放的
delta
值上荡。 - 2) 在這里,您可以根據(jù)用戶拖動(dòng)滑塊的位置調(diào)整上限或下限值馒闷。
- 3) 此部分在
CATransaction
中設(shè)置disabledActions
標(biāo)志酪捡。 這可確保立即應(yīng)用每個(gè)圖層的frame更改,而沒有動(dòng)畫纳账。 最后逛薇,調(diào)用updateLayerFrames
將指示器移動(dòng)到正確的位置。 - 4)
boundValue(_:toLowerValue:upperValue :)
限制傳入的值疏虫,使其在指定范圍內(nèi)永罚。 使用此輔助函數(shù)比嵌套的min/max
調(diào)用更容易閱讀啤呼。
您已經(jīng)編碼了滑塊的拖動(dòng),但您仍需要處理觸摸和拖動(dòng)事件的結(jié)束呢袱。
在continueTracking(_:with :)
之后添加以下方法:
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
lowerThumbImageView.isHighlighted = false
upperThumbImageView.isHighlighted = false
}
此代碼將兩個(gè)指示器重置為非突出顯示狀態(tài)官扣。
構(gòu)建并運(yùn)行您的項(xiàng)目,并使用閃亮的新滑塊羞福! 你應(yīng)該可以拖動(dòng)指示器惕蹄。
您會(huì)注意到,當(dāng)滑塊跟蹤觸摸時(shí)治专,您可以將手指拖動(dòng)到控件的邊界之外焊唬,然后返回控件內(nèi),而不會(huì)丟失跟蹤操作看靠。 對(duì)于具有低精度指示設(shè)備的小屏幕設(shè)備而言赶促,這是一個(gè)重要的可用性功能 - 或者因?yàn)樗鼈兏R姟?/p>
Notifying Changes - 通知改變
您現(xiàn)在擁有一個(gè)交互式控件,用戶可以操作它來設(shè)置上限和下限挟炬。但是鸥滨,您如何將這些更改通知傳達(dá)給調(diào)用應(yīng)用程序,以便應(yīng)用程序知道控件具有新值谤祖?
您可以實(shí)現(xiàn)許多不同的模式來提供更改通知:NSNotification
婿滓,鍵值觀察(KVO),代理模式粥喜,target-action
模式以及許多其他模式凸主。這么多選擇!
那么該怎么辦额湘?
如果您查看UIKit控件卿吐,您會(huì)發(fā)現(xiàn)他們不使用NSNotification
或鼓勵(lì)使用KVO
,因此為了與UIKit保持一致锋华,您可以排除這兩個(gè)選項(xiàng)嗡官。另外兩種模式 - 代理模式,target-action
模式 - 在UIKit中廣泛使用毯焕。
以下是對(duì)代理和target-action
模式的詳細(xì)分析:
Delegate pattern - 委托模式:使用委托模式衍腥,您提供的協(xié)議包含用于一系列通知的方法。該控件有一個(gè)屬性纳猫,通常名為
delegate
婆咸,它接受任何實(shí)現(xiàn)此協(xié)議的類。一個(gè)典型的例子是UITableView
芜辕,它提供了UITableViewDelegate
協(xié)議尚骄。請(qǐng)注意,這些控件只接受單個(gè)委托實(shí)例物遇。委托方法可以使用任意數(shù)量的參數(shù)乖仇,因此您可以根據(jù)需要向這些方法傳遞盡可能多的信息憾儒。Target-action pattern - 目標(biāo)操作模式:UIControl基類提供目標(biāo)操作模式询兴。當(dāng)發(fā)生控制狀態(tài)的改變時(shí)乃沙,目標(biāo)被通知由一個(gè)
UIControlEvents
枚舉值描述的事件。您可以提供多個(gè)目標(biāo)來控制操作诗舰,雖然可以創(chuàng)建自定義事件(請(qǐng)參閱UIControlEventApplicationReserved
)警儒,但您最多只能有四個(gè)自定義事件】舾控制操作無法通過事件發(fā)送任何信息蜀铲,因此在觸發(fā)事件時(shí),它們不能用于傳遞額外信息属百。
這兩種模式的主要區(qū)別如下:
-
Multicast:目標(biāo)操作模式
multicasts
其更改通知记劝,而委托模式綁定到單個(gè)委托實(shí)例。 - Flexibility:您可以在委托模式中自己定義協(xié)議族扰,這意味著您可以精確控制傳遞的信息量厌丑。目標(biāo)操作無法傳遞額外信息,客戶端在收到事件后必須自行查找渔呵。
您的范圍滑塊控件沒有為您提供通知所需的大量狀態(tài)更改或交互怒竿。唯一改變的是控件的上限和下限值。
在這種情況下扩氢,target-action
模式非常有意義耕驰。這是您在iOS自定義控件教程開始時(shí)將UIControl
子類化的原因之一。
它現(xiàn)在有意義录豺!
滑塊值在continueTracking(_:with :)
內(nèi)更新朦肘,因此您需要添加通知代碼。
打開RangeSlider.swift
双饥,找到continueTracking(_:with :)
并在return true
語句之前添加以下內(nèi)容:
sendActions(for: .valueChanged)
這就是通知任何訂閱目標(biāo)的變化所需要做的全部工作厚骗。
現(xiàn)在您已經(jīng)完成了通知處理,您應(yīng)該將其連接到您的應(yīng)用程序兢哭。
打開ViewController.swift
并將以下方法添加到類的底部:
@objc func rangeSliderValueChanged(_ rangeSlider: RangeSlider) {
let values = "(\(rangeSlider.lowerValue) \(rangeSlider.upperValue))"
print("Range slider value changed: \(values)")
}
此方法將范圍滑塊值記錄到控制臺(tái)领舰,以證明控件正在按計(jì)劃發(fā)送通知。
現(xiàn)在迟螺,將以下代碼添加到viewDidLoad()
的末尾:
rangeSlider.addTarget(self, action: #selector(rangeSliderValueChanged(_:)),
for: .valueChanged)
每次范圍滑塊發(fā)送valueChanged
事件時(shí)冲秽,都會(huì)調(diào)用rangeSliderValueChanged(_ :)
。
構(gòu)建并運(yùn)行您的應(yīng)用程序矩父,并來回移動(dòng)滑塊锉桑。 您將在控制臺(tái)中看到控件的值,類似于:
Range slider value changed: (0.117670682730924 0.390361445783134)
Range slider value changed: (0.117670682730924 0.38835341365462)
Range slider value changed: (0.117670682730924 0.382329317269078)
您現(xiàn)在可能已經(jīng)厭倦了查看多色范圍滑塊UI窍株。 它看起來像一個(gè)憤怒的水果沙拉民轴! 是時(shí)候給控件一個(gè)急需的換裝攻柠。
Modifying Your Control With Core Graphics - 使用核心圖形修改您的控件
首先,您將更新滑塊指示器移動(dòng)的軌道圖形后裸。
在RangeSliderTrackLayer.swift
中瑰钮,使用以下代碼替換代碼:
import UIKit
class RangeSliderTrackLayer: CALayer {
weak var rangeSlider: RangeSlider?
}
此代碼將引用添加回RangeSlider
。 由于滑塊擁有軌道微驶,因此引用是一個(gè)weak變量浪谴,以避免循環(huán)引用。
打開RangeSlider.swift
因苹,找到trackLayer
屬性并將其修改為新圖層類的實(shí)例:
private let trackLayer = RangeSliderTrackLayer()
現(xiàn)在苟耻,找到init(frame :)
并將其替換為以下內(nèi)容:
override init(frame: CGRect) {
super.init(frame: frame)
trackLayer.rangeSlider = self
trackLayer.contentsScale = UIScreen.main.scale
layer.addSublayer(trackLayer)
lowerThumbImageView.image = thumbImage
addSubview(lowerThumbImageView)
upperThumbImageView.image = thumbImage
addSubview(upperThumbImageView)
}
上面的代碼確保新的軌道圖層具有對(duì)范圍滑塊的引用,并刪除默認(rèn)的背景顏色扶檐。 設(shè)置contentsScale
因子以匹配設(shè)備屏幕的因子可確保視網(wǎng)膜顯示屏上的一切都清晰凶杖。
還有一點(diǎn):刪除控件的紅色背景。
打開ViewController.swift
款筑,在viewDidLoad()
中找到以下行并將其刪除:
rangeSlider.backgroundColor = .red
立即構(gòu)建并運(yùn)行智蝠。 你看到了什么?
指示器處于懸浮狀態(tài)醋虏?
不要擔(dān)心 - 你剛剛刪除了華而不實(shí)的測(cè)試顏色寻咒。 你的控件仍在那里,但現(xiàn)在你有一個(gè)空白的畫布來裝扮它颈嚼。
由于大多數(shù)開發(fā)人員喜歡它時(shí)控件可以配置為模擬他們正在編碼的特定應(yīng)用程序的外觀和感覺毛秘,因此您將向滑塊添加一些屬性以允許自定義控件的外觀。
打開RangeSlider.swift
并在upperValue
屬性下添加以下屬性:
var trackTintColor = UIColor(white: 0.9, alpha: 1)
var trackHighlightTintColor = UIColor(red: 0, green: 0.45, blue: 0.94, alpha: 1)
接下來阻课,打開RangeSliderTrackLayer.swift
叫挟。
此圖層渲染兩個(gè)指示器滑動(dòng)的軌道。 它目前繼承自CALayer
限煞,它只呈現(xiàn)純色抹恳。
要繪制軌道,您需要實(shí)現(xiàn)draw(in :)
并使用Core Graphics API
來執(zhí)行渲染署驻。
將以下方法添加到RangeSliderTrackLayer
:
override func draw(in ctx: CGContext) {
guard let slider = rangeSlider else {
return
}
let path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
ctx.addPath(path.cgPath)
ctx.setFillColor(slider.trackTintColor.cgColor)
ctx.fillPath()
ctx.setFillColor(slider.trackHighlightTintColor.cgColor)
let lowerValuePosition = slider.positionForValue(slider.lowerValue)
let upperValuePosition = slider.positionForValue(slider.upperValue)
let rect = CGRect(x: lowerValuePosition, y: 0,
width: upperValuePosition - lowerValuePosition,
height: bounds.height)
ctx.fill(rect)
}
剪裁軌道形狀后奋献,您將填充背景。 之后旺上,您將填寫突出顯示的范圍瓶蚂。
構(gòu)建并運(yùn)行以查看您的新軌道圖層! 它看起來像這樣:
Handling Changes to Control Properties - 處理對(duì)控件屬性的更改
控件現(xiàn)在看起來很時(shí)髦宣吱;視覺樣式是多功能的窃这,它支持target-action
通知。
聽起來你已經(jīng)完成了征候?
想一想如果在渲染后在代碼中設(shè)置了一個(gè)范圍滑塊屬性會(huì)發(fā)生什么杭攻。 例如祟敛,您可能希望將滑塊范圍更改為某個(gè)預(yù)設(shè)值,或更改軌道突出顯示以指示有效范圍兆解。
目前馆铁,沒有什么觀察屬性setter。 您需要將該功能添加到控件中痪宰。 您需要實(shí)現(xiàn)更新控件frame或繪圖的屬性觀察器叼架。
打開RangeSlider.swift
并更改以下屬性的屬性聲明畔裕,如下所示:
var minimumValue: CGFloat = 0 {
didSet {
updateLayerFrames()
}
}
var maximumValue: CGFloat = 1 {
didSet {
updateLayerFrames()
}
}
var lowerValue: CGFloat = 0.2 {
didSet {
updateLayerFrames()
}
}
var upperValue: CGFloat = 0.8 {
didSet {
updateLayerFrames()
}
}
var trackTintColor = UIColor(white: 0.9, alpha: 1) {
didSet {
trackLayer.setNeedsDisplay()
}
}
var trackHighlightTintColor = UIColor(red: 0, green: 0.45, blue: 0.94, alpha: 1) {
didSet {
trackLayer.setNeedsDisplay()
}
}
var thumbImage = #imageLiteral(resourceName: "Oval") {
didSet {
upperThumbImageView.image = thumbImage
lowerThumbImageView.image = thumbImage
updateLayerFrames()
}
}
var highlightedThumbImage = #imageLiteral(resourceName: "HighlightedOval") {
didSet {
upperThumbImageView.highlightedImage = highlightedThumbImage
lowerThumbImageView.highlightedImage = highlightedThumbImage
updateLayerFrames()
}
}
您可以調(diào)用setNeedsDisplay()
來更改軌道圖層衣撬,并為每個(gè)其他更改調(diào)用updateLayerFrames()
。 更改thumbImage
或highlightedThumbImage
時(shí)扮饶,還會(huì)更改其各自圖像視圖的屬性具练。
您還添加了一個(gè)新屬性!highlightThumbImage
顯示指示器圖像視圖高亮顯示甜无。 它有助于為用戶提供有關(guān)他們?nèi)绾闻c控件交互的更多反饋扛点。
現(xiàn)在,找到updateLayerFrames()
并將以下內(nèi)容添加到方法的頂部:
CATransaction.begin()
CATransaction.setDisableActions(true)
將以下內(nèi)容添加到方法的最底部:
CATransaction.commit()
此代碼將整個(gè)幀更新包裝到一個(gè)事務(wù)中岂丘,以使重新流渲染變得平滑陵究。 它還會(huì)禁用圖層上的隱式動(dòng)畫,就像之前一樣奥帘,因此圖層frame
會(huì)立即更新铜邮。
由于您現(xiàn)在每次更改上限值和下限值時(shí)自動(dòng)更新frame
,請(qǐng)?jiān)?code>continueTracking(_:with :)中找到以下代碼并將其刪除:
// 3
CATransaction.begin()
CATransaction.setDisableActions(true)
updateLayerFrames()
CATransaction.commit()
這就是讓范圍滑塊對(duì)屬性更改做出反應(yīng)所需要做的全部工作寨蹋。
但是松蒜,您現(xiàn)在需要更多代碼來測(cè)試新的屬性觀察器,并確保所有內(nèi)容都已連接并按預(yù)期工作已旧。
打開ViewController.swift
并將以下代碼添加到viewDidLoad()
的末尾:
let time = DispatchTime.now() + 1
DispatchQueue.main.asyncAfter(deadline: time) {
self.rangeSlider.trackHighlightTintColor = .red
self.rangeSlider.thumbImage = #imageLiteral(resourceName: "RectThumb")
self.rangeSlider.highlightedThumbImage =
#imageLiteral(resourceName: "HighlightedRect")
}
這會(huì)在一秒延遲后更新某些控件的屬性秸苗。 它還將軌道高亮顏色更改為紅色,將指示器圖像更改為矩形运褪。
構(gòu)建并運(yùn)行您的項(xiàng)目惊楼。 一秒鐘之后,您將看到范圍滑塊從此更改:
更改為:
已經(jīng)很好了秸讹!
您的范圍滑塊現(xiàn)在功能齊全檀咙,可以在您自己的應(yīng)用程序中使用!
不僅如此嗦枢。在共享自定義控件之前攀芯,請(qǐng)考慮以下幾點(diǎn):
有很多地方可以開始與全世界共享自定義控件。以下是一些開始的建議:
- GitHub - 分享開源項(xiàng)目最受歡迎的地方之一文虏。 GitHub上已有許多iOS自定義控件侣诺。它允許人們通過分配代碼進(jìn)行其他控制或在現(xiàn)有控件上引發(fā)問題來輕松訪問代碼并進(jìn)行協(xié)作殖演。
-
CocoaPods - 為了讓人們可以輕松地將控件添加到他們的項(xiàng)目中,您可以通過
CocoaPods
共享它年鸳,CocoaPods
是iOS和macOS項(xiàng)目的依賴管理器趴久。 -
Cocoa Controls - 此站點(diǎn)提供商業(yè)和開源控件的目錄。
Cocoa Controls
涵蓋的許多開源控件都托管在GitHub上搔确,這是推廣創(chuàng)作的好方法彼棍。
后記
本篇主要講述了自定義控件:可重復(fù)使用的滑塊,感興趣的給個(gè)贊或者關(guān)注~~~