Skip to content

Instantly share code, notes, and snippets.

@kunass2
Last active September 22, 2015 18:55
Show Gist options
  • Save kunass2/ca4b82878a56b0e6fa52 to your computer and use it in GitHub Desktop.
Save kunass2/ca4b82878a56b0e6fa52 to your computer and use it in GitHub Desktop.
//
// 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