Lope (Customizable SliderView)
Just like my teacher would always say, don’t reinvent the wheel. These were the words that rang in my head when I was to use a SliderView in the Max Okada Champion app. Like designers do not care they just make very beautiful designs and tell you “hey I am done” I searched the web to get already implemented designs just to follow the approach but found nothing. Time was running out and I had to show that I still got it ‘LOL’ so I thought about the logic for a second the I decided to bring my thought to life
I though to myself should I use the interface builder or not and guess what, I ended up using pure code for Lope. Don’t run just yet, you can also implement using the interface builder. Let us dive into it…. yay
The first step is to setup your components
class Lope: UIView {
lazy var baseView: UIView = {
let baseView = UIView()
baseView.translatesAutoresizingMaskIntoConstraints = false
baseView.backgroundColor = .gray
return baseView
}() lazy var sliderImage: UIImageView = {
let sliderImage = UIImageView()
sliderImage.translatesAutoresizingMaskIntoConstraints = false
sliderImage.backgroundColor = .blue
sliderImage.layer.cornerRadius = 10
sliderImage.isUserInteractionEnabled = true
sliderImage.clipsToBounds = true
return sliderImage
}()
lazy var title: UILabel = {
let title = UILabel()
title.text = "Slide Me..."
title.translatesAutoresizingMaskIntoConstraints = false
return title
}()
var viewCenter: CGPoint!
var startingFrame: CGRect?
weak var delegate: LopeDelegate?
let screenSize = UIScreen.main.bounds
var slided = false override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(baseView)
baseView.addSubview(sliderImage)
baseView.addSubview(title)
constraints()
swipeFunc()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}}
We declare a private function and name it swipeFunc()
We also declare a function to hold our constrains constraints()
Now to the constraint logic
func constraints() {
NSLayoutConstraint.activate([
baseView.centerXAnchor.constraint(equalTo: self.centerXAnchor, constant: 0),
baseView.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0),
baseView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0),
baseView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0),
baseView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0),
baseView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0),
sliderImage.centerYAnchor.constraint(equalTo: baseView.centerYAnchor, constant: 0),
sliderImage.leadingAnchor.constraint(equalTo: baseView.leadingAnchor, constant: 4),
sliderImage.topAnchor.constraint(equalTo: baseView.topAnchor, constant: 8),
sliderImage.bottomAnchor.constraint(equalTo: baseView.bottomAnchor, constant: -8),
sliderImage.aspectRation(1.0/1.0),
title.centerXAnchor.constraint(equalTo: baseView.centerXAnchor, constant: 0),
title.centerYAnchor.constraint(equalTo: baseView.centerYAnchor, constant: 0),
])
}
To the business logic
private func swipeFunc() {// 1 Create a Pan gestureRecognizer
let swipeGesture = UIPanGestureRecognizer(target: self, action: #selector(acknowledgeSwiped(sender:)))
sliderImage.addGestureRecognizer(swipeGesture)
swipeGesture.delegate = self as? UIGestureRecognizerDelegate
}
@objc func acknowledgeSwiped(sender: UIPanGestureRecognizer) {
if let sliderView = sender.view {// 2 Set the translation variable to the translation of the baseView let translation = sender.translation(in: self.baseView) // 3 For the cases of the Pan gesture, .begin, .changed, .ended/.default switch sender.state {
case .began:
startingFrame = sliderImage.frame
fallthrough
case .changed:
if let startFrame = startingFrame {
var movex = translation.x
if movex < -startFrame.origin.x {
movex = -startFrame.origin.x
}
let xMax = self.baseView.frame.width - startFrame.origin.x - startFrame.width
if movex > xMax {
movex = xMax
}
// 6 Using CGAffineTransform of CoreGraphics, we set the X value of the translation to movex and y to 0. Why is set to zero in order to make sure the sliderView only moves in the center of the backgroundView sliderView.transform = CGAffineTransform(translationX: movex, y: 0)
}
default:// 7 after the pan reaches the end of the View, it is then animated back to its initial position and to avoid users dragging the slider to the right, we check to be sure that the sliderImage.frame.origin.x casted from CGFloat to Double is greater than zero.Then we called the endSlide Delegate Method UIView.animate(withDuration: 0.1, animations: {
if (Double(self.sliderImage.frame.origin.x) > 0) {
self.delegate?.endSlide(true)
}
sliderView.transform = CGAffineTransform.identity
})
}
}
}
// Overiding point is important to make the slider slide in the view
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return sliderImage.frame.contains(point)
}
using delegations to perfom request on begin and on finish of slideprotocol LopeDelegate: class {
func startSlide(_ start: Bool)
func endSlide(_ end: Bool)
}
Using this in your project is as easy as
var lope: Lope!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
lope = Lope(frame: CGRect.zero)
lope.delegate = self
lope.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(lope)
setup()
}
func setup() {
NSLayoutConstraint.activate([
// lope.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0),
// lope.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0),
lope.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0),
lope.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0),
lope.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0),
lope.heightAnchor.constraint(equalToConstant: 64),
])
}
}
extension ViewController: LopeDelegate {
func startSlide(_ start: Bool) {
print("startSlide Lope")
}
func endSlide(_ end: Bool) {
print("endSlide Lope")
}
}
Complete Project is available Via CocoaPods