diff --git a/Sources/SlidableImage.swift b/Sources/SlidableImage.swift index 9e9399c..eb76489 100644 --- a/Sources/SlidableImage.swift +++ b/Sources/SlidableImage.swift @@ -8,6 +8,25 @@ import UIKit +/// A enum used to determine which direction the slider should slide from +public enum SlideDirection: Int { + case left + case right + case top + case bottom +} + +/// A struct containing border information for the slider. You can use this struct to define the width and color of the slider border. +public struct SlidableImageBorder { + var borderWidth: CGFloat + var borderColor: UIColor + + public init(borderWidth width: CGFloat, borderColor color: UIColor) { + self.borderWidth = width + self.borderColor = color + } +} + /// Super easy Slider for before&after images open class SlidableImage: UIView { @@ -20,16 +39,27 @@ open class SlidableImage: UIView { /// Circle view with icon for sliding images. You can override it with your custom view. open var sliderCircle: UIView + /// Enum that describes which direction the slider will slide from. + open var slideDirection: SlideDirection + + /// Struct that tells the slider if there should be a border view added + open var sliderBorder: SlidableImageBorder? = nil + private var sliderBorderView: UIView? + /// Generic initializer with views /// /// - Parameters: /// - frame: Frame size /// - firstView: First view - should have size equal to frame and second view /// - secondView: Second view - should have size equal to frame and second view - public init(frame: CGRect, firstView: UIView, secondView: UIView) { + /// - slideDirection: Optional parameter for the direction that the slider should slide. The default value is .right. + /// - sliderBorder: Optional paramter that will add a border to the slider of the specfied size and color + public init(frame: CGRect, firstView: UIView, secondView: UIView, sliderImage: UIImage? = nil, slideDirection: SlideDirection? = nil, sliderBorder: SlidableImageBorder? = nil) { self.firstView = firstView self.secondView = secondView - sliderCircle = SlidableImage.setupSliderCircle() + self.sliderCircle = SlidableImage.setupSliderCircle(sliderImage: sliderImage) + self.slideDirection = slideDirection ?? .right + self.sliderBorder = sliderBorder super.init(frame: frame) initializeViews() @@ -42,11 +72,13 @@ open class SlidableImage: UIView { /// - frame: Frame size /// - firstImage: First image for sliding /// - secondImage: Second image for sliding - convenience public init(frame: CGRect, firstImage: UIImage, secondImage: UIImage) { + /// - slideDirection: Optional parameter for the direction that the slider should slide. The default value is .right. + /// - sliderBorder: Optional paramter that will add a border to the slider of the specfied size and color + convenience public init(frame: CGRect, firstImage: UIImage, secondImage: UIImage, sliderImage: UIImage? = nil, slideDirection: SlideDirection? = nil, sliderBorder: SlidableImageBorder? = nil) { let firstView = SlidableImage.setup(image: firstImage, frame: frame) let secondView = SlidableImage.setup(image: secondImage, frame: frame) - - self.init(frame: frame, firstView: firstView, secondView: secondView) + + self.init(frame: frame, firstView: firstView, secondView: secondView, sliderImage: sliderImage, slideDirection: slideDirection, sliderBorder: sliderBorder) } required public init?(coder aDecoder: NSCoder) { @@ -57,26 +89,69 @@ open class SlidableImage: UIView { /// /// - Parameter maskLocation: Position of slider open func updateMask(location maskLocation: CGFloat) { - let maskRectPath = UIBezierPath(rect: CGRect(x: bounds.minX, + var maskRectPath: UIBezierPath + let mask = CAShapeLayer() + switch slideDirection { + case .left: + maskRectPath = UIBezierPath(rect: CGRect(x: maskLocation, + y: bounds.minY, + width: bounds.width, + height: bounds.height)) + case .right: + maskRectPath = UIBezierPath(rect: CGRect(x: bounds.minX, y: bounds.minY, width: maskLocation, height: bounds.height)) - let mask = CAShapeLayer() + case .top: + maskRectPath = UIBezierPath(rect: CGRect(x: bounds.minX, + y: maskLocation, + width: bounds.width, + height: bounds.height)) + case .bottom: + maskRectPath = UIBezierPath(rect: CGRect(x: bounds.minX, + y: bounds.minY, + width: bounds.width, + height: maskLocation)) + } mask.path = maskRectPath.cgPath secondView.layer.mask = mask - - sliderCircle.center.x = maskLocation + switch slideDirection { + case .left, .right: + sliderCircle.center.x = maskLocation + case .top, .bottom: + sliderCircle.center.y = maskLocation + } } /// Private wrapper for setup view fileprivate func initializeViews() { clipsToBounds = true sliderCircle.center = center - updateMask(location: center.x) - + switch slideDirection { + case .left, .right: + updateMask(location: center.x) + case .top, .bottom: + updateMask(location: center.y) + } + addSubview(firstView) addSubview(secondView) addSubview(sliderCircle) + + // Only add the slider to the view if a non nil sliderBorder was set + if let sliderBorder = sliderBorder { + sliderBorderView = UIView() + sliderBorderView?.translatesAutoresizingMaskIntoConstraints = false + sliderBorderView?.backgroundColor = sliderBorder.borderColor + addSubview(sliderBorderView!) + addConstraints([ + NSLayoutConstraint(item: sliderBorderView!, attribute: .centerX, relatedBy: .equal, toItem: sliderCircle, attribute: .centerX, multiplier: 1.0, constant: 0.0), + NSLayoutConstraint(item: sliderBorderView!, attribute: .centerY, relatedBy: .equal, toItem: sliderCircle, attribute: .centerY, multiplier: 1.0, constant: 0.0), + NSLayoutConstraint(item: sliderBorderView!, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 1.0, constant: 0.0), + NSLayoutConstraint(item: sliderBorderView!, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: sliderBorder.borderWidth) + ]) + bringSubview(toFront: sliderCircle) + } } /// Private wrapper for adding gesture recognizer @@ -88,20 +163,37 @@ open class SlidableImage: UIView { @objc private func gestureHandler(_ panGestureRecognizer: UIPanGestureRecognizer) { let location = panGestureRecognizer.location(in: firstView) - guard (bounds.minX...bounds.maxX ~= location.x) else { - return + switch slideDirection { + case .left, .right: + guard (bounds.minX...bounds.maxX ~= location.x) else { return } + updateMask(location: location.x) + case .top, .bottom: + guard (bounds.minY...bounds.maxY ~= location.y) else { return } + updateMask(location: location.y) } - - updateMask(location: location.x) } /// Private wrapper for setup circle slider view /// + /// - Parameters: + /// - image: Content image for slider circle /// - Returns: Slider circle - private class func setupSliderCircle() -> UIView { - let frame = CGRect(x: 0, y: 0, width: 50, height: 50) - - return ArrowsView(frame: frame) + private class func setupSliderCircle(sliderImage: UIImage? = nil) -> UIView { + // Workaround - without this view, gesture recognizer doesn't work + let circle = UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 50)) + + guard let sliderImage = sliderImage else { + return ArrowsView(frame: frame) + } + + circle.layer.cornerRadius = circle.bounds.width / 2 + + let imageView = UIImageView(image: sliderImage) + imageView.contentMode = .scaleAspectFill + circle.addSubview(imageView) + imageView.center = circle.center + + return circle } /// Private wrapper for setup image view