İOS'ta bir modal'ı kapatmak için nasıl aşağı sürüklenir?


91

Bir modeli kapatmanın yaygın bir yolu, aşağı kaydırmaktır - Yeterince uzaksa, modal kapatılırsa, kullanıcının modu aşağı doğru sürüklemesine nasıl izin veririz, aksi takdirde orijinal konumuna geri dönebilir?

Örneğin, Twitter uygulamasının fotoğraf görünümlerinde veya Snapchat'ın "keşfet" modunda kullanıldığını bulabiliriz.

Benzer iş parçacıkları, bir kullanıcı aşağı kaydırdığında bir modal VC'yi kapatmak için bir UISwipeGestureRecognizer ve [self dismissViewControllerAnimated ...] kullanabileceğimizi belirtir. Ancak bu yalnızca tek bir kaydırma işlemini gerçekleştirir, kullanıcının modu sürüklemesine izin vermez.


Özel etkileşimli geçişlere bir göz atın. Bunu gerçekleştirmenin yolu budur. developer.apple.com/library/prerelease/ios/documentation/UIKit/…
croX

Robert Chen tarafından github.com/ThornTechPublic/InteractiveModal deposuna atıfta bulunuldu ve her şeyi işlemek için bir sarmalayıcı / işleyici sınıfı yazdı. Artık Demirbaş kodu destekleri dört temel geçişler (yukarıdan aşağıya, üstüne alt, soldan sağa ve sağdan sola doğru) görevden jestleri ile github.com/chamira/ProjSetup/blob/master/AppProject/_BasicSetup/...
Chamira Fernando

@ChamiraFernando, kodunuza baktı ve çok yardımcı oldu. Bir yerine birden çok yönün dahil edilmesini sağlamanın bir yolu var mı?
Jevon Cowell

Yapacağım. Bu günlerde zaman çok büyük bir kısıtlama :(
Chamira Fernando

Yanıtlar:


95

Bir modal'i devre dışı bırakmak için etkileşimli olarak aşağı sürüklemek için bir eğitim oluşturdum.

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

Bu konuyu ilk başta kafa karıştırıcı buldum, bu yüzden eğitim bunu adım adım oluşturuyor.

görüntü açıklamasını buraya girin

Kodu kendiniz çalıştırmak istiyorsanız, bu depo:

https://github.com/ThornTechPublic/InteractiveModal

Kullandığım yaklaşım bu:

Denetleyiciyi Görüntüle

Kapatma animasyonunu özel bir animasyonla geçersiz kılarsınız. Kullanıcı modeli sürüklüyorsa interactordevreye girer.

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

Animatörü Kapat

Özel bir animatör yaratırsınız. Bu, bir UIViewControllerAnimatedTransitioningprotokolün içinde paketlediğiniz özel bir animasyondur .

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

İnteraktör

Durum UIPercentDrivenInteractiveTransitionmakineniz olarak hareket edebilmesi için alt sınıflarsınız . Uygulayıcı nesnesine her iki VC tarafından erişildiğinden, kaydırma ilerlemesini takip etmek için kullanın.

import UIKit

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

Modal Görünüm Denetleyicisi

Bu, pan hareketi durumunu interactor yöntem çağrılarına eşler. translationInView() yDeğeri, kullanıcının, bir eşiği geçti olup olmadığını belirler. Kaydırma hareketi olduğunda .Ended, uygulayıcı bitirir veya iptal eder.

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

}

4
Hey Robert, harika iş. Tablo görünümleriyle çalışmasına izin vermek için bunu nasıl değiştirebileceğimiz konusunda bir fikriniz var mı? Yani, masa görünümü en üstteyken işten çıkarmak için aşağı çekebilmek? Teşekkürler
Ross Barbish

