Arrastrar y soltar básico en iOS

93

Quiero tener una vista en la que haya vehículos circulando y que el usuario también pueda arrastrar y soltar. ¿Cuál cree que es la mejor estrategia a gran escala para hacer esto? ¿Es mejor obtener eventos táctiles desde las vistas que representan los vehículos o desde la vista más grande? ¿Existe un paradigma simple que haya utilizado para arrastrar y soltar con el que esté satisfecho? ¿Cuáles son los inconvenientes de las diferentes estrategias?

Luke
fuente

Respuestas:

126

Suponga que tiene una UIViewescena con una imagen de fondo y muchas vehicles, puede definir cada vehículo nuevo como un UIButton(UIImageView probablemente también funcionará):

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self action:@selector(imageTouch:withEvent:) forControlEvents:UIControlEventTouchDown];
[button addTarget:self action:@selector(imageMoved:withEvent:) forControlEvents:UIControlEventTouchDragInside];
[button setImage:[UIImage imageNamed:@"vehicle.png"] forState:UIControlStateNormal];
[self.view addSubview:button];

Luego, puede mover el vehículo a donde desee, respondiendo al UIControlEventTouchDragInsideevento, por ejemplo:

- (IBAction) imageMoved:(id) sender withEvent:(UIEvent *) event
{
    CGPoint point = [[[event allTouches] anyObject] locationInView:self.view];
    UIControl *control = sender;
    control.center = point;
}

Es mucho más fácil para un vehículo individual manejar sus propios arrastres, en comparación con el manejo de la escena como un todo.

Oh ho
fuente
¡Se ve genial y muy simple! Probaré esto.
Luke
10
¿Esta estrategia causaría que el arrastre se rompa si el usuario arrastra el botón más rápido que la animación actualizada? Si su dedo saliera de la caja, dejaría de recibir las llamadas imageMoved: withEvent:, ¿correcto?
Tim
¿Cómo saber si la imagen deja de arrastrarse? ¿Cómo sabrías que el dedo se movió?
ymutlu
ohho - ¿es posible compensar el "control" de arrastre de una imagen o botón para que pueda ser arrastrado por la esquina superior derecha o superior izquierda?
LJ Wilson
cuando intento obtener una excepción - [drag_move_textViewController imageTouch: withEvent:]: selector no reconocido enviado a la instancia 0x4b4b1c0
iosDev
34

Además de la respuesta de ohho, he probado una implementación similar, pero sin problema de arrastre "rápido" y centrado para arrastrar.

La inicialización del botón es ...

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self action:@selector(imageMoved:withEvent:) forControlEvents:UIControlEventTouchDragInside];
[button addTarget:self action:@selector(imageMoved:withEvent:) forControlEvents:UIControlEventTouchDragOutside];
[button setImage:[UIImage imageNamed:@"vehicle.png"] forState:UIControlStateNormal];
[self.view addSubview:button];

e implementación del método:

- (IBAction) imageMoved:(id) sender withEvent:(UIEvent *) event
{
    UIControl *control = sender;

    UITouch *t = [[event allTouches] anyObject];
    CGPoint pPrev = [t previousLocationInView:control];
    CGPoint p = [t locationInView:control];

    CGPoint center = control.center;
    center.x += p.x - pPrev.x;
    center.y += p.y - pPrev.y;
    control.center = center;
}

No digo que esta sea la solución perfecta para la respuesta. Sin embargo, encontré que esta es la solución más fácil para arrastrar.

JakubKnejzlik
fuente
2
En su código de ejemplo, usted define UIControl * control = sender después de que se usa, ¿no debería definirse antes?
Peter Johnson
@PeterJohnson: De hecho, no importa en este caso. No hay un uso anterior de esta variable desde la línea que ha mencionado :)
JakubKnejzlik
1
¿Qué pasa con CGPoint pPrev = [t previousLocationInView: control]; CGPoint p = [t locationInView: control]; Estas dos líneas anteriores parecen referirse a "control"?
Peter Johnson
¡Oh Dios mío! ¿Cómo puedo perderme eso? : -O ... he editado la respuesta.
JakubKnejzlik
agrego el generador de interfaz de formulario de botón y uso el diseño automático para algunos inicios de sesión oculto el botón y lo muestro nuevamente cuando lo hago, así que regresa al lugar original cómo prevenir este comportamiento
Amr Angry
2

Tuve un caso en el que me gustaría arrastrar / soltar uiviews entre varias otras uiviews. En ese caso, encontré la mejor solución para rastrear los eventos de panorámica en una supervista que contiene todas las zonas de colocación y todos los objetos de hastial de arrastre. La razón principal para NO agregar los oyentes de eventos a las vistas arrastradas reales fue que perdí los eventos panorámicos en curso tan pronto como cambié la supervista para la vista arrastrada.

En su lugar, implementé una solución de arrastrar y soltar bastante genérica que podría usarse en una o varias zonas de colocación.

He creado un ejemplo simple que se puede ver aquí: Arrastre una vista de uiviews entre varias otras uiviews

Si decide hacer lo contrario, intente usar uicontrol en lugar de uibutton. Declaran los métodos addTarget y son mucho más fáciles de extender; por lo tanto, dan un estado y comportamiento personalizados.

jeyben
fuente
1

De hecho, rastrearía el arrastre en la vista del vehículo en sí, en lugar de la vista grande, a menos que haya una razón particular para no hacerlo.

En un caso en el que permito al usuario colocar elementos arrastrándolos en la pantalla. En ese caso, experimenté con que tanto la vista superior como la vista secundaria se pudieran arrastrar. Encontré que es un código más limpio si agrega algunas vistas "arrastrables" a la UIView y maneja cómo se pueden arrastrar. Usé una simple devolución de llamada al UIView principal para verificar si la nueva ubicación era adecuada o no, para poder indicar con animaciones.

Tener la pista de la vista superior arrastrando, supongo, es tan bueno, pero eso lo hace un poco más complicado si desea agregar vistas que no se pueden arrastrar y que aún interactúan con el usuario, como un botón.

Magnus
fuente
1
-(void)funcAddGesture
{ 

 // DRAG BUTTON
        UIPanGestureRecognizer *panGestureRecognizer;
        panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(dragButton:)];
        panGestureRecognizer.cancelsTouchesInView = YES;

        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.frame = CGRectMake(50, 100, 60, 60);
        [button addTarget:self action:@selector(buttontapEvent) forControlEvents:UIControlEventPrimaryActionTriggered];
        [button setImage:[UIImage imageNamed:@"button_normal.png"] forState:UIControlStateNormal];
        [button addGestureRecognizer:panGestureRecognizer];
        [self.view addSubview:button];
}


- (void)dragButton:(UIPanGestureRecognizer *)recognizer {

    UIButton *button = (UIButton *)recognizer.view;
    CGPoint translation = [recognizer translationInView:button];

    float xPosition = button.center.x;
    float yPosition = button.center.y;
    float buttonCenter = button.frame.size.height/2;

    if (xPosition < buttonCenter)
        xPosition = buttonCenter;
    else if (xPosition > self.view.frame.size.width - buttonCenter)
        xPosition = self.view.frame.size.width - buttonCenter;

    if (yPosition < buttonCenter)
        yPosition = buttonCenter;
    else if (yPosition > self.view.frame.size.height - buttonCenter)
        yPosition = self.view.frame.size.height - buttonCenter;

    button.center = CGPointMake(xPosition + translation.x, yPosition + translation.y);
    [recognizer setTranslation:CGPointZero inView:button];
}


- (void)buttontapEvent {

    NSLog(@"buttontapEvent");
}
Balaji G
fuente
1

La respuesta de ohho funcionó bien para mí. En caso de que necesite la versión rápida 2.x:

override func viewDidLoad() {

    let myButton = UIButton(type: .Custom)
    myButton.setImage(UIImage(named: "vehicle"), forState: .Normal)

    // drag support
    myButton.addTarget(self, action:#selector(imageMoved(_:event:)), forControlEvents:.TouchDragInside)
    myButton.addTarget(self, action:#selector(imageMoved(_:event:)), forControlEvents:.TouchDragOutside)
}

private func imageMoved(sender: AnyObject, event: UIEvent) {
    guard let control = sender as? UIControl else { return }
    guard let touches = event.allTouches() else { return }
    guard let touch = touches.first else { return }

    let prev = touch.previousLocationInView(control)
    let p = touch.locationInView(control)
    var center = control.center
    center.x += p.x - prev.x
    center.y += p.y - prev.y
    control.center = center
}
parag
fuente
0

Para Swift 4 Fácil y sencillo. 😛

Paso 1: conecte su botón desde el guión gráfico para ver el controlador. Y establecer todas las restricciones básicas de autodiseño

 @IBOutlet weak var cameraButton: UIButton!

Paso 2: agregue Pan Gesture para su botón en viewDidLoad ()

self.cameraButton.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGestureHandler(panGesture:))))

Paso 3:

Agregar panGestureHandler

@objc func panGestureHandler(panGesture recognizer: UIPanGestureRecognizer) {

         let location = recognizer.location(in: view)
        cameraButton.center = location

    }

Y ahora tu acción de botón

@IBAction func ButtonAction(_ sender: Any) {

        print("This is button Action")
    }

ingrese la descripción de la imagen aquí

Agregar Ver el resultado ☝️

Anup Gupta
fuente