Di iOS, bagaimana cara menyeret ke bawah untuk menutup modal?

91

Cara umum untuk menutup modal adalah dengan menggesek ke bawah - Bagaimana kita mengizinkan pengguna untuk menyeret modal ke bawah, jika cukup jauh, modal ditutup, jika tidak maka bergerak kembali ke posisi semula?

Misalnya, kami dapat menemukan ini digunakan pada tampilan foto aplikasi Twitter, atau mode "temukan" Snapchat.

Utas serupa menunjukkan bahwa kita dapat menggunakan UISwipeGestureRecognizer dan [self dischargeViewControllerAnimated ...] untuk menutup modal VC saat pengguna menggeser ke bawah. Tapi ini hanya menangani satu gesekan, tidak membiarkan pengguna menyeret modal.

foobar.dll
sumber
Lihat transisi interaktif kustom. Ini adalah cara Anda dapat menerapkannya. developer.apple.com/library/prerelease/ios/documentation/UIKit/…
croX
Merujuk ke github.com/ThornTechPublic/InteractiveModal repo oleh Robert Chen dan menulis kelas pembungkus / penangan untuk menangani semuanya. Tidak ada lagi kode boilerplate yang mendukung empat transisi dasar (atas ke bawah, bawah ke atas, kiri ke kanan, dan kanan ke kiri) dengan isyarat penutupan github.com/chamira/ProjSetup/blob/master/AppProject/_BasicSetup/…
Fernando
@ChamiraFernando, melihat kode Anda dan itu sangat membantu. Adakah cara untuk membuatnya sehingga beberapa arah disertakan, bukan satu?
Jevon Cowell
Aku akan melakukannya. Waktu menjadi kendala besar akhir-akhir ini :(
Chamira Fernando

Jawaban:

95

Saya baru saja membuat tutorial untuk menarik modal secara interaktif untuk mengabaikannya.

http://www.thorntech.com/2016/02/ios-tutorial-close-modal-dragging/

Saya menemukan topik ini membingungkan pada awalnya, jadi tutorial menyusun ini langkah demi langkah.

masukkan deskripsi gambar di sini

Jika Anda hanya ingin menjalankan kodenya sendiri, ini adalah reponya:

https://github.com/ThornTechPublic/InteractiveModal

Ini adalah pendekatan yang saya gunakan:

Lihat Controller

Anda mengganti animasi penutupan dengan yang khusus. Jika pengguna menyeret modal, interactortendangan masuk.

import UIKit

class ViewController: UIViewController {
    let interactor = Interactor()
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if let destinationViewController = segue.destinationViewController as? ModalViewController {
            destinationViewController.transitioningDelegate = self
            destinationViewController.interactor = interactor
        }
    }
}

extension ViewController: UIViewControllerTransitioningDelegate {
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return DismissAnimator()
    }
    func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        return interactor.hasStarted ? interactor : nil
    }
}

Singkirkan Animator

Anda membuat animator khusus. Ini adalah animasi kustom yang Anda paketkan di dalam UIViewControllerAnimatedTransitioningprotokol.

import UIKit

class DismissAnimator : NSObject {
}

extension DismissAnimator : UIViewControllerAnimatedTransitioning {
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
        return 0.6
    }

    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
        guard
            let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey),
            let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey),
            let containerView = transitionContext.containerView()
            else {
                return
        }
        containerView.insertSubview(toVC.view, belowSubview: fromVC.view)
        let screenBounds = UIScreen.mainScreen().bounds
        let bottomLeftCorner = CGPoint(x: 0, y: screenBounds.height)
        let finalFrame = CGRect(origin: bottomLeftCorner, size: screenBounds.size)

        UIView.animateWithDuration(
            transitionDuration(transitionContext),
            animations: {
                fromVC.view.frame = finalFrame
            },
            completion: { _ in
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
            }
        )
    }
}

Interactor

Anda membuat subkelas UIPercentDrivenInteractiveTransitionsehingga dapat bertindak sebagai mesin negara Anda. Karena objek interaktor diakses oleh kedua VC, gunakan itu untuk melacak kemajuan panning.

import UIKit

class Interactor: UIPercentDrivenInteractiveTransition {
    var hasStarted = false
    var shouldFinish = false
}

Modal View Controller

Ini memetakan status gerakan geser ke panggilan metode interaksionor. The translationInView() ynilai menentukan apakah pengguna melewati ambang batas. Saat isyarat pan adalah .Ended, interaksinya akan menyelesaikan atau membatalkan.

import UIKit

class ModalViewController: UIViewController {

    var interactor:Interactor? = nil

    @IBAction func close(sender: UIButton) {
        dismissViewControllerAnimated(true, completion: nil)
    }

    @IBAction func handleGesture(sender: UIPanGestureRecognizer) {
        let percentThreshold:CGFloat = 0.3

        // convert y-position to downward pull progress (percentage)
        let translation = sender.translationInView(view)
        let verticalMovement = translation.y / view.bounds.height
        let downwardMovement = fmaxf(Float(verticalMovement), 0.0)
        let downwardMovementPercent = fminf(downwardMovement, 1.0)
        let progress = CGFloat(downwardMovementPercent)
        guard let interactor = interactor else { return }

        switch sender.state {
        case .Began:
            interactor.hasStarted = true
            dismissViewControllerAnimated(true, completion: nil)
        case .Changed:
            interactor.shouldFinish = progress > percentThreshold
            interactor.updateInteractiveTransition(progress)
        case .Cancelled:
            interactor.hasStarted = false
            interactor.cancelInteractiveTransition()
        case .Ended:
            interactor.hasStarted = false
            interactor.shouldFinish
                ? interactor.finishInteractiveTransition()
                : interactor.cancelInteractiveTransition()
        default:
            break
        }
    }

}
Robert Chen
sumber
4
Hai Robert, kerja bagus. Apakah Anda tahu bagaimana kami dapat memodifikasi ini untuk memungkinkannya bekerja dengan tampilan tabel? Artinya, mampu menarik ke bawah untuk menutup saat tampilan tabel di atas? Terima kasih
Ross Barbish
1
Ross, saya membuat cabang baru yang memiliki contoh yang berfungsi: github.com/ThornTechPublic/InteractiveModal/tree/Ross . Jika Anda ingin melihat seperti apa dulu, lihat GIF ini: raw.githubusercontent.com/ThornTechPublic/InteractiveModal/… . Tampilan tabel memiliki panGestureRecognizer built-in yang dapat dihubungkan ke metode handleGesture (_ :) yang ada melalui target-action. Untuk menghindari konflik dengan pengguliran tabel normal, penghentian pull-down hanya dilakukan saat tabel digulir ke atas. Saya menggunakan snapshot, dan menambahkan banyak komentar juga.
Robert Chen
Robert, lebih banyak pekerjaan hebat. Saya membuat implementasi saya sendiri yang menggunakan metode panning tableView yang ada seperti scrollViewDidScroll, scrollViewWillBeginDragging. Ini membutuhkan tableView untuk memiliki bounce dan bounceVertically keduanya disetel ke true - dengan cara itu kita bisa mengukur ContentOffset dari item tableview. Keuntungan dari metode ini adalah metode ini tampaknya memungkinkan tampilan tabel digeser dari layar dalam satu gerakan jika ada kecepatan yang cukup (karena pantulan). Saya mungkin akan mengirimi Anda permintaan tarik minggu ini, kedua opsi tampaknya valid.
Ross Barbish
Kerja bagus @RossBarbish, saya tidak sabar untuk melihat bagaimana Anda melakukannya. Akan sangat menyenangkan untuk dapat menggulir ke atas, lalu memasuki mode transisi interaktif, semuanya dalam satu gerakan yang lancar.
Robert Chen
1
properti presentasi set segueuntuk over current contextmenghindari layar hitam di belakang ketika Anda menarik ke bawah viewController
nitish005
63

Saya akan membagikan bagaimana saya melakukannya di Swift 3:

Hasil

Penerapan

class MainViewController: UIViewController {

  @IBAction func click() {
    performSegue(withIdentifier: "showModalOne", sender: nil)
  }
  
}

class ModalOneViewController: ViewControllerPannable {
  override func viewDidLoad() {
    super.viewDidLoad()
    
    view.backgroundColor = .yellow
  }
  
  @IBAction func click() {
    performSegue(withIdentifier: "showModalTwo", sender: nil)
  }
}

class ModalTwoViewController: ViewControllerPannable {
  override func viewDidLoad() {
    super.viewDidLoad()
    
    view.backgroundColor = .green
  }
}

Dimana Modals View Controllers mewarisi dari classyang telah saya buat ( ViewControllerPannable) untuk membuatnya dapat diseret dan ditutup ketika mencapai kecepatan tertentu.

ViewControllerPannable

class ViewControllerPannable: UIViewController {
  var panGestureRecognizer: UIPanGestureRecognizer?
  var originalPosition: CGPoint?
  var currentPositionTouched: CGPoint?
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panGestureAction(_:)))
    view.addGestureRecognizer(panGestureRecognizer!)
  }
  
  func panGestureAction(_ panGesture: UIPanGestureRecognizer) {
    let translation = panGesture.translation(in: view)
    
    if panGesture.state == .began {
      originalPosition = view.center
      currentPositionTouched = panGesture.location(in: view)
    } else if panGesture.state == .changed {
        view.frame.origin = CGPoint(
          x: translation.x,
          y: translation.y
        )
    } else if panGesture.state == .ended {
      let velocity = panGesture.velocity(in: view)

      if velocity.y >= 1500 {
        UIView.animate(withDuration: 0.2
          , animations: {
            self.view.frame.origin = CGPoint(
              x: self.view.frame.origin.x,
              y: self.view.frame.size.height
            )
          }, completion: { (isCompleted) in
            if isCompleted {
              self.dismiss(animated: false, completion: nil)
            }
        })
      } else {
        UIView.animate(withDuration: 0.2, animations: {
          self.view.center = self.originalPosition!
        })
      }
    }
  }
}
Wilson
sumber
1
Saya menyalin kode Anda dan berhasil. Bu latar belakang tampilan model saat pull down berwarna hitam, tidak transparan seperti milik Anda
Nguyễn Anh Việt
5
Dalam storyboard dari panel Attributes InspectorStoryboard segue dari MainViewControllerke ModalViewController: setel properti Presentation ke Over Current Context
Wilson
Ini tampaknya lebih mudah daripada jawaban yang diterima, tetapi saya mendapatkan kesalahan di kelas ViewControllerPannable. Kesalahannya adalah "tidak dapat memanggil nilai jenis non-fungsi UIPanGestureRecognizer". Ada di baris "panGestureRecognizer = UIPanGestureRecognizer (target: self, action: #selector (panGestureAction (_ :)))" Ada ide?
tylerSF
Memperbaiki kesalahan yang saya sebutkan dengan mengubahnya menjadi UIPanGestureRecognizer. "panGestureRecognizer = panGestureRecognizer (target ..." diubah menjadi: "panGestureRecognizer = UIPanGestureRecognizer (target ..."
tylerSF
Karena saya menampilkan VC yang tidak secara sederhana menampilkannya, bagaimana cara menghapus latar belakang hitam saat menutupnya?
Mr. Bean
20

Berikut adalah solusi satu file berdasarkan jawaban @ wilson (terima kasih 👍) dengan peningkatan berikut:


Daftar Perbaikan dari solusi sebelumnya

  • Batasi panning sehingga tampilan hanya turun:
    • Hindari terjemahan horizontal dengan hanya memperbarui ykoordinatview.frame.origin
    • Hindari menggeser layar saat menggeser ke atas let y = max(0, translation.y)
  • Juga tutup pengontrol tampilan berdasarkan tempat jari dilepaskan (default ke setengah bagian bawah layar) dan tidak hanya berdasarkan kecepatan gesekan
  • Tampilkan pengontrol tampilan sebagai modal untuk memastikan pengontrol tampilan sebelumnya muncul di belakang dan menghindari latar belakang hitam (harus menjawab pertanyaan Anda @ nguyễn-anh-việt)
  • Hapus yang tidak dibutuhkan currentPositionToucheddanoriginalPosition
  • Tunjukkan parameter berikut:
    • minimumVelocityToHide: kecepatan apa yang cukup untuk disembunyikan (default ke 1500)
    • minimumScreenRatioToHide: seberapa rendah cukup untuk disembunyikan (default ke 0,5)
    • animationDuration : seberapa cepat kita menyembunyikan / menampilkan (default ke 0.2s)

Larutan

Swift 3 & Swift 4:

//
//  PannableViewController.swift
//

import UIKit

class PannableViewController: UIViewController {
    public var minimumVelocityToHide: CGFloat = 1500
    public var minimumScreenRatioToHide: CGFloat = 0.5
    public var animationDuration: TimeInterval = 0.2

