Last active
September 22, 2015 18:55
-
-
Save kunass2/ca4b82878a56b0e6fa52 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// BSSuperTableView.swift | |
// BSSmartReorder | |
// | |
// Created by Bartłomiej Semańczyk on 18/09/15. | |
// Copyright © 2015 Railwaymen. All rights reserved. | |
// | |
import UIKit | |
enum ReorderDirection { | |
case Up | |
case Down | |
case Unknown | |
} | |
class BSTableViewReorder: UITableView, UIScrollViewDelegate { | |
private var longPressGestureRecognizer: UILongPressGestureRecognizer! | |
private var snapshotForReorderringCell: UIImageView? | |
private var scrollRate: CGFloat = 0 | |
private var scrollDisplayLink: CADisplayLink? | |
private var currentIndexPath: NSIndexPath? | |
private var sourceIndexPath: NSIndexPath? | |
private var previousRelativeLocation = CGPointZero | |
private var form = NSDateFormatter() | |
private var numberOfRowsInTable: Int { | |
get { | |
var numberOfRows = 0 | |
for section in 0..<numberOfSections { | |
numberOfRows += numberOfRowsInSection(section) | |
} | |
return numberOfRows | |
} | |
} | |
private var relativeLocation: CGPoint { | |
get { | |
return longPressGestureRecognizer.locationInView(self) | |
} | |
} | |
private var state: UIGestureRecognizerState { | |
get { | |
return longPressGestureRecognizer.state | |
} | |
} | |
private var coveredIndexPath: NSIndexPath? { | |
get { | |
return indexPathForRowAtPoint(relativeLocation) | |
} | |
} | |
private var cellForCoveredIndexPath: UITableViewCell? { | |
get { | |
return cellForRowAtIndexPath(coveredIndexPath!) | |
} | |
} | |
private var cellForCurrentIndexPath: UITableViewCell? { | |
get { | |
return cellForRowAtIndexPath(currentIndexPath!) | |
} | |
} | |
private var direction: ReorderDirection { | |
get { | |
if previousRelativeLocation.y == relativeLocation.y { | |
return .Unknown | |
} | |
return previousRelativeLocation.y - relativeLocation.y > 0 ? .Up : .Down | |
} | |
} | |
private var heightForCurrentCell: CGFloat { | |
get { | |
return rectForRowAtIndexPath(currentIndexPath!).size.height | |
} | |
} | |
private var heightForCoveredCell: CGFloat { | |
get { | |
return rectForRowAtIndexPath(coveredIndexPath!).size.height | |
} | |
} | |
private var currentContentSize: CGSize { | |
get { | |
let width = contentSize.width | |
var height: CGFloat = 0 | |
for section in 0..<numberOfSections { | |
let numberOfRows = numberOfRowsInSection(section) | |
for row in 0..<numberOfRows { | |
height += rectForRowAtIndexPath(NSIndexPath(forRow: row, inSection: section)).size.height | |
} | |
} | |
return CGSize(width: width, height: height) | |
} | |
} | |
private var currentContentOffset: CGPoint { | |
get { | |
var offset = CGPointZero | |
if let firstVisibleIndexPath = indexPathsForVisibleRows?.first { | |
for row in 0..<firstVisibleIndexPath.row { | |
offset.y += rectForRowAtIndexPath(NSIndexPath(forRow: row, inSection: 0)).size.height | |
} | |
if let cell = cellForRowAtIndexPath(firstVisibleIndexPath) { | |
offset.y += superview!.convertPoint(frame.origin, toView: cell).y | |
} | |
} | |
return offset | |
} | |
} | |
required init?(coder aDecoder: NSCoder) { | |
super.init(coder: aDecoder) | |
longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: Selector("longPressed")) | |
addGestureRecognizer(longPressGestureRecognizer) | |
form.dateFormat = "hh:mm:ss:SSS" | |
} | |
func longPressed() { | |
contentOffset = currentContentOffset | |
guard numberOfRowsInTable > 0 else { | |
longPressGestureRecognizer.enabled = false | |
longPressGestureRecognizer.enabled = true | |
return | |
} | |
switch state { | |
case .Began: | |
if let cell = cellForCoveredIndexPath { | |
cell.setSelected(false, animated: false) | |
cell.setHighlighted(false, animated: false) | |
setupSnapshot() | |
currentIndexPath = coveredIndexPath | |
sourceIndexPath = coveredIndexPath | |
scrollDisplayLink = CADisplayLink(target: self, selector: Selector("scrollTable")) | |
scrollDisplayLink?.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes) | |
cellForCurrentIndexPath?.hidden = true | |
} | |
case .Ended, .Cancelled: | |
scrollDisplayLink?.invalidate() | |
scrollDisplayLink = nil | |
scrollRate = 0 | |
UIView.animateWithDuration(0.25, animations: { | |
let rect = self.rectForRowAtIndexPath(self.currentIndexPath!) | |
self.snapshotForReorderringCell?.transform = CGAffineTransformIdentity | |
self.snapshotForReorderringCell?.frame = CGRectOffset(self.snapshotForReorderringCell!.bounds, rect.origin.x, rect.origin.y) | |
self.cellForCurrentIndexPath?.hidden = false | |
}, completion: { finished in | |
self.dataSource?.tableView?(self, moveRowAtIndexPath: self.sourceIndexPath!, toIndexPath: self.currentIndexPath!) | |
self.snapshotForReorderringCell?.removeFromSuperview() | |
self.snapshotForReorderringCell = nil | |
self.currentIndexPath = nil | |
self.sourceIndexPath = nil | |
}) | |
default: | |
() | |
} | |
} | |
func scrollTable() { | |
guard relativeLocation.y > 0 && relativeLocation.y <= currentContentSize.height + 50 else { | |
longPressGestureRecognizer.enabled = false | |
longPressGestureRecognizer.enabled = true | |
return | |
} | |
let scrollZoneHeight = bounds.size.height / 6 | |
let bottomScrollBeginning = currentContentOffset.y + frame.size.height - scrollZoneHeight | |
let topScrollBeginning = currentContentOffset.y + scrollZoneHeight | |
if relativeLocation.y >= bottomScrollBeginning { | |
scrollRate = 0.2 | |
} else if relativeLocation.y <= topScrollBeginning { | |
scrollRate = -0.2 | |
} else { | |
scrollRate = 0 | |
} | |
var newOffset = CGPointMake(contentOffset.x, contentOffset.y + scrollRate * 10) | |
if currentContentSize.height < frame.size.height { | |
newOffset = contentOffset | |
} else if newOffset.y > currentContentSize.height - frame.size.height { | |
newOffset.y = currentContentSize.height - frame.size.height | |
} else if newOffset.y < 0 { | |
newOffset = CGPointZero | |
} | |
contentOffset = newOffset | |
updateTable() | |
updateSnapshot() | |
previousRelativeLocation = relativeLocation | |
} | |
private func updateTable() { | |
if coveredIndexPath != nil && coveredIndexPath! != currentIndexPath! { | |
let verticalPositionInCoveredCell = longPressGestureRecognizer.locationInView(cellForRowAtIndexPath(coveredIndexPath!)).y | |
if direction == .Down && heightForCoveredCell - verticalPositionInCoveredCell <= heightForCurrentCell / 2 { | |
beginUpdates() | |
moveRowAtIndexPath(currentIndexPath!, toIndexPath: coveredIndexPath!) | |
currentIndexPath = coveredIndexPath | |
endUpdates() | |
} else if direction == .Up && verticalPositionInCoveredCell <= heightForCurrentCell / 2 { | |
beginUpdates() | |
moveRowAtIndexPath(currentIndexPath!, toIndexPath: coveredIndexPath!) | |
currentIndexPath = coveredIndexPath | |
endUpdates() | |
} | |
} | |
} | |
private func updateSnapshot() { | |
if relativeLocation.y >= 0 && relativeLocation.y <= currentContentSize.height + 50 { | |
snapshotForReorderringCell?.center = CGPointMake(center.x, relativeLocation.y) | |
} | |
} | |
private func setupSnapshot() { | |
UIGraphicsBeginImageContextWithOptions(cellForCoveredIndexPath!.bounds.size, false, 0) | |
cellForCoveredIndexPath!.layer.renderInContext(UIGraphicsGetCurrentContext()!) | |
snapshotForReorderringCell = UIImageView(image: UIGraphicsGetImageFromCurrentImageContext()) | |
addSubview(snapshotForReorderringCell!) | |
let rect = rectForRowAtIndexPath(coveredIndexPath!) | |
snapshotForReorderringCell?.frame = CGRectOffset(snapshotForReorderringCell!.bounds, rect.origin.x, rect.origin.y) | |
snapshotForReorderringCell?.layer.masksToBounds = false | |
snapshotForReorderringCell?.layer.shadowColor = UIColor.blackColor().CGColor | |
snapshotForReorderringCell?.layer.shadowOffset = CGSizeMake(0, 0) | |
snapshotForReorderringCell?.layer.shadowRadius = 4 | |
snapshotForReorderringCell?.layer.shadowOpacity = 0.7 | |
snapshotForReorderringCell?.layer.opacity = 1 | |
UIView.animateWithDuration(0.25, animations: { | |
self.snapshotForReorderringCell?.transform = CGAffineTransformMakeScale(1.1, 1.1) | |
self.snapshotForReorderringCell?.center = CGPointMake(self.center.x, self.relativeLocation.y) | |
}) | |
UIGraphicsEndImageContext() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment