การลากและวางขั้นพื้นฐานใน iOS


93

ฉันอยากได้มุมมองที่มียานพาหนะขับไปรอบ ๆ ที่ผู้ใช้สามารถลากและวางได้ คุณคิดว่ากลยุทธ์ขนาดใหญ่ที่ดีที่สุดในการทำสิ่งนี้คืออะไร? จะเป็นการดีที่สุดที่จะรับเหตุการณ์การสัมผัสจากมุมมองที่เป็นตัวแทนของยานพาหนะหรือจากมุมมองที่กว้างขึ้น? มีกระบวนทัศน์ง่ายๆที่คุณใช้ในการลากและวางที่คุณพอใจหรือไม่? อะไรคือข้อเสียของกลยุทธ์ที่แตกต่างกัน?


คำตอบ:


126

สมมติว่าคุณมีUIViewฉากที่มีภาพพื้นหลังและจำนวนมากvehiclesคุณอาจกำหนดรถใหม่แต่ละคันเป็นUIButton(UIImageView ก็น่าจะใช้ได้เช่นกัน):

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];

จากนั้นคุณสามารถเคลื่อนย้ายรถได้ทุกที่ที่คุณต้องการโดยตอบสนองต่อUIControlEventTouchDragInsideเหตุการณ์เช่น:

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

มันง่ายกว่ามากสำหรับรถแต่ละคันในการจัดการการลากของตัวเองเมื่อเทียบกับการจัดการฉากโดยรวม


ดูดีและเรียบง่ายมาก! ฉันจะลองดู
ลุค

10
กลยุทธ์นี้จะทำให้การลากขาดหรือไม่หากผู้ใช้ลากปุ่มเร็วกว่าภาพเคลื่อนไหวที่อัปเดต ถ้านิ้วของพวกเขาอยู่นอกกรอบมันจะหยุดรับ imageMoved: withEvent: โทรถูกต้องหรือไม่?
ทิม

คุณจะรู้ได้อย่างไรว่ารูปภาพหยุดลาก คุณจะรู้ได้อย่างไรว่านิ้วขยับออก?
ymutlu

โอ้โห - เป็นไปได้ไหมที่จะชดเชยการลาก "แฮนเดิล" สำหรับรูปภาพหรือปุ่มเพื่อให้ลากด้วยมุมขวาบนหรือมุมซ้ายบน
LJ Wilson

เมื่อฉันพยายามรับข้อยกเว้น - [drag_move_textViewController imageTouch: withEvent:]: ตัวเลือกที่ไม่รู้จักถูกส่งไปยังอินสแตนซ์ 0x4b4b1c0
iosDev

34

นอกจากคำตอบของ ohho แล้วฉันได้ลองใช้งานที่คล้ายกัน แต่ไม่มีปัญหาในการลาก "เร็ว" และตั้งศูนย์กลางเพื่อลาก

การเริ่มต้นปุ่มคือ ...

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];

และวิธีการใช้งาน:

- (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;
}

ฉันไม่ได้บอกว่านี่เป็นทางออกที่สมบูรณ์แบบสำหรับคำตอบ อย่างไรก็ตามฉันพบว่านี่เป็นวิธีที่ง่ายที่สุดสำหรับการลาก


2
ในโค้ดตัวอย่างของคุณคุณกำหนด UIControl * control = ผู้ส่งหลังจากใช้งานแล้ว - ไม่ควรกำหนดไว้ก่อนหน้านี้หรือไม่?
Peter Johnson

@PeterJohnson: อันที่จริงมันไม่สำคัญในกรณีนี้ ไม่มีการใช้ตัวแปรนี้ก่อนหน้านี้ตั้งแต่บรรทัดที่คุณกล่าวถึง :)
JakubKnejzlik

1
แล้ว CGPoint pPrev = [t previousLocationInView: control]; CGPoint p = [t locationInView: control]; สองบรรทัดก่อนหน้านี้ทั้งสองดูเหมือนจะอ้างถึง "การควบคุม"?
Peter Johnson

โอ้เอ้ย! จะพลาดไปได้อย่างไร : -O ... ฉันแก้ไขคำตอบแล้ว
JakubKnejzlik