    override func viewDidLoad() {
        super.viewDidLoad()

        // Listen for pan gesture
        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(onPan(_:)))
        view.addGestureRecognizer(panGesture)
    }

    @objc func onPan(_ panGesture: UIPanGestureRecognizer) {

        func slideViewVerticallyTo(_ y: CGFloat) {
            self.view.frame.origin = CGPoint(x: 0, y: y)
        }

        switch panGesture.state {

        case .began, .changed:
            // If pan started or is ongoing then
            // slide the view to follow the finger
            let translation = panGesture.translation(in: view)
            let y = max(0, translation.y)
            slideViewVerticallyTo(y)

        case .ended:
            // If pan ended, decide it we should close or reset the view
            // based on the final position and the speed of the gesture
            let translation = panGesture.translation(in: view)
            let velocity = panGesture.velocity(in: view)
            let closing = (translation.y > self.view.frame.size.height * minimumScreenRatioToHide) ||
                          (velocity.y > minimumVelocityToHide)

            if closing {
                UIView.animate(withDuration: animationDuration, animations: {
                    // If closing, animate to the bottom of the view
                    self.slideViewVerticallyTo(self.view.frame.size.height)
                }, completion: { (isCompleted) in
                    if isCompleted {
                        // Dismiss the view when it dissapeared
                        dismiss(animated: false, completion: nil)
                    }
                })
            } else {
                // If not closing, reset the view to the top
                UIView.animate(withDuration: animationDuration, animations: {
                    slideViewVerticallyTo(0)
                })
            }

        default:
            // If gesture state is undefined, reset the view to the top
            UIView.animate(withDuration: animationDuration, animations: {
                slideViewVerticallyTo(0)
            })

        }
    }

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)   {
        super.init(nibName: nil, bundle: nil)
        modalPresentationStyle = .overFullScreen;
        modalTransitionStyle = .coverVertical;
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        modalPresentationStyle = .overFullScreen;
        modalTransitionStyle = .coverVertical;
    }
}
agirault
sumber
Dalam kode Anda, ada salah ketik atau variabel "minimumHeightRatioToHide" yang
hilang
1
Terima kasih @shokaveli, diperbaiki (dulu minimumScreenRatioToHide)
agirault
Ini adalah solusi yang sangat bagus. Namun, saya memiliki masalah kecil dan tidak begitu yakin apa penyebabnya: dropbox.com/s/57abkl9vh2goif8/pannable.gif?dl=0 Latar belakang merah adalah bagian dari modal VC, latar belakang biru adalah bagian dari VC yang mempresentasikan modal. Ada perilaku glitchy ini saat pengenal gerakan pan dimulai yang sepertinya tidak bisa saya perbaiki.
Knolraap
1
Halo Mungkin melihat nilai self.view.frame.originsebelum Anda memanggil sliceViewVerticallyTopertama kali: tampaknya offset yang kita lihat sama dengan tinggi bilah status, jadi mungkin asal awal Anda bukan 0?
Agirault
1
Lebih baik digunakan slideViewVerticallyTosebagai fungsi bertingkat di onPan.
Nik Kov
16

membuat demo untuk menarik ke bawah secara interaktif untuk menutup pengontrol tampilan seperti mode temukan snapchat. Periksa github ini untuk proyek sampel.

masukkan deskripsi gambar di sini

med
sumber
2
Bagus, tapi itu benar-benar ketinggalan jaman. Adakah yang tahu proyek sampel lain seperti ini?
pelajar
14

Swift 4.x, Menggunakan Pangesture

Cara yang sederhana

Vertikal

class ViewConrtoller: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(onDrage(_:))))
    }

    @objc func onDrage(_ sender:UIPanGestureRecognizer) {
        let percentThreshold:CGFloat = 0.3
        let translation = sender.translation(in: view)

        let newX = ensureRange(value: view.frame.minX + translation.x, minimum: 0, maximum: view.frame.maxX)
        let progress = progressAlongAxis(newX, view.bounds.width)

        view.frame.origin.x = newX //Move view to new position

        if sender.state == .ended {
            let velocity = sender.velocity(in: view)
           if velocity.x >= 300 || progress > percentThreshold {
               self.dismiss(animated: true) //Perform dismiss
           } else {
               UIView.animate(withDuration: 0.2, animations: {
                   self.view.frame.origin.x = 0 // Revert animation
               })
          }
       }

       sender.setTranslation(.zero, in: view)
    }
}

Fungsi pembantu

func progressAlongAxis(_ pointOnAxis: CGFloat, _ axisLength: CGFloat) -> CGFloat {
        let movementOnAxis = pointOnAxis / axisLength
        let positiveMovementOnAxis = fmaxf(Float(movementOnAxis), 0.0)
        let positiveMovementOnAxisPercent = fminf(positiveMovementOnAxis, 1.0)
        return CGFloat(positiveMovementOnAxisPercent)
    }

    func ensureRange<T>(value: T, minimum: T, maximum: T) -> T where T : Comparable {
        return min(max(value, minimum), maximum)
    }