1
Ross, çalışan bir örneği olan yeni bir şube oluşturdum: github.com/ThornTechPublic/InteractiveModal/tree/Ross . İlk önce neye benzediğini görmek istiyorsanız, şu GIF'e bakın: raw.githubusercontent.com/ThornTechPublic/InteractiveModal/… . Tablo görünümünde, hedef eylem yoluyla mevcut handleGesture (_ :) yöntemine bağlanabilen yerleşik bir panGestureRecognizer bulunur. Normal tablo kaydırma ile çakışmayı önlemek için, aşağı açılır kapama işlemi yalnızca masa en üste kaydırıldığında devreye girer. Bir anlık görüntü kullandım ve birçok yorum da ekledim.
Robert Chen

Robert, daha harika iş. ScrollViewDidScroll, scrollViewWillBeginDragging gibi mevcut tableView kaydırma yöntemlerini kullanan kendi uygulamamı yaptım. TableView öğesinin sıçrama ve sıçrama değerlerine sahip olmasını gerektirirVertically her ikisi de true olarak ayarlanmış - bu şekilde tableview öğelerinin ContentOffset değerini ölçebiliriz. Bu yöntemin avantajı, yeterli hız varsa (zıplama nedeniyle) tablo görünümünün tek bir hareketle ekrandan kaydırılmasına izin veriyor gibi görünmesidir. Muhtemelen bu hafta bir ara size bir çekme isteği göndereceğim, her iki seçenek de geçerli görünüyor.
Ross Barbish

İyi iş @RossBarbish, bunu nasıl başardığını görmek için sabırsızlanıyorum. Hepsi tek bir akışkan hareketle yukarı kaydırıp ardından etkileşimli geçiş moduna girebilmek oldukça hoş olacak.
Robert Chen

1
viewController'ı aşağı çektiğinizde arkadaki siyah ekranı önlemek segueiçin sunum özelliğini ayarlayınover current context
nitish005

63

Swift 3'te bunu nasıl yaptığımı paylaşacağım:

Sonuç

Uygulama

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

Modal Görünüm Denetleyicileri, belirli bir hıza ulaştıklarında sürüklenebilir ve kapatılabilir hale getirmek için oluşturduğumdan class( ViewControllerPannable) miras alır .

ViewControllerPannable sınıfı

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!
        })
      }
    }
  }
}

1
Kodunuzu kopyaladım ve işe yaradı. Bu aşağı çekildiğinde model görünümünün arka planı siyahtır, sizinki kadar şeffaf değildir
Nguyenễn Anh Việt

5
Film şeridinde gelen Müfettiş Özellikleri panelini Storyboard segueait MainViewControlleriçin ModalViewControllerayarlayın: Sunum mülk Aşırı Akım Bağlamında
Wilson

Bu, kabul edilen cevaptan daha kolay görünüyor, ancak ViewControllerPannable sınıfında bir hata alıyorum. Hata "işlev olmayan tip UIPanGestureRecognizer'ın değeri çağrılamıyor". "PanGestureRecognizer = UIPanGestureRecognizer (target: self, action: #selector (panGestureAction (_ :)))" satırında. Herhangi bir fikriniz var mı?
tylerSF

Bahsettiğim hatayı UIPanGestureRecognizer olarak değiştirerek düzeltildi. "panGestureRecognizer = panGestureRecognizer (target ..." şu şekilde değiştirildi: "panGestureRecognizer = UIPanGestureRecognizer (target ..."
tylerSF

VC'yi mod olarak sunmadığım için, kapatırken siyah arka planı nasıl kaldırabilirim?
Mr. Bean

20

İşte aşağıdaki iyileştirmelerle birlikte @ Wilson'ın cevabına (teşekkürler 👍) dayalı tek dosyalı bir çözüm:


Önceki çözümden iyileştirmelerin listesi

  • Görüntüyü yalnızca aşağı inecek şekilde kaydırmayı sınırlayın:
    • Sadece ykoordinatını güncelleyerek yatay çeviriden kaçınınview.frame.origin
    • İle yukarı kaydırırken ekranın dışına kaydırmaktan kaçının. let y = max(0, translation.y)
  • Ayrıca, yalnızca kaydırmanın hızına bağlı olarak değil, parmağın bırakıldığı yere (varsayılan olarak ekranın alt yarısına) bağlı olarak görünüm denetleyicisini kapatın
  • Önceki görüntü denetleyicisinin arkasında görünmesini sağlamak ve siyah bir arka plandan kaçınmak için görünüm denetleyicisini modal olarak gösterin (sorunuzu cevaplamalıdır @ nguyễn-anh-việt)
  • Gereksiz kaldırın currentPositionTouchedveoriginalPosition
  • Aşağıdaki parametreleri gösterin:
    • minimumVelocityToHide: hangi hız gizlemek için yeterlidir (varsayılan 1500'dür)
    • minimumScreenRatioToHide: ne kadar düşük gizlemek için yeterlidir (varsayılan 0,5'tir)
    • animationDuration : ne kadar hızlı gizleriz / gösteririz (varsayılan 0,2 saniyedir)

Çözüm

Swift 3 ve 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;
    }
}

Kodunuzda ya yazım hatası ya da "minimumHeightRatioToHide" değişkeni eksik
shokaveli

1
Teşekkürler @shokaveli, düzeltildi ( minimumScreenRatioToHide
eskiden

Bu çok güzel bir çözüm. Bununla birlikte, küçük bir sorunum var ve nedeninin ne olduğundan tam olarak emin değilim: dropbox.com/s/57abkl9vh2goif8/pannable.gif?dl=0 Kırmızı arka plan modal VC'nin bir parçası, mavi arka plan ise VC'nin bir parçası. modeli sundu. Pan hareketi tanıyıcı başladığında düzeltemediğim bu aksaklıklı davranış var.
Knolraap

1
Merhaba @Knolraap. Belki ilk self.view.frame.originaramadan önceki değerine bakabilirsiniz sliceViewVerticallyTo: Gördüğümüz ofset durum çubuğu yüksekliğiyle aynı görünüyor, bu yüzden belki de başlangıç ​​kaynağınız 0 değil mi?
agirault

1
İçinde slideViewVerticallyToyuvalanmış bir işlev olarak kullanmak daha iyidir onPan.
Nik Kov

16

Snapchat'ın keşfetme modu gibi görünüm denetleyicisini devre dışı bırakmak için etkileşimli olarak aşağı sürüklemek için bir demo oluşturdu. Örnek proje için bu github'a bakın.

görüntü açıklamasını buraya girin


2
Harika, ama gerçekten modası geçmiş. Bunun gibi başka bir örnek proje bilen var mı?
thelearner

14

Swift 4.x, Pangesture Kullanımı

Basit yol

Dikey

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

Yardımcı işlevi

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

Zor yol

Buna bakın -> https://github.com/satishVekariya/DraggableViewController


1
Kodunuzu kullanmayı denedim. ancak alt görünümüm altta olduğunda küçük bir değişiklik yapmak istiyorum ve kullanıcı görünümü sürüklediğinde, alt görünümün yüksekliği de dokunulan konuma göre artmalıdır. Not: - alt görünüme yerleştirilen hareket olayı
Ekra

Elbette, yapabilirsin
SPatel

Merhaba @SPatel x ekseninin sol tarafında sürüklemek için bu kodun nasıl değiştirileceğine dair bir fikriniz var mı, yani x ekseni boyunca negatif hareketler?
Nikhil Pandey

1
@SPatel Dikey x ekseni hareketlerini gösterdiğinden ve yatay, y ekseni hareketlerini gösterdiğinden, cevabın başlığını da değiştirmeniz gerekir.
Nikhil Pandey

1
modalPresentationStyle = UIModalPresentationOverFullScreenEkranın arkasındaki ekranı önlemek için ayarlamayı unutmayın view.
tounaobun

13

Bunu yapmanın süper basit bir yolunu buldum. Aşağıdaki kodu görünüm denetleyicinize koymanız yeterlidir:

Swift 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
    }
}

1
Teşekkürler, mükemmel çalışıyor! Arayüz oluşturucudaki görünüme bir Pan Gesture Recogniser bırakın ve yukarıdaki @IBAction ile bağlantı kurun.
balazs630

Swift 5'te de çalışıyor. @ Balazs630'un verdiği talimatları izleyin.
LondonGuy

Bunun en iyi yol olduğunu düşünüyorum.
Enkha

@Alex Shubin, ViewController'dan TabbarController'a sürüklendiğinde nasıl kapatılır?
Vadlapalli Masthan

12

Swift 4 için depoyu büyük ölçüde günceller .

İçin Swift 3 , ben şimdiki a aşağıdaki oluşturduk UIViewControllersağdan sola ve pan jest kapatmak için. Bunu bir GitHub deposu olarak yükledim .

görüntü açıklamasını buraya girin

DismissOnPanGesture.swift dosya:

//  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
    }
}

Kolay kullanım:

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!)
    }
}

1
Sadece müthiş! Paylaşım için teşekkürler.
pay Chauhan

Merhaba, Pan Gesture Recognizer kullanarak nasıl sunum yapabilirim lütfen bana yardım edin
Muralikrishna

Şu anda eğiticilerle bir youtube kanalı başlatıyorum. İOS 13+ / Swift 5 için bu sorunu ele almak için bir bölüm oluşturabilirim
David Seek

6

Açıkladığınız şey, etkileşimli bir özel geçiş animasyonudur . Bir geçişin hem animasyonunu hem de itici hareketini, yani sunulan bir görünüm denetleyicisinin kapatılmasını (veya kaldırılmasını) özelleştiriyorsunuz. Bunu uygulamanın en kolay yolu, bir UIPanGestureRecognizer'ı bir UIPercentDrivenInteractiveTransition ile birleştirmektir.

Kitabım bunun nasıl yapılacağını açıklıyor ve ben de (kitaptan) örnekler yayınladım. Bu özel örnek farklı bir durumdur - geçiş yanlamadır, aşağı değildir ve bir sekme çubuğu denetleyicisi içindir, sunulan bir denetleyici değildir - ancak temel fikir tamamen aynıdır:

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

Bu projeyi indirip çalıştırırsanız, tam olarak anlattığınız şey olduğunu göreceksiniz, ancak yanlamasına: Sürükleme yarıdan fazlaysa, geçiş yapıyoruz, ancak değilse, iptal edip geri dönüyoruz. yer.


3
404 Sayfa Bulunamadı.
tuzakçı

6

Yalnızca dikey kapatma

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!
            })
        }
    }

6

Kullanımı kolay bir uzantı oluşturdum.

Sadece InteractiveViewController ile olan UIViewController doğasında ve bitirdiniz InteractiveViewController

Etkileşimli olarak göstermek için denetleyicinizden showInteractive () yöntemini çağırın.

görüntü açıklamasını buraya girin


4

Hedef C'de: İşte kod

içindeviewDidLoad

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

kapatılmadan önce aşağı kaydırma süresini nasıl kontrol edebilirim?
TonyTony

2

İşte @Wilson cevabına göre yaptığım bir uzantı:

// 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
                })
            }
        }
    }

}

KULLANIM

Görünüm denetleyicinizin içinde kaydırılabilir olmasını istersiniz:

override func viewDidLoad() {
    super.viewDidLoad()

    self.isSwipable()
}

ve gezinme denetleyicisi olarak görünüm denetleyicisinin aşırı sol tarafından kaydırılarak kapatılacaktır.


Hey, kodunu kullandım ve doğru kaydırma için mükemmel çalışıyor ama aşağı kaydırın, bunu nasıl yapabilirim? Lütfen yardım et!
Khush

2

Bu , eksenden Drag ViewController için basit sınıfım . Sadece herited DraggableViewController adresinin sınıfı.

MyCustomClass: DraggableViewController

Yalnızca sunulan ViewController için çalışın.

// 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)
    }
}


0

Kullanıcının sürüklemesini algılamak ve bununla birlikte modal görünümü taşımak için bir UIPanGestureRecognizer kullanabilirsiniz. Bitiş konumu yeterince aşağıdaysa, görünüm kapatılabilir veya başka bir şekilde orijinal konumuna geri döndürülebilir.

Bunun gibi bir şeyin nasıl uygulanacağı hakkında daha fazla bilgi için bu yanıta bakın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.