i got the code but its not working, let me know if im did anything wrong.
In the code i have a imageview which i created programmatically and adding the image into a uiview.Right now im unable to load the image in the "draggingView".
func longPressed(_ sender : UILongPressGestureRecognizer){
let locationInView = sender.location(in: self.view)
let cell = sender.view as? ParamterCollectionViewCell
var tImageView: UIImageView!
tImageView.frame = CGRect(x:0, y:0, width: 30 , height:30)
tImageView.image = parameterImageArray[0]
switch sender.state {
case .began:
// create an NSData object from myView
let archive = NSKeyedArchiver.archivedData(withRootObject: tImageView!)
// create a clone by unarchiving the NSData
draggingView = NSKeyedUnarchiver.unarchiveObject(with: archive) as? UIView
self.view.addSubview(draggingView!)
draggingView!.center = locationInView;
self.view.bringSubview(toFront: draggingView!)
break
case .changed:
if (contentView.frame.contains(locationInView)) {
draggingView?.center = locationInView;
}
break
case .ended:
break
default:
break
}
}
}
Give this a try... Very similar in idea to your code, but I think it might be a bit better of an approach:
func longPressed(_ sender : UILongPressGestureRecognizer) {
let locationInView: CGPoint = sender.location(in: self.view)
let locationInCollectionView: CGPoint = sender.location(in: self.collectionView)
if sender.state == .began {
if let indexPath: IndexPath = self.collectionView.indexPathForItem(at: locationInCollectionView) {
if let cell = self.collectionView.cellForItem(at: indexPath) {
UIGraphicsBeginImageContext(cell.bounds.size)
cell.layer.render(in: UIGraphicsGetCurrentContext()!)
let cellImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.movingCellImageView = UIImageView(image: cellImage)
self.movingCellImageView?.alpha = 0.75
self.view.addSubview(self.movingCellImageView!)
self.movingCellImageView?.center = locationInView
}
}
}
// Move movingCellImageView following finger position
if sender.state == .changed {
self.movingCellImageView?.center = locationInView
}
// Remove movingCellImageView when ended
if sender.state == .ended {
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut, animations: {
self.movingCellImageView?.alpha = 0
}, completion: { (success: Bool) in
self.movingCellImageView?.removeFromSuperview()
self.movingCellImageView = nil
})
}
}
References:
Tim Puckett - https://adoptioncurve.net/archives/2014/07/creating-a-draggable-uicollectionviewcell/
Swift implementation by Chan Jing Hong
http://eatliftswift.weebly.com/technical/creating-a-draggable-uicollectionviewcell
Related
I created a UIView and a UIImageView which is inside the UIView as a subview, then I added a pan gesture to the UIImageView to slide within the UIView, the image slides now but the problem I have now is when the slider gets to the end of the view if movex > xMax, I want to print this just once print("SWIPPERD movex"). The current code I have there continues to print print("SWIPPERD movex") as long as the user does not remove his/her hand from the UIImageView which is used to slide
private func swipeFunc() {
let swipeGesture = UIPanGestureRecognizer(target: self, action: #selector(acknowledgeSwiped(sender:)))
sliderImage.addGestureRecognizer(swipeGesture)
swipeGesture.delegate = self as? UIGestureRecognizerDelegate
}
#objc func acknowledgeSwiped(sender: UIPanGestureRecognizer) {
if let sliderView = sender.view {
let translation = sender.translation(in: self.baseView) //self.sliderView
switch sender.state {
case .began:
startingFrame = sliderImage.frame
viewCenter = baseView.center
fallthrough
case .changed:
if let startFrame = startingFrame {
var movex = translation.x
if movex < -startFrame.origin.x {
movex = -startFrame.origin.x
print("SWIPPERD minmax")
}
let xMax = self.baseView.frame.width - startFrame.origin.x - startFrame.width - 15 //self.sliderView
if movex > xMax {
movex = xMax
print("SWIPPERD movex")
}
var movey = translation.y
if movey < -startFrame.origin.y { movey = -startFrame.origin.y }
let yMax = self.baseView.frame.height - startFrame.origin.y - startFrame.height //self.sliderView
if movey > yMax {
movey = yMax
// print("SWIPPERD min")
}
sliderView.transform = CGAffineTransform(translationX: movex, y: movey)
}
default: // .ended and others:
UIView.animate(withDuration: 0.1, animations: {
sliderView.transform = CGAffineTransform.identity
})
}
}
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return sliderImage.frame.contains(point)
}
You may want to use the .ended state instead of .changed state, based on your requirements. And you've mentioned you want to get the right direction only. You could try below to determine if the swipe came from right to left, or vice-versa, change as you wish:
let velocity = sender.velocity(in: sender.view)
let rightToLeftSwipe = velocity.x < 0
I have a node object in 3d view and i need to drag that object,
So far i have tried from here : Placing, Dragging and Removing SCNNodes in ARKit
and converted in swift
#objc func handleDragGesture(_ gestureRecognizer: UIGestureRecognizer) {
let tapPoint = gestureRecognizer.location(in: self.sceneView)
switch gestureRecognizer.state {
case .began:
print("Object began to move")
let hitResults = self.sceneView.hitTest(tapPoint, options: nil)
if hitResults.isEmpty { return }
let hitResult = hitResults.first
if let node = hitResult?.node.parent?.parent?.parent {
self.photoNode = node
}
case .changed:
print("Moving object position changed")
if let _ = self.photoNode {
let hitResults = self.sceneView.hitTest(tapPoint, types: .featurePoint)
let hitResult = hitResults.last
if let transform = hitResult?.worldTransform {
let matrix = SCNMatrix4FromMat4(transform)
let vector = SCNVector3Make(matrix.m41, matrix.m42, matrix.m43)
self.photoNode?.position = vector
}
}
case .ended:
print("Done moving object")
default:
break
}
}
but it is not working properly. what is the correct way to do?
You can do this using panGestureRecongniser... see basic swift Playground code for handling a SCNNode.
import UIKit
import ARKit
import SceneKit
import PlaygroundSupport
public var textNode : SCNNode?
// Main ARKIT ViewController
class ViewController : UIViewController, ARSCNViewDelegate, ARSessionDelegate {
var textNode: SCNNode!
var counter = 0
#IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
// set the views delegate
sceneView.delegate = self as! ARSCNViewDelegate
// show statistics such as fps and timing information
sceneView.showsStatistics = true
// Create a new scene
sceneView.scene.rootNode
// Add ligthing
sceneView.autoenablesDefaultLighting = true
let text = SCNText(string: "Drag Me with Pan Gesture!", extrusionDepth: 1)
// create material
let material = SCNMaterial()
material.diffuse.contents = UIColor.green
text.materials = [material]
//Create Node object
textNode = SCNNode()
textNode.name = "textNode"
textNode.scale = SCNVector3(x:0.004,y:0.004,z:0.004)
textNode.geometry = text
textNode.position = SCNVector3(x: 0, y:0.02, z: -1)
// add new node to root node
self.sceneView.scene.rootNode.addChildNode(textNode)
// Add pan gesture for dragging the textNode about
sceneView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(panGesture(_:))))
}
override func loadView() {
sceneView = ARSCNView(frame:CGRect(x: 0.0, y: 0.0, width: 500.0, height: 600.0))
// Set the view's delegate
sceneView.delegate = self
let config = ARWorldTrackingConfiguration()
config.planeDetection = .horizontal
// Now we'll get messages when planes were detected...
sceneView.session.delegate = self
self.view = sceneView
sceneView.session.run(config)
}
#objc func panGesture(_ gesture: UIPanGestureRecognizer) {
gesture.minimumNumberOfTouches = 1
let results = self.sceneView.hitTest(gesture.location(in: gesture.view), types: ARHitTestResult.ResultType.featurePoint)
guard let result: ARHitTestResult = results.first else {
return
}
let position = SCNVector3Make(result.worldTransform.columns.3.x, result.worldTransform.columns.3.y, result.worldTransform.columns.3.z)
textNode.position = position
}
}
PlaygroundPage.current.liveView = ViewController()
PlaygroundPage.current.needsIndefiniteExecution = true
EDIT:
The above drag function only worked if you had 1 object in the view, so it was not really necessary to hit the node to start dragging. It will just drag from where ever you tapped on the screen. If you have multiple objects in the view, and you want to drag nodes independently. You could change the panGesture function to the following, detect each node tapped first:
// drags nodes independently
#objc func panGesture(_ gesture: UIPanGestureRecognizer) {
gesture.minimumNumberOfTouches = 1
let results = self.sceneView.hitTest(gesture.location(in: gesture.view), types: ARHitTestResult.ResultType.featurePoint)
guard let result: ARHitTestResult = results.first else {
return
}
let hits = self.sceneView.hitTest(gesture.location(in: gesture.view), options: nil)
if let tappedNode = hits.first?.node {
let position = SCNVector3Make(result.worldTransform.columns.3.x, result.worldTransform.columns.3.y, result.worldTransform.columns.3.z)
tappedNode.position = position
}
}
REF: https://stackoverflow.com/a/48220751/5589073
This code works for me
private func drag(sender: UIPanGestureRecognizer) {
switch sender.state {
case .began:
let location = sender.location(in: self.sceneView)
guard let hitNodeResult = self.sceneView.hitTest(location,
options: nil).first else { return }
self.PCoordx = hitNodeResult.worldCoordinates.x
self.PCoordy = hitNodeResult.worldCoordinates.y
self.PCoordz = hitNodeResult.worldCoordinates.z
case .changed:
// when you start to pan in screen with your finger
// hittest gives new coordinates of touched location in sceneView
// coord-pcoord gives distance to move or distance paned in sceneview
let hitNode = sceneView.hitTest(sender.location(in: sceneView), options: nil)
if let coordx = hitNode.first?.worldCoordinates.x,
let coordy = hitNode.first?.worldCoordinates.y,
let coordz = hitNode.first?.worldCoordinates.z {
let action = SCNAction.moveBy(x: CGFloat(coordx - self.PCoordx),
y: CGFloat(coordy - self.PCoordy),
z: CGFloat(coordz - self.PCoordz),
duration: 0.0)
self.photoNode.runAction(action)
self.PCoordx = coordx
self.PCoordy = coordy
self.PCoordz = coordz
}
sender.setTranslation(CGPoint.zero, in: self.sceneView)
case .ended:
self.PCoordx = 0.0
self.PCoordy = 0.0
self.PCoordz = 0.0
default:
break
}
}
I'm stuck with this problem:
I'm using this code to swap two cells of the same table view. I want to ask you how I should edit this code to swap two cells of two different table view in the same controller? I think that there isn't much to edit but I can't do on my own... thank you all.
#objc func onLongPressGestureDone(sender: UILongPressGestureRecognizer) {
let locationInView = sender.location(in: tableViewDone)
let indexPath = tableViewDone.indexPathForRow(at: locationInView)
if sender.state == .began {
if indexPath != nil {
initialIndexPathDone = indexPath
let cell = tableViewDone.cellForRow(at: indexPath!)
cellSnapshotDone = snapshotOfCellDone(inputView: cell!)
var center = cell?.center
cellSnapshotDone?.center = center!
cellSnapshotDone?.alpha = 0.0
tableViewDone.addSubview(cellSnapshotDone!)
UIView.animate(withDuration: 0.25, animations: { () -> Void in
center?.y = locationInView.y
self.cellSnapshotDone?.center = center!
self.cellSnapshotDone?.transform = (self.cellSnapshotDone?.transform.scaledBy(x: 1.05, y: 1.05))!
self.cellSnapshotDone?.alpha = 0.99
cell?.alpha = 0.0
}, completion: { (finished) -> Void in
if finished {
cell?.isHidden = true
}
})
}
} else if sender.state == .changed {
var center = cellSnapshotDone?.center
center?.y = locationInView.y
cellSnapshotDone?.center = center!
if ((indexPath != nil) && (indexPath != initialIndexPathDone)) {
progetto.done.swapAt(indexPath!.row, initialIndexPathDone!.row)
tableViewDone.moveRow(at: initialIndexPathDone!, to: indexPath!)
initialIndexPathDone = indexPath
}
} else if sender.state == .ended {
let cell = tableViewDone.cellForRow(at: initialIndexPathDone!)
cell?.isHidden = false
cell?.alpha = 0.0
UIView.animate(withDuration: 0.25, animations: { () -> Void in
self.cellSnapshotDone?.center = (cell?.center)!
self.cellSnapshotDone?.transform = CGAffineTransform.identity
self.cellSnapshotDone?.alpha = 0.0
cell?.alpha = 1.0
}, completion: { (finished) -> Void in
if finished {
self.initialIndexPathDone = nil
self.cellSnapshotDone?.removeFromSuperview()
self.cellSnapshotDone = nil
}
})
}
}
func snapshotOfCellDone(inputView: UIView) -> UIView {
UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, false, 0.0)
inputView.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let cellSnapshotDone = UIImageView(image: image)
cellSnapshotDone.layer.masksToBounds = false
cellSnapshotDone.layer.cornerRadius = 0.0
cellSnapshotDone.layer.shadowOffset = CGSize(width: -5.0, height: 0.0)
cellSnapshotDone.layer.shadowRadius = 5.0
cellSnapshotDone.layer.shadowOpacity = 0.4
return cellSnapshotDone
}
You can do this easier with the moveRow(at:to:) function:
tableView.beginUpdates()
tableView.moveRow(at: firstIndexPath, to: secondIndexPath)
tableView.moveRow(at: secondIndexPath, to: firstIndexPath)
tableView.endUpdates()
I have a setting that allows the user to turn on and off the UILongPressGesture on a UITableView cell. I have a simple bool that I am checking if I should add or remove the gesture.
// Gesture Recognizer
let longPress: UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressDetected(_:)))
if(options?.alphaOrder == false){
self.tableView.addGestureRecognizer(longPress)
}
else{
self.tableView.removeGestureRecognizer(longPress)
}
Long press method
func longPressDetected(_ sender: Any){
let longPress:UILongPressGestureRecognizer = sender as! UILongPressGestureRecognizer
let state:UIGestureRecognizerState = longPress.state
let location:CGPoint = longPress.location(in: self.tableView) as CGPoint
let indexPath = self.tableView.indexPathForRow(at: location)
var snapshot:UIView!
var sourceIndexPath:NSIndexPath?
if (holderView) != nil{
snapshot = holderView
}
if (beginningIndexPath) != nil{
sourceIndexPath = beginningIndexPath
}
switch(state){
case UIGestureRecognizerState.began:
if let test = indexPath{
sourceIndexPath = indexPath! as NSIndexPath
beginningIndexPath = sourceIndexPath
let cell:UITableViewCell = self.tableView.cellForRow(at: test)!
snapshot = self.customSnapShotFrom(view: cell)
holderView = snapshot
var center:CGPoint = cell.center
snapshot.center = center
snapshot.alpha = 0.0
self.tableView.addSubview(snapshot)
UIView.animate(withDuration: 0.25, animations:{
center.y = location.y
snapshot.center = center
snapshot.transform = CGAffineTransform(scaleX: 1.05, y: 1.05)
snapshot.alpha = 0.98
cell.alpha = 0.0
},completion:{ _ in
cell.isHidden = true})
}
break
case UIGestureRecognizerState.changed:
// print("changed")
var center:CGPoint = snapshot.center
center.y = location.y
snapshot.center = center
if let test = indexPath {
let bool = indexPath!.elementsEqual(beginningIndexPath as IndexPath)
if !bool {
self.updatePriorities(draggedOverIndexPath: test as NSIndexPath)
}
}
default:
// print("finished")
let cell:UITableViewCell = self.tableView.cellForRow(at: sourceIndexPath as! IndexPath)!
cell.isHidden = false
cell.alpha = 0.0
UIView.animate(withDuration: 0.25, animations: {
snapshot.center = cell.center;
snapshot.transform = .identity
snapshot.alpha = 0.0;
cell.alpha = 1.0;
}, completion: { _ in
sourceIndexPath = nil
snapshot.removeFromSuperview()
snapshot = nil
})
}
}
func customSnapShotFrom(view:UIView) -> UIView {
let snapshot:UIView = view.snapshotView(afterScreenUpdates: true)!
snapshot.layer.masksToBounds = false;
snapshot.layer.cornerRadius = 0.0;
snapshot.layer.shadowOffset = CGSize(width: 2.0, height: 2.0);
snapshot.layer.shadowRadius = 4.0;
snapshot.layer.shadowOpacity = 1.0;
return snapshot;
}
func updatePriorities(draggedOverIndexPath:NSIndexPath) {
let firstItem: Person = fetchedResultsController.object(at: beginningIndexPath as IndexPath)
let secondItem: Person = fetchedResultsController.object(at: draggedOverIndexPath as IndexPath)
firstItem.priority = Int32(draggedOverIndexPath.row)
secondItem.priority = Int32(beginningIndexPath.row)
beginningIndexPath = draggedOverIndexPath
coreDataStack.saveContext()
}
This works,but I noticed that even when the gesture is removed, I'm still able to long press on the table cell and move it. It doesn't rearrange the table cell, but I would like to stop this from happening.
Here's some semi-psuedo code that shows what I would recommend. This is better, because because every time the viewWillAppear the GestureRecognizer is updated.
class CustomViewController: UIViewController {
var longPress: UILongPressGestureRecognizer
required init() {
super.init(nibName: nil, bundle: nil)
longPress = UILongPressGestureRecognizer(target: self, action: #selector(longPressDetected(_:)))
self.tableView.addGestureRecognizer(longPress)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
longPress.isEnabled = (options?.alphaOrder == false)
}
func longPressDetected(_ sender: Any) {
...existing method...
}
}
I am working on an app with a tableView in a ViewController. In this tableView I have sections and rows and I want my user to be able to long press, drag and drop cells in between sections with UILongPressGestureRecognizer.
With the following code my gesture recognizer works but at the moment that I try to drag the cell and drop it anywhere in the tableView, my app crashes. The crashes occur in two different lines. When the cell is long pressed and drag inside a section it crashes on this line: tableView.moveRowAtIndexPath(Path.initialIndexPath!, toIndexPath: indexPath!)
if I try to drag out of a section it crashes on this line: swap(&Categories.categories[indexPath!.row], &Categories.categories[Path.initialIndexPath!.row])
(the arrays with the data for each section and rows are in a separate class, named Categories. The array with sections is called "sections", and the one with rows is named "categories.")
I would appreciate any help with finding out why it fails and how can I fix it.
func setupLongPress() {
let longpress = UILongPressGestureRecognizer(target: self, action: "longPressGestureRecognized:")
tableView.addGestureRecognizer(longpress)
}
func longPressGestureRecognized(gestureRecognizer: UIGestureRecognizer) {
let longPress = gestureRecognizer as! UILongPressGestureRecognizer
let state = longPress.state
let locationInView = longPress.locationInView(tableView)
let indexPath = tableView.indexPathForRowAtPoint(locationInView)
struct My {
static var cellSnapshot : UIView? = nil
}
struct Path {
static var initialIndexPath : NSIndexPath? = nil
}
switch state {
case UIGestureRecognizerState.Began:
if indexPath != nil {
Path.initialIndexPath = indexPath
let cell = tableView.cellForRowAtIndexPath(indexPath!) as UITableViewCell!
My.cellSnapshot = snapshopOfCell(cell)
var center = cell.center
My.cellSnapshot!.center = center
My.cellSnapshot!.alpha = 0.0
tableView.addSubview(My.cellSnapshot!)
UIView.animateWithDuration(0.25, animations: { () -> Void in
center.y = locationInView.y
My.cellSnapshot!.center = center
My.cellSnapshot!.transform = CGAffineTransformMakeScale(1.05, 1.05)
My.cellSnapshot!.alpha = 0.98
cell.alpha = 0.0
}, completion: { (finished) -> Void in
if finished {
cell.hidden = true
}
})
}
case UIGestureRecognizerState.Changed:
var center = My.cellSnapshot!.center
center.y = locationInView.y
My.cellSnapshot!.center = center
if ((indexPath != nil) && (indexPath != Path.initialIndexPath)) {
swap(&Categories.categories[indexPath!.row], &Categories.categories[Path.initialIndexPath!.row])
tableView.moveRowAtIndexPath(Path.initialIndexPath!, toIndexPath: indexPath!)
Path.initialIndexPath = indexPath
}
default:
let cell = tableView.cellForRowAtIndexPath(Path.initialIndexPath!) as UITableViewCell!
cell.hidden = false
cell.alpha = 0.0
UIView.animateWithDuration(0.5, animations: { () -> Void in
My.cellSnapshot!.center = cell.center
My.cellSnapshot!.transform = CGAffineTransformIdentity
My.cellSnapshot!.alpha = 0.0
cell.alpha = 1.0
}, completion: { (finished) -> Void in
if finished {
Path.initialIndexPath = nil
My.cellSnapshot!.removeFromSuperview()
My.cellSnapshot = nil
}
})
}
}
func snapshopOfCell(inputView: UIView) -> UIView {
UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, false, 0.0)
inputView.layer.renderInContext(UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext() as UIImage
UIGraphicsEndImageContext()
let cellSnapshot : UIView = UIImageView(image: image)
cellSnapshot.layer.masksToBounds = false
cellSnapshot.layer.cornerRadius = 0.0
cellSnapshot.layer.shadowOffset = CGSizeMake(-5.0, 0.0)
cellSnapshot.layer.shadowRadius = 1.0
cellSnapshot.layer.shadowOpacity = 0.2
return cellSnapshot
}
Thank you!