Cara yang sulit

Lihat ini -> https://github.com/satishVekariya/DraggableViewController

SPatel
sumber
1
Saya mencoba menggunakan kode Anda. tetapi saya ingin melakukan sedikit perubahan jika subview saya berada di bawah dan saat pengguna menyeret tampilan, ketinggian subview juga harus meningkat sehubungan dengan posisi yang disadap. Catatan: - acara isyarat itu telah diletakkan di subview
Ekra
Tentu, Anda bisa melakukannya
SPatel
Hai @SPatel Ada ide tentang cara memodifikasi kode ini untuk digunakan untuk menyeret di sisi kiri sumbu x, yaitu gerakan negatif di sepanjang sumbu x?
Nikhil Pandey
1
@SPatel Anda juga perlu mengubah judul jawaban karena vertikal menunjukkan gerakan sumbu x dan horizontal menunjukkan gerakan sumbu y.
Nikhil Pandey
1
Ingat untuk mengatur modalPresentationStyle = UIModalPresentationOverFullScreenuntuk menghindari layar belakang di belakang view.
tounaobun
13

Saya menemukan cara super sederhana untuk melakukan ini. Cukup masukkan kode berikut ke pengontrol tampilan Anda:

Cepat 4

override func viewDidLoad() {
    super.viewDidLoad()
    let gestureRecognizer = UIPanGestureRecognizer(target: self,
                                                   action: #selector(panGestureRecognizerHandler(_:)))
    view.addGestureRecognizer(gestureRecognizer)
}

@IBAction func panGestureRecognizerHandler(_ sender: UIPanGestureRecognizer) {
    let touchPoint = sender.location(in: view?.window)
    var initialTouchPoint = CGPoint.zero

    switch sender.state {
    case .began:
        initialTouchPoint = touchPoint
    case .changed:
        if touchPoint.y > initialTouchPoint.y {
            view.frame.origin.y = touchPoint.y - initialTouchPoint.y
        }
    case .ended, .cancelled:
        if touchPoint.y - initialTouchPoint.y > 200 {
            dismiss(animated: true, completion: nil)
        } else {
            UIView.animate(withDuration: 0.2, animations: {
                self.view.frame = CGRect(x: 0,
                                         y: 0,
                                         width: self.view.frame.size.width,
                                         height: self.view.frame.size.height)
            })
        }
    case .failed, .possible:
        break
    }
}
Alex Shubin
sumber
1
Terima kasih, bekerja dengan sempurna! Cukup letakkan Pan Gesture Recogniser ke tampilan di pembuat antarmuka dan hubungkan dengan @IBAction di atas.
balazs630
Bekerja di Swift 5 juga. Ikuti saja instruksi yang diberikan @ balazs630.
LondonGuy
Saya pikir ini adalah cara terbaik.
Enkha
@Alex Shubin, Bagaimana cara menutup saat menyeret dari ViewController ke TabbarController?
Vadlapalli Masthan
12

Memperbarui repo Swift 4 secara besar- besaran .

Untuk Swift 3 , saya telah membuat yang berikut ini untuk menampilkan UIViewControllerdari kanan ke kiri dan mengabaikannya dengan gerakan menggeser. Saya telah mengunggah ini sebagai repositori GitHub .

masukkan deskripsi gambar di sini

DismissOnPanGesture.swift mengajukan:

//  Created by David Seek on 11/21/16.
//  Copyright © 2016 David Seek. All rights reserved.

import UIKit

class DismissAnimator : NSObject {
}

extension DismissAnimator : UIViewControllerAnimatedTransitioning {
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.6
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

        let screenBounds = UIScreen.main.bounds
        let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
        let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
        var x:CGFloat      = toVC!.view.bounds.origin.x - screenBounds.width
        let y:CGFloat      = toVC!.view.bounds.origin.y
        let width:CGFloat  = toVC!.view.bounds.width
        let height:CGFloat = toVC!.view.bounds.height
        var frame:CGRect   = CGRect(x: x, y: y, width: width, height: height)

        toVC?.view.alpha = 0.2

        toVC?.view.frame = frame
        let containerView = transitionContext.containerView

        containerView.insertSubview(toVC!.view, belowSubview: fromVC!.view)


        let bottomLeftCorner = CGPoint(x: screenBounds.width, y: 0)
        let finalFrame = CGRect(origin: bottomLeftCorner, size: screenBounds.size)

        UIView.animate(
            withDuration: transitionDuration(using: transitionContext),
            animations: {
                fromVC!.view.frame = finalFrame
                toVC?.view.alpha = 1

                x = toVC!.view.bounds.origin.x
                frame = CGRect(x: x, y: y, width: width, height: height)

                toVC?.view.frame = frame
            },
            completion: { _ in
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
            }
        )
    }
}

class Interactor: UIPercentDrivenInteractiveTransition {
    var hasStarted = false
    var shouldFinish = false
}

let transition: CATransition = CATransition()

func presentVCRightToLeft(_ fromVC: UIViewController, _ toVC: UIViewController) {
    transition.duration = 0.5
    transition.type = kCATransitionPush
    transition.subtype = kCATransitionFromRight
    fromVC.view.window!.layer.add(transition, forKey: kCATransition)
    fromVC.present(toVC, animated: false, completion: nil)
}

func dismissVCLeftToRight(_ vc: UIViewController) {
    transition.duration = 0.5
    transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
    transition.type = kCATransitionPush
    transition.subtype = kCATransitionFromLeft
    vc.view.window!.layer.add(transition, forKey: nil)
    vc.dismiss(animated: false, completion: nil)
}

func instantiatePanGestureRecognizer(_ vc: UIViewController, _ selector: Selector) {
    var edgeRecognizer: UIScreenEdgePanGestureRecognizer!
    edgeRecognizer = UIScreenEdgePanGestureRecognizer(target: vc, action: selector)
    edgeRecognizer.edges = .left
    vc.view.addGestureRecognizer(edgeRecognizer)
}

func dismissVCOnPanGesture(_ vc: UIViewController, _ sender: UIScreenEdgePanGestureRecognizer, _ interactor: Interactor) {
    let percentThreshold:CGFloat = 0.3
    let translation = sender.translation(in: vc.view)
    let fingerMovement = translation.x / vc.view.bounds.width
    let rightMovement = fmaxf(Float(fingerMovement), 0.0)
    let rightMovementPercent = fminf(rightMovement, 1.0)
    let progress = CGFloat(rightMovementPercent)

    switch sender.state {
    case .began:
        interactor.hasStarted = true
        vc.dismiss(animated: true, completion: nil)
    case .changed:
        interactor.shouldFinish = progress > percentThreshold
        interactor.update(progress)
    case .cancelled:
        interactor.hasStarted = false
        interactor.cancel()
    case .ended:
        interactor.hasStarted = false
        interactor.shouldFinish
            ? interactor.finish()
            : interactor.cancel()
    default:
        break
    }
}

Penggunaan mudah:

import UIKit

class VC1: UIViewController, UIViewControllerTransitioningDelegate {

    let interactor = Interactor()

    @IBAction func present(_ sender: Any) {
        let vc = self.storyboard?.instantiateViewController(withIdentifier: "VC2") as! VC2
        vc.transitioningDelegate = self
        vc.interactor = interactor

        presentVCRightToLeft(self, vc)
    }

    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return DismissAnimator()
    }

    func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        return interactor.hasStarted ? interactor : nil
    }
}

class VC2: UIViewController {

    var interactor:Interactor? = nil