ฉันเพิ่มตัวสร้างรูปแบบปุ่มอินเทอร์เฟซและฉันใช้การจัดวางอัตโนมัติสำหรับการเข้าสู่ระบบบางอย่างฉันซ่อนปุ่มและแสดงอีกครั้งเมื่อฉันทำเช่นนั้นมันจะกลับไปที่เดิมวิธีการป้องกันไม่ให้สิ่งนี้
Amr Angry

2

ฉันมีกรณีที่ฉันต้องการลาก / วาง uiviews ระหว่าง uiviews อื่น ๆ หลายรายการ ในกรณีนั้นฉันพบทางออกที่ดีที่สุดในการติดตามเหตุการณ์การแพนในมุมมองขั้นสูงที่มีโซนดรอปทั้งหมดและวัตถุหน้าจั่วลากทั้งหมด เหตุผลหลักในการไม่เพิ่มผู้ฟังเหตุการณ์ลงในมุมมองที่ลากจริงคือฉันสูญเสียเหตุการณ์การแพนที่กำลังดำเนินอยู่ทันทีที่ฉันเปลี่ยน superview สำหรับมุมมองที่ลาก

แต่ฉันใช้โซลูชันการลากวางแบบทั่วไปที่สามารถใช้ในโซนการเลื่อนเดียวหรือหลายโซน

ฉันได้สร้างตัวอย่างง่ายๆที่สามารถเห็นได้ที่นี่: ลาก uiviews ที่วางระหว่าง uiviews อื่น ๆ หลายรายการ

หากคุณตัดสินใจไปทางอื่นให้ลองใช้ uicontrol แทน uibutton พวกเขาประกาศเมธอด addTarget และขยายได้ง่ายกว่ามากดังนั้นจึงให้สถานะและพฤติกรรมที่กำหนดเอง


1

จริงๆแล้วฉันจะติดตามการลากบนมุมมองยานพาหนะมากกว่ามุมมองขนาดใหญ่ - เว้นแต่จะมีเหตุผลเฉพาะที่ไม่ควรทำ

ในกรณีหนึ่งที่ฉันอนุญาตให้ผู้ใช้วางรายการโดยลากบนหน้าจอ ในกรณีนั้นฉันได้ทดลองโดยให้ทั้งมุมมองด้านบนและมุมมองเด็กลากได้ ฉันพบว่าเป็นรหัสที่สะอาดกว่าหากคุณเพิ่มมุมมอง "ลากได้" สองสามมุมมองลงใน UIView และจัดการวิธีการลาก ฉันใช้การโทรกลับไปยัง UIView หลักเพื่อตรวจสอบว่าตำแหน่งใหม่นั้นเหมาะสมหรือไม่ - ดังนั้นฉันจึงสามารถระบุด้วยภาพเคลื่อนไหวได้

การลากแทร็กมุมมองด้านบนฉันเดาว่าดี แต่นั่นทำให้ยุ่งกว่าเล็กน้อยหากคุณต้องการเพิ่มมุมมองที่ไม่สามารถลากได้ซึ่งยังคงโต้ตอบกับผู้ใช้เช่นปุ่ม


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");
}

1

คำตอบของ ohho ใช้ได้ผลดีสำหรับฉัน ในกรณีที่คุณต้องการรุ่น 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
}


0

สำหรับ Swift 4 วิธีที่ง่ายและสะดวกที่สุด 😛

ขั้นตอนที่ 1: เชื่อมต่อปุ่มของคุณจาก Storyboard เพื่อดูคอนโทรลเลอร์ และตั้งค่าข้อ จำกัด โครงร่างอัตโนมัติขั้นพื้นฐานทั้งหมด

 @IBOutlet weak var cameraButton: UIButton!

ขั้นตอนที่ 2: เพิ่ม Pan Gesture สำหรับปุ่มของคุณในviewDidLoad ()

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

ขั้นตอนที่ 3:

เพิ่ม panGestureHandler

@objc func panGestureHandler(panGesture recognizer: UIPanGestureRecognizer) {

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

    }

และตอนนี้การดำเนินการปุ่มของคุณ

@IBAction func ButtonAction(_ sender: Any) {

        print("This is button Action")
    }

ป้อนคำอธิบายภาพที่นี่

เพิ่มดูผลลัพธ์☝️

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.