Swift programlama dilinde base UI elemanlarını kullanarak, tasarımda bizden istenen custom componentleri kolayca geliştirebiliriz.
Örneğin aşağıdaki gibi bir Slider yapısını UISlider’dan türeterek yapmaya çalışalım.
UISlider İnceleme
UISlider yukarıdaki gibi bir iskelete sahiptir.
Thumb -> Değer ayarlamak için sürükleme işlemi yapan araç
Track -> Thumb’un üzerinde hareket ettiği, minimum ve maksimum noktaları boyunca uzanan çizgi
MinimumTrackColor -> Thumb ve minimum noktasında kalan track rengi
MaximumTrackColor -> Thumb ve maksimum noktasında kalan track rengi
MinimumImage -> Minimum noktasını belirten görsel
MaximumImage -> Maksimum noktasını belirten görsel
Eğer bizden iskelette yer alan nesnelerinin özelliklerini (renk, görsel) değiştirerek elde edilebilecek bir Slider istenirse yukarıdaki özellikleri kullanarak kolayca elde edebiliriz.
Fakat şu an bizim oluşturmak istediğimiz Slider biraz daha özel bir yapı, bunu da hep birlikte nasıl oluşturacağımızı görelim.
Custom Slider Yapımı
Öncelikle UISlider’dan miras alan ‘Slider’ adında bir class oluşturalım.
1 2 3 4 5 6 7 8 9 |
import UIKit final class Slider: UISlider { } |
Ardından tint color’lar bizim işimizi görmeyeceği için görsel olarak ortadan kaldıralım.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import UIKit final class Slider: UISlider { override func draw(_ rect: CGRect) { super.draw(rect) setup() } private func setup() { clear() } private func clear() { tintColor = .clear maximumTrackTintColor = .clear backgroundColor = .clear thumbTintColor = .clear } } |
Şimdi de Track nesnesini çizelim.
Bunu yapmak için bir layer oluşturup, Slider’ımızın sahip olduğu layer’a subLayer olarak ekleyeceğiz.
Not: Buradan sonra yapılacak tüm boyut ayarlamaları vb. işlemler tasarım ve isteğe göre değiştirilebilir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
final class Slider: UISlider { private let baseLayer = CALayer() // Step 3 override func draw(_ rect: CGRect) { super.draw(rect) setup() } private func setup() { clear() createBaseLayer() // Step 3 } private func clear() { tintColor = .clear maximumTrackTintColor = .clear backgroundColor = .clear thumbTintColor = .clear } // Step 3 private func createBaseLayer() { baseLayer.borderWidth = 1 baseLayer.borderColor = UIColor.lightGray.cgColor baseLayer.masksToBounds = true baseLayer.backgroundColor = UIColor.white.cgColor baseLayer.frame = .init(x: 0, y: frame.height / 4, width: frame.width, height: frame.height / 2) baseLayer.cornerRadius = baseLayer.frame.height / 2 layer.insertSublayer(baseLayer, at: 0) } } |
Nereye kadar geldik bir bakalım.
Görüldüğü üzere Track’imiz hazır. Şimdi de Thumb’u oluşturalım.
Öncelikle aşağıdaki gibi bir ThumbView oluşturalım.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// Step 4 final class ThumbView: UIView { override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder: NSCoder) { super.init(coder: coder) setup() } private func setup() { backgroundColor = UIColor(red: 183 / 255, green: 122 / 255, blue: 231 / 255, alpha: 1) let middleView = UIView(frame: .init(x: frame.midX - 6, y: frame.midY - 6, width: 12, height: 12)) middleView.backgroundColor = .white middleView.layer.cornerRadius = 6 addSubview(middleView) } } |
UISlider API’ı Thumb verebilmek için bizden bir view değil image istiyor. Bu nedenle oluşturacağımız ThumbView’ın snapshot’unu almak için aşağıdaki gibi bir extension yazıyoruz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Step 4 extension UIView { var snapshot: UIImage { let renderer = UIGraphicsImageRenderer(bounds: bounds) let capturedImage = renderer.image { context in layer.render(in: context.cgContext) } return capturedImage } } |
Sıra geldi Slider içerisinde thumb image’ı oluşturmaya.
1 2 3 4 5 6 7 8 9 |
private func setup() { clear() createBaseLayer() // Step 3 createThumbImageView() // Step 5 } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Step 5 private func createThumbImageView() { let thumbSize = (3 * frame.height) / 4 let thumbView = ThumbView(frame: .init(x: 0, y: 0, width: thumbSize, height: thumbSize)) thumbView.layer.cornerRadius = thumbSize / 2 let thumbSnapshot = thumbView.snapshot setThumbImage(thumbSnapshot, for: .normal) } |
Burada karşımıza çıkan setThumImage fonksiyonu bizden bir adet image ve o image’in hangi Slider durumunda görünmesi istediğimizi bekliyor. Yukarıdaki gibi sadece ‘.normal’ durumu için bir geliştirme yaparsak aşağıdaki videoda olduğu gibi bir görünüm elde ederiz.
Bu nedenle bütün state’ler için thumb image ayarlamasını yapıyoruz.
1 2 3 4 5 6 7 8 9 10 11 12 |
// Step 6 setThumbImage(thumbSnapshot, for: .normal) setThumbImage(thumbSnapshot, for: .highlighted) setThumbImage(thumbSnapshot, for: .application) setThumbImage(thumbSnapshot, for: .disabled) setThumbImage(thumbSnapshot, for: .focused) setThumbImage(thumbSnapshot, for: .reserved) setThumbImage(thumbSnapshot, for: .selected) |
Görüldüğü üzere artık her state için istediğimiz thumb image’i görebiliyoruz.
Şimdi de thumb image’in solunu yani slider üzerinden seçili kısmı örneğimizdeki gibi gradient bir görünüme kavuşturalım.
Bunun için öncelikle bir gradient layer objesini class düzeyinde yaratıyoruz.
Ardından tıpkı base layer’da yaptığımız gibi özelliklerini verip, layer’a ekliyoruz.
1 2 3 4 5 6 |
private let baseLayer = CALayer() // Step 3 private let trackLayer = CAGradientLayer() // Step 7 |
1 2 3 4 5 6 7 8 9 10 |
private func setup() { clear() createBaseLayer() // Step 3 createThumbImageView() // Step 5 configureTrackLayer() // Step 7 } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// Step 7 private func configureTrackLayer() { let firstColor = UIColor(red: 210/255, green: 152/255, blue: 238/255, alpha: 1).cgColor let secondColor = UIColor(red: 166/255, green: 20/255, blue: 217/255, alpha: 1).cgColor trackLayer.colors = [firstColor, secondColor] trackLayer.startPoint = .init(x: 0, y: 0.5) trackLayer.endPoint = .init(x: 1, y: 0.5) trackLayer.frame = .init(x: 0, y: frame.height / 4, width: 0, height: frame.height / 2) trackLayer.cornerRadius = trackLayer.frame.height / 2 layer.insertSublayer(trackLayer, at: 1) } |
Şimdi de track layer’ın konum ve boyut bilgilerini slider aksiyonuna göre ayarlayalım.
Bunu yapabilmek için öncelikle kullanıcının aksiyonlarını algılayabilmemiz gerekir. Bu sebeple slider’ımıza aksiyonları tanıması için bir target ve fonksiyon ekleyeceğiz.
1 2 3 4 5 6 7 8 9 10 11 12 |
// Step 8 private func addUserInteractions() { addTarget(self, action: #selector(valueChanged(_:)), for: .valueChanged) } @objc private func valueChanged(_ sender: Slider) { } |
1 2 3 4 5 6 7 8 9 10 11 |
private func setup() { clear() createBaseLayer() // Step 3 createThumbImageView() // Step 5 configureTrackLayer() // Step 7 addUserInteractions() // Step 8 } |
“valueChanged” fonksiyonumuzun içini dolduralım.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@objc private func valueChanged(_ sender: Slider) { // Step 9 let thumbRectA = thumbRect(forBounds: bounds, trackRect: trackRect(forBounds: bounds), value: value) trackLayer.frame = .init(x: 0, y: frame.height / 4, width: thumbRectA.midX, height: frame.height / 2) } |
Burada öncelikle thumb image’imizin o anki konumunu öğreniyoruz. Bu bilgi ile track layer’ımızın sahip olacağı genişlik değerini elde etmiş oluyoruz.
Devamında elde edilen genişlik değeriyle beraber track layer’ın frame’ini oluşturuyoruz. Buradaki “y” ve “height” değerleri tercih ve tasarıma göre değişebilir.
Şimdi ne durumdayız bir bakalım.
Track layer’ımızın beklediğimiz gibi çizilmeye başladı ama bir sorunumuz var. Track layer bizim hareketlerimize sürekli geç tepki veriyor GİBİ! Aslında öyle değil, frame değişikliği ani olmak yerine bir transaction action altında animasyon ile gerçekleştiği için böyle göze hoş gelmeyen bir görüntü ortaya çıkıyor.
Şimdi bunu çözelim.
Geldiğimiz son noktada hedefimize ulaştık. Zaman ayırdığınız için teşekkür ederim, faydalı olması dileğiyle.