    override func viewDidLoad() {
        super.viewDidLoad()
        instantiatePanGestureRecognizer(self, #selector(gesture))
    }

    @IBAction func dismiss(_ sender: Any) {
        dismissVCLeftToRight(self)
    }

    func gesture(_ sender: UIScreenEdgePanGestureRecognizer) {
        dismissVCOnPanGesture(self, sender, interactor!)
    }
}
David Seek
sumber
1
Menakjubkan! Terima kasih telah berbagi.
Sharad Chauhan
Hai, Bagaimana saya bisa mempresentasikan menggunakan Pan Gesture Recognizer, tolong bantu saya, terima kasih
Muralikrishna
Saya memulai saluran youtube dengan tutorial sekarang. Saya mungkin membuat episode untuk membahas masalah ini untuk iOS 13+ / Swift 5
David Seek
6

Yang Anda gambarkan adalah animasi transisi kustom interaktif . Anda menyesuaikan animasi dan isyarat mengemudi dari transisi, misalnya menutup (atau tidak) pengontrol tampilan yang disajikan. Cara termudah untuk menerapkannya adalah dengan menggabungkan UIPanGestureRecognizer dengan UIPercentDrivenInteractiveTransition.

Buku saya menjelaskan bagaimana melakukan ini, dan saya telah memposting contoh (dari buku). Contoh khusus ini adalah situasi yang berbeda - transisi ke samping, bukan ke bawah, dan ini untuk pengontrol tab bar, bukan pengontrol yang disajikan - tetapi ide dasarnya persis sama:

https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/bk2ch06p296customAnimation2/ch19p620customAnimation1/AppDelegate.swift

Jika Anda mengunduh proyek itu dan menjalankannya, Anda akan melihat bahwa apa yang terjadi persis seperti yang Anda gambarkan, kecuali bahwa itu menyamping: jika seret lebih dari setengah, kami bertransisi, tetapi jika tidak, kami membatalkan dan memasang kembali ke tempat.

Matt
sumber
3
404 halaman tidak ditemukan.
penjebak
6

Hanya tutup vertikal

func panGestureAction(_ panGesture: UIPanGestureRecognizer) {
    let translation = panGesture.translation(in: view)

    if panGesture.state == .began {
        originalPosition = view.center
        currentPositionTouched = panGesture.location(in: view)    
    } else if panGesture.state == .changed {
        view.frame.origin = CGPoint(
            x:  view.frame.origin.x,
            y:  view.frame.origin.y + translation.y
        )
        panGesture.setTranslation(CGPoint.zero, in: self.view)
    } else if panGesture.state == .ended {
        let velocity = panGesture.velocity(in: view)
        if velocity.y >= 150 {
            UIView.animate(withDuration: 0.2
                , animations: {
                    self.view.frame.origin = CGPoint(
                        x: self.view.frame.origin.x,
                        y: self.view.frame.size.height
                    )
            }, completion: { (isCompleted) in
                if isCompleted {
                    self.dismiss(animated: false, completion: nil)
                }
            })
        } else {
            UIView.animate(withDuration: 0.2, animations: {
                self.view.center = self.originalPosition!
            })
        }
    }
nona Gbot
sumber
6

Saya telah membuat ekstensi yang mudah digunakan.

Cukup melekatkan UIViewController Anda dengan InteractiveViewController dan Anda sudah selesai InteractiveViewController

panggil metode showInteractive () dari pengontrol Anda untuk ditampilkan sebagai Interaktif.

masukkan deskripsi gambar di sini

Shoaib
sumber
4

Dalam Tujuan C: Inilah kodenya

diviewDidLoad

UISwipeGestureRecognizer *swipeRecognizer = [[UISwipeGestureRecognizer alloc]
                                             initWithTarget:self action:@selector(swipeDown:)];
swipeRecognizer.direction = UISwipeGestureRecognizerDirectionDown;
[self.view addGestureRecognizer:swipeRecognizer];

//Swipe Down Method

- (void)swipeDown:(UIGestureRecognizer *)sender{
[self dismissViewControllerAnimated:YES completion:nil];
}
Deepak Kumar
sumber
bagaimana cara mengontrol waktu untuk menggesek ke bawah sebelum ditutup?
TonyTony
2

Berikut adalah ekstensi yang saya buat berdasarkan jawaban @Wilson:

// MARK: IMPORT STATEMENTS
import UIKit

// MARK: EXTENSION
extension UIViewController {

    // MARK: IS SWIPABLE - FUNCTION
    func isSwipable() {
        let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
        self.view.addGestureRecognizer(panGestureRecognizer)
    }

    // MARK: HANDLE PAN GESTURE - FUNCTION
    @objc func handlePanGesture(_ panGesture: UIPanGestureRecognizer) {
        let translation = panGesture.translation(in: view)
        let minX = view.frame.width * 0.135
        var originalPosition = CGPoint.zero

        if panGesture.state == .began {
            originalPosition = view.center
        } else if panGesture.state == .changed {
            view.frame.origin = CGPoint(x: translation.x, y: 0.0)

            if panGesture.location(in: view).x > minX {
                view.frame.origin = originalPosition
            }

            if view.frame.origin.x <= 0.0 {
                view.frame.origin.x = 0.0
            }
        } else if panGesture.state == .ended {
            if view.frame.origin.x >= view.frame.width * 0.5 {
                UIView.animate(withDuration: 0.2
                     , animations: {
                        self.view.frame.origin = CGPoint(
                            x: self.view.frame.size.width,
                            y: self.view.frame.origin.y
                        )
                }, completion: { (isCompleted) in
                    if isCompleted {
                        self.dismiss(animated: false, completion: nil)
                    }
                })
            } else {
                UIView.animate(withDuration: 0.2, animations: {
                    self.view.frame.origin = originalPosition
                })
            }
        }
    }

}

PEMAKAIAN

Di dalam pengontrol tampilan Anda, Anda ingin digesek:

override func viewDidLoad() {
    super.viewDidLoad()

    self.isSwipable()
}

dan itu akan dapat ditutup dengan menggesek dari sisi paling kiri pengontrol tampilan, sebagai pengontrol navigasi.

fredericdnd
sumber
Hei, saya telah menggunakan kode Anda dan berfungsi dengan sempurna untuk gesekan ke kanan tetapi saya ingin mengabaikan Geser ke bawah, Bagaimana saya bisa melakukan ini? Tolong bantu!
Khush
2

Ini kelas sederhana saya untuk Drag ViewController dari sumbu . Hanya Herited kelas Anda dari DraggableViewController.

MyCustomClass: DraggableViewController

Hanya berfungsi untuk ViewController yang disajikan.

// MARK: - DraggableViewController

public class DraggableViewController: UIViewController {

    public let percentThresholdDismiss: CGFloat = 0.3
    public var velocityDismiss: CGFloat = 300
    public var axis: NSLayoutConstraint.Axis = .horizontal
    public var backgroundDismissColor: UIColor = .black {
        didSet {
            navigationController?.view.backgroundColor = backgroundDismissColor
        }
    }

    // MARK: LifeCycle

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(onDrag(_:))))
    }

    // MARK: Private methods

    @objc fileprivate func onDrag(_ sender: UIPanGestureRecognizer) {

        let translation = sender.translation(in: view)

        // Movement indication index
        let movementOnAxis: CGFloat

        // Move view to new position
        switch axis {
        case .vertical:
            let newY = min(max(view.frame.minY + translation.y, 0), view.frame.maxY)
            movementOnAxis = newY / view.bounds.height
            view.frame.origin.y = newY

        case .horizontal:
            let newX = min(max(view.frame.minX + translation.x, 0), view.frame.maxX)
            movementOnAxis = newX / view.bounds.width
            view.frame.origin.x = newX
        }

        let positiveMovementOnAxis = fmaxf(Float(movementOnAxis), 0.0)
        let positiveMovementOnAxisPercent = fminf(positiveMovementOnAxis, 1.0)
        let progress = CGFloat(positiveMovementOnAxisPercent)
        navigationController?.view.backgroundColor = UIColor.black.withAlphaComponent(1 - progress)

        switch sender.state {
        case .ended where sender.velocity(in: view).y >= velocityDismiss || progress > percentThresholdDismiss:
            // After animate, user made the conditions to leave
            UIView.animate(withDuration: 0.2, animations: {
                switch self.axis {
                case .vertical:
                    self.view.frame.origin.y = self.view.bounds.height

                case .horizontal:
                    self.view.frame.origin.x = self.view.bounds.width
                }
                self.navigationController?.view.backgroundColor = UIColor.black.withAlphaComponent(0)

            }, completion: { finish in
                self.dismiss(animated: true) //Perform dismiss
            })
        case .ended:
            // Revert animation
            UIView.animate(withDuration: 0.2, animations: {
                switch self.axis {
                case .vertical:
                    self.view.frame.origin.y = 0

                case .horizontal:
                    self.view.frame.origin.x = 0
                }
            })
        default:
            break
        }
        sender.setTranslation(.zero, in: view)
    }
}
YannSteph
sumber
2

Bagi mereka yang benar-benar ingin menyelami lebih dalam Transisi UIViewController Kustom, saya merekomendasikan tutorial hebat ini dari raywenderlich.com .

Proyek sampel akhir asli mengandung bug. Jadi saya memperbaikinya dan mengunggahnya ke repo Github . Projnya ada di Swift 5, jadi Anda dapat dengan mudah menjalankan dan memainkannya.

Berikut pratinjau:

Dan itu juga interaktif!

Selamat meretas!

Benjamin Wen
sumber
0

Anda dapat menggunakan UIPanGestureRecognizer untuk mendeteksi seret pengguna dan memindahkan tampilan modal dengannya. Jika posisi akhir cukup jauh di bawah, tampilan dapat ditutup, atau dianimasikan kembali ke posisi semula.

Lihat jawaban ini untuk informasi lebih lanjut tentang bagaimana menerapkan sesuatu seperti ini.

DanielG
sumber