Skip to content

Instantly share code, notes, and snippets.

@JULI-ya
Created August 31, 2016 20:42
Show Gist options
  • Select an option

  • Save JULI-ya/1a7c293b022207bb427caa3bbb9d3ed8 to your computer and use it in GitHub Desktop.

Select an option

Save JULI-ya/1a7c293b022207bb427caa3bbb9d3ed8 to your computer and use it in GitHub Desktop.

Revisions

  1. JULI-ya created this gist Aug 31, 2016.
    226 changes: 226 additions & 0 deletions SeparatorView.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,226 @@
    import UIKit

    enum SeparatorType {
    case Horisontal
    case Vertical
    }

    let kTotalSize: CGFloat = 44; // the total height of the separator (including parts that are not visible
    let kVisibleSize: CGFloat = 10; // the height of the visible portion of the separator
    let kMargin: CGFloat = (kTotalSize - kVisibleSize) / 2.0; // the height of the non-visible portions of the separator (i.e. above and below the visible portion)
    let kMinSize: CGFloat = 10; // the minimum height allowed for views above and below the separator

    protocol OnConstraintsUpdateProtocol {
    func updateConstraintOnBasisOfTouch(touch: UITouch)
    }

    class SeparatorView: UIView {

    var startConstraint: NSLayoutConstraint? // the constraint that dictates the vertical position of the separator
    var primaryView: UIView // the view above the separator
    var secondaryView: UIView // the view below the separator

    // some properties used for handling the touches

    var oldPosition: CGFloat = 0.0 // the position of the separator before the gesture started
    var firstTouch: CGPoint? // the position where the drag gesture started

    var updateListener: OnConstraintsUpdateProtocol?

    internal static func addSeparatorBetweenViews(separatorType: SeparatorType, primaryView: UIView, secondaryView: UIView, parentView: UIView) -> SeparatorView {

    var separator: SeparatorView

    if (separatorType == .Horisontal) {
    separator = HorizontalSeparatorView(primaryView: primaryView, secondaryView: secondaryView)
    } else {
    separator = VerticalSeparatorView(primaryView: primaryView, secondaryView: secondaryView)
    }
    separator.setupParentViewConstraints(parentView)
    parentView.addSubview(separator)
    separator.setupSeparatorConstraints()

    return separator
    }

    init(primaryView: UIView, secondaryView: UIView){
    self.primaryView = primaryView
    self.secondaryView = secondaryView
    super.init(frame: CGRectZero)
    self.translatesAutoresizingMaskIntoConstraints = false
    self.userInteractionEnabled = true
    self.backgroundColor = UIColor.clearColor()
    }

    func setupParentViewConstraints(parentView: UIView){}

    func setupSeparatorConstraints(){}

    required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
    }

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    self.firstTouch = touches.first?.locationInView(self.superview)
    self.startConstraint!.constant = self.oldPosition;
    self.startConstraint!.active = true;
    }

    override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {

    guard let touch = touches.first, event = event else { return }

    // for more responsive UX, use predicted touches, if possible
    let predictedTouch = event.predictedTouchesForTouch(touch)?.last
    if ((predictedTouch) != nil) {
    updateListener?.updateConstraintOnBasisOfTouch(predictedTouch!)
    return;
    }
    // if no predicted touch found, just use the touch provided
    updateListener?.updateConstraintOnBasisOfTouch(touch)
    }

    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
    guard let touch = touches.first else { return }
    updateListener?.updateConstraintOnBasisOfTouch(touch)
    }

    override func drawRect(rect: CGRect) {
    let path = UIBezierPath(rect: rect)
    UIColor.blackColor().set()
    path.stroke()
    path.fill()
    }
    }

    class HorizontalSeparatorView: SeparatorView, OnConstraintsUpdateProtocol {

    override init(primaryView: UIView, secondaryView: UIView) {
    super.init(primaryView: primaryView, secondaryView: secondaryView)
    updateListener = self
    }

    required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
    }

    override func setupParentViewConstraints(parentView: UIView) {
    parentView.leadingAnchor.constraintEqualToAnchor(primaryView.leadingAnchor).active = true
    parentView.trailingAnchor.constraintEqualToAnchor(primaryView.trailingAnchor).active = true
    parentView.leadingAnchor.constraintEqualToAnchor(secondaryView.leadingAnchor).active = true
    parentView.trailingAnchor.constraintEqualToAnchor(secondaryView.trailingAnchor).active = true
    parentView.topAnchor.constraintEqualToAnchor(primaryView.topAnchor).active = true
    let height = secondaryView.heightAnchor.constraintEqualToAnchor(primaryView.heightAnchor)
    height.priority = 250
    height.active = true
    parentView.bottomAnchor.constraintEqualToAnchor(secondaryView.bottomAnchor).active = true
    }

    override func setupSeparatorConstraints() {
    self.heightAnchor.constraintEqualToConstant(kTotalSize).active = true
    self.superview?.leadingAnchor.constraintEqualToAnchor(self.leadingAnchor).active = true
    self.superview?.trailingAnchor.constraintEqualToAnchor(self.trailingAnchor).active = true
    primaryView.bottomAnchor.constraintEqualToAnchor(self.topAnchor, constant: kMargin).active = true
    secondaryView.topAnchor.constraintEqualToAnchor(self.bottomAnchor, constant: -kMargin).active = true

    startConstraint = self.topAnchor.constraintEqualToAnchor(self.superview?.topAnchor, constant: 0)
    }

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    self.oldPosition = self.frame.origin.y;
    super.touchesBegan(touches, withEvent: event)
    }

    override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
    super.touchesMoved(touches, withEvent: event)
    }


    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
    super.touchesEnded(touches, withEvent: event)
    }

    func updateConstraintOnBasisOfTouch(touch: UITouch) {
    // calculate where separator should be moved to
    var y: CGFloat = self.oldPosition + touch.locationInView(self.superview).y - self.firstTouch!.y

    // make sure the views above and below are not too small
    y = max(y, self.primaryView.frame.origin.y + kMinSize - kMargin)
    y = min(y, self.secondaryView.frame.origin.y + self.secondaryView.frame.size.height - (kMargin + kMinSize))

    // set constraint
    self.startConstraint!.constant = y
    }

    override func drawRect(rect: CGRect) {
    let separatorRect = CGRectMake(0, kMargin, self.bounds.size.width, kVisibleSize)
    super.drawRect(separatorRect)
    }

    }

    class VerticalSeparatorView: SeparatorView, OnConstraintsUpdateProtocol {

    override init(primaryView: UIView, secondaryView: UIView) {
    super.init(primaryView: primaryView, secondaryView: secondaryView)
    updateListener = self
    }

    required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
    }

    override func setupParentViewConstraints(parentView: UIView) {
    parentView.topAnchor.constraintEqualToAnchor(primaryView.topAnchor).active = true
    parentView.topAnchor.constraintEqualToAnchor(secondaryView.topAnchor).active = true
    parentView.bottomAnchor.constraintEqualToAnchor(primaryView.bottomAnchor).active = true
    parentView.leadingAnchor.constraintEqualToAnchor(secondaryView.topAnchor).active = true
    parentView.bottomAnchor.constraintEqualToAnchor(secondaryView.bottomAnchor).active = true
    parentView.leadingAnchor.constraintEqualToAnchor(primaryView.leadingAnchor).active = true
    let width = secondaryView.widthAnchor.constraintEqualToAnchor(primaryView.widthAnchor)
    width.priority = 250
    width.active = true
    parentView.trailingAnchor.constraintEqualToAnchor(secondaryView.trailingAnchor).active = true
    }

    override func setupSeparatorConstraints() {
    self.widthAnchor.constraintEqualToConstant(kTotalSize).active = true
    self.superview?.topAnchor.constraintEqualToAnchor(self.topAnchor).active = true
    self.superview?.bottomAnchor.constraintEqualToAnchor(self.bottomAnchor).active = true
    primaryView.trailingAnchor.constraintEqualToAnchor(self.leadingAnchor, constant: kMargin).active = true
    secondaryView.leadingAnchor.constraintEqualToAnchor(self.trailingAnchor, constant: -kMargin).active = true

    startConstraint = self.leadingAnchor.constraintEqualToAnchor(self.superview?.leadingAnchor, constant: 0)
    }

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    self.oldPosition = self.frame.origin.x;
    super.touchesBegan(touches, withEvent: event)
    }

    override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
    super.touchesMoved(touches, withEvent: event)
    }

    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
    super.touchesEnded(touches, withEvent: event)
    }

    func updateConstraintOnBasisOfTouch(touch: UITouch) {
    // calculate where separator should be moved to
    var x: CGFloat = self.oldPosition + touch.locationInView(self.superview).x - self.firstTouch!.x

    // make sure the views above and below are not too small
    x = max(x, self.primaryView.frame.origin.x + kMinSize - kMargin)
    x = min(x, self.secondaryView.frame.origin.x + self.secondaryView.frame.size.width - (kMargin + kMinSize))

    // set constraint
    self.startConstraint!.constant = x
    }

    override func drawRect(rect: CGRect) {
    let separatorRect = CGRectMake(kMargin, 0, kVisibleSize, self.bounds.size.height)
    super.drawRect(separatorRect)
    }

    }