-
Notifications
You must be signed in to change notification settings - Fork 24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement LPR For TableView Sections? #24
Comments
hmm… I’m not sure I quite understand the effect you’re trying to achieve. Would you mind sharing a concrete example (either in code or visually)? |
Thanks for the reply! Yes you can view my effect here: https://imgur.com/a/76VRx as well as the changes I made to LPRTableView in order to make it select the header for each section and move the sections by dragging the header. My problem is I cannot get it to scroll up or down when I am dragging the header in each direction as you can do with rows. In my case you would have to scroll the sections down instead of the rows I suppose is where I am struggling. //
// LPRTableView.swift
// LPRTableView
//
// Objective-C code Copyright (c) 2013 Ben Vogelzang. All rights reserved.
// Swift adaptation Copyright (c) 2014 Nicolas Gomollon. All rights reserved.
//
import Foundation
import QuartzCore
import UIKit
/** The delegate of a LPRTableView object can adopt the LPRTableViewDelegate protocol. Optional methods of the protocol allow the delegate to modify a cell visually before dragging occurs, or to be notified when a cell is about to be dragged or about to be dropped. */
@objc
public protocol LPRTableViewDelegate: NSObjectProtocol {
/** Provides the delegate a chance to modify the cell visually before dragging occurs. Defaults to using the cell as-is if not implemented. */
@objc optional func tableView(_ tableView: UITableView, draggingCell cell: UITableViewHeaderFooterView, at indexPath: IndexPath) -> UITableViewHeaderFooterView
/** Called within an animation block when the dragging view is about to show. */
@objc optional func tableView(_ tableView: UITableView, showDraggingView view: UIView, at indexPath: IndexPath)
/** Called within an animation block when the dragging view is about to hide. */
@objc optional func tableView(_ tableView: UITableView, hideDraggingView view: UIView, at indexPath: IndexPath)
/** Called when the dragging gesture's vertical location changes. */
@objc optional func tableView(_ tableView: UITableView, draggingGestureChanged gesture: UILongPressGestureRecognizer)
}
open class LPRTableView: UITableView {
/** The object that acts as the delegate of the receiving table view. */
weak open var longPressReorderDelegate: LPRTableViewDelegate?
fileprivate var longPressGestureRecognizer: UILongPressGestureRecognizer!
fileprivate var initialIndexPath: IndexPath?
fileprivate var currentLocationIndexPath: IndexPath?
fileprivate var draggingView: UIView?
fileprivate var scrollRate = 0.0
fileprivate var scrollDisplayLink: CADisplayLink?
fileprivate var feedbackGenerator: AnyObject?
fileprivate var previousGestureVerticalPosition: CGFloat?
/** A Bool property that indicates whether long press to reorder is enabled. */
open var longPressReorderEnabled: Bool {
get {
return longPressGestureRecognizer.isEnabled
}
set {
longPressGestureRecognizer.isEnabled = newValue
}
}
/**
The minimum period a finger must press on a cell for the reordering to begin.
The time interval is in seconds. The default duration is is 0.5 seconds.
*/
open var minimumPressDuration: CFTimeInterval {
get {
return longPressGestureRecognizer.minimumPressDuration
}
set {
longPressGestureRecognizer.minimumPressDuration = newValue
}
}
public convenience init() {
self.init(frame: CGRect.zero)
}
public override init(frame: CGRect, style: UITableViewStyle) {
super.init(frame: frame, style: style)
initialize()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
fileprivate func initialize() {
longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(LPRTableView._longPress(_:)))
addGestureRecognizer(longPressGestureRecognizer)
self.estimatedRowHeight = 0
self.estimatedSectionHeaderHeight = 0
self.estimatedSectionFooterHeight = 0
}
}
extension LPRTableView {
fileprivate func canMoveRowAt(indexPath: IndexPath) -> Bool {
return (dataSource?.responds(to: #selector(UITableViewDataSource.tableView(_:canMoveRowAt:))) == false) || (dataSource?.tableView?(self, canMoveRowAt: indexPath) == true)
}
fileprivate func cancelGesture() {
longPressGestureRecognizer.isEnabled = false
longPressGestureRecognizer.isEnabled = true
}
@objc internal func _longPress(_ gesture: UILongPressGestureRecognizer) {
let location = gesture.location(in: self)
let indexPath = indexPathForRow(at: location)
let sections = numberOfSections
var rows = 0
for i in 0..<sections {
rows += numberOfRows(inSection: i)
}
// Get out of here if the long press was not on a valid row or our table is empty
// or the dataSource tableView:canMoveRowAtIndexPath: doesn't allow moving the row.
if (rows == 0) ||
((gesture.state == UIGestureRecognizerState.began) && (indexPath == nil)) ||
((gesture.state == UIGestureRecognizerState.ended) && (currentLocationIndexPath == nil)) ||
((gesture.state == UIGestureRecognizerState.began) && !canMoveRowAt(indexPath: indexPath!)) {
cancelGesture()
return
}
// Started.
if gesture.state == .began {
self.hapticFeedbackSetup()
self.hapticFeedbackSelectionChanged()
self.previousGestureVerticalPosition = location.y
if let indexPath = indexPath {
if var cell = headerView(forSection: indexPath.section) {
// Create the view that will be dragged around the screen.
if (draggingView == nil) {
if let draggingCell = longPressReorderDelegate?.tableView?(self, draggingCell: cell, at: indexPath) {
cell = draggingCell
}
// Make an image from the pressed table view cell.
UIGraphicsBeginImageContextWithOptions(cell.bounds.size, false, 0.0)
cell.layer.render(in: UIGraphicsGetCurrentContext()!)
let cellImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
draggingView = UIImageView(image: cellImage)
if let draggingView = draggingView {
addSubview(draggingView)
let rect1 = rect(forSection: indexPath.section)
draggingView.frame = draggingView.bounds.offsetBy(dx: rect1.origin.x, dy: rect1.origin.y)
UIView.beginAnimations("LongPressReorder-ShowDraggingView", context: nil)
longPressReorderDelegate?.tableView?(self, showDraggingView: draggingView, at: indexPath)
UIView.commitAnimations()
// Add drop shadow to image and lower opacity.
draggingView.layer.masksToBounds = false
draggingView.layer.shadowColor = UIColor.black.cgColor
draggingView.layer.shadowOffset = CGSize.zero
draggingView.layer.shadowRadius = 4.0
draggingView.layer.shadowOpacity = 0.7
draggingView.layer.opacity = 0.85
// Zoom image towards user.
UIView.beginAnimations("LongPressReorder-Zoom", context: nil)
draggingView.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)
draggingView.center = CGPoint(x: center.x, y: newYCenter(for: draggingView, with: location))
UIView.commitAnimations()
}
}
cell.isHidden = true
currentLocationIndexPath = indexPath
initialIndexPath = indexPath
// Enable scrolling for cell.
scrollDisplayLink = CADisplayLink(target: self, selector: #selector(LPRTableView._scrollTableWithCell(_:)))
scrollDisplayLink?.add(to: RunLoop.main, forMode: RunLoopMode.defaultRunLoopMode)
}
}
}
// Dragging.
else if gesture.state == .changed {
if let draggingView = draggingView {
// Update position of the drag view
draggingView.center = CGPoint(x: center.x, y: newYCenter(for: draggingView, with: location))
if let previousGestureVerticalPosition = self.previousGestureVerticalPosition {
if location.y != previousGestureVerticalPosition {
longPressReorderDelegate?.tableView?(self, draggingGestureChanged: gesture)
self.previousGestureVerticalPosition = location.y
}
} else {
longPressReorderDelegate?.tableView?(self, draggingGestureChanged: gesture)
self.previousGestureVerticalPosition = location.y
}
}
let inset: UIEdgeInsets
if #available(iOS 11.0, *) {
inset = adjustedContentInset
} else {
inset = contentInset
}
var rect = bounds
// Adjust rect for content inset, as we will use it below for calculating scroll zones.
rect.size.height -= inset.top
updateCurrentLocation(gesture)
// Tell us if we should scroll, and in which direction.
let scrollZoneHeight = rect.size.height / 6.0
let bottomScrollBeginning = contentOffset.y + inset.top + rect.size.height - scrollZoneHeight
let topScrollBeginning = contentOffset.y + inset.top + scrollZoneHeight
// We're in the bottom zone.
print("=== Location: \(location.y)")
if location.y >= bottomScrollBeginning {
scrollRate = Double(location.y - bottomScrollBeginning) / Double(scrollZoneHeight)
}
// We're in the top zone.
else if location.y <= topScrollBeginning {
scrollRate = Double(location.y - topScrollBeginning) / Double(scrollZoneHeight)
}
else {
scrollRate = 0.0
}
}
// Dropped.
else if (gesture.state == .ended) || (gesture.state == .cancelled) || (gesture.state == .failed) {
// Remove previously cached Gesture location
self.previousGestureVerticalPosition = nil
// Remove scrolling CADisplayLink.
scrollDisplayLink?.invalidate()
scrollDisplayLink = nil
scrollRate = 0.0
// Animate the drag view to the newly hovered cell.
UIView.animate(withDuration: 0.3,
animations: { [unowned self] () -> Void in
if let draggingView = self.draggingView {
if let currentLocationIndexPath = self.currentLocationIndexPath {
UIView.beginAnimations("LongPressReorder-HideDraggingView", context: nil)
self.longPressReorderDelegate?.tableView?(self, hideDraggingView: draggingView, at: currentLocationIndexPath)
UIView.commitAnimations()
let rect = self.rectForRow(at: currentLocationIndexPath)
draggingView.transform = CGAffineTransform.identity
draggingView.frame = draggingView.bounds.offsetBy(dx: rect.origin.x, dy: rect.origin.y)
}
}
},
completion: { [unowned self] (Bool) -> Void in
if let draggingView = self.draggingView {
draggingView.removeFromSuperview()
}
// Reload the rows that were affected just to be safe.
self.reloadData()
self.currentLocationIndexPath = nil
self.draggingView = nil
self.hapticFeedbackSelectionChanged()
self.hapticFeedbackFinalize()
})
}
}
fileprivate func updateCurrentLocation(_ gesture: UILongPressGestureRecognizer) {
let location = gesture.location(in: self)
guard var indexPath = indexPathForRow(at: location) else { return }
if let iIndexPath = initialIndexPath,
let ip = delegate?.tableView?(self, targetIndexPathForMoveFromRowAt: iIndexPath, toProposedIndexPath: indexPath) {
indexPath = ip
}
guard let clIndexPath = currentLocationIndexPath else { return }
let oldHeight = rectForRow(at: clIndexPath).size.height
let newHeight = rectForRow(at: indexPath).size.height
if let cell = headerView(forSection: clIndexPath.section) {
cell.isHidden = true
}
if ((indexPath != clIndexPath) && (gesture.location(in: headerView(forSection: indexPath.section)).y > (newHeight - oldHeight))) && canMoveRowAt(indexPath: indexPath) {
beginUpdates()
moveSection(clIndexPath.section, toSection: indexPath.section)
dataSource?.tableView?(self, moveRowAt: clIndexPath, to: indexPath)
currentLocationIndexPath = indexPath
endUpdates()
self.hapticFeedbackSelectionChanged()
}
}
@objc internal func _scrollTableWithCell(_ sender: CADisplayLink) {
guard let gesture = longPressGestureRecognizer else { return }
let location = gesture.location(in: self)
guard !(location.y.isNaN || location.x.isNaN) else { return } // Explicitly check for out-of-bound touch.
let yOffset = Double(contentOffset.y) + scrollRate * 10.0
var newOffset = CGPoint(x: contentOffset.x, y: CGFloat(yOffset))
let inset: UIEdgeInsets
if #available(iOS 11.0, *) {
inset = adjustedContentInset
} else {
inset = contentInset
}
if newOffset.y < -inset.top {
newOffset.y = -inset.top
} else if (contentSize.height + inset.bottom) < frame.size.height {
newOffset = contentOffset
} else if newOffset.y > ((contentSize.height + inset.bottom) - frame.size.height) {
newOffset.y = (contentSize.height + inset.bottom) - frame.size.height
}
contentOffset = newOffset
if let draggingView = draggingView {
draggingView.center = CGPoint(x: center.x, y: newYCenter(for: draggingView, with: location))
}
updateCurrentLocation(gesture)
}
fileprivate func newYCenter(for draggingView: UIView, with location: CGPoint) -> CGFloat {
let cellCenter = draggingView.frame.height / 2
let bottomBound = contentSize.height - cellCenter
if location.y < cellCenter {
return cellCenter
} else if location.y > bottomBound {
return bottomBound
}
return location.y
}
}
extension LPRTableView {
fileprivate func hapticFeedbackSetup() {
guard #available(iOS 10.0, *) else { return }
let feedbackGenerator = UISelectionFeedbackGenerator()
feedbackGenerator.prepare()
self.feedbackGenerator = feedbackGenerator
}
fileprivate func hapticFeedbackSelectionChanged() {
guard #available(iOS 10.0, *),
let feedbackGenerator = self.feedbackGenerator as? UISelectionFeedbackGenerator else { return }
feedbackGenerator.selectionChanged()
feedbackGenerator.prepare()
}
fileprivate func hapticFeedbackFinalize() {
guard #available(iOS 10.0, *) else { return }
self.feedbackGenerator = nil
}
} |
Is it possible to implement this framework for re-ordering sections by getting indexPath from a selected point. I can manage to do it on my own without this framework but I am having difficulties with the scrolling so I was wondering if I could use this instead.
Even with this framework I have got it working to move the sections but I cannot implement the scrolling again.
The text was updated successfully, but these errors were encountered: