рдХреНрдпрд╛ рдЖрдк рдЕрдиреБрдкреНрд░рдпреЛрдЧреЛрдВ рдореЗрдВ рдкреЙрдк-рдЕрдк рд╕реЗ рднреА рдирд╛рд░рд╛рдЬ рд╣реИрдВ? рдЗрд╕ рд▓реЗрдЦ рдореЗрдВ рдореИрдВ рджрд┐рдЦрд╛рдКрдВрдЧрд╛ рдХрд┐ рдХреИрд╕реЗ рдЗрдВрдЯрд░реИрдХреНрдЯрд┐рд╡ рддрд░реАрдХреЗ рд╕реЗ рдЫрд┐рдкрд╛рдиреЗ рдФрд░ рдкреЙрдк-рдЕрдк рджрд┐рдЦрд╛рдиреЗ рдХреЗ рд▓рд┐рдП, рдПрдиреАрдореЗрд╢рди рдХреЛ рд░реБрдХрд╛рд╡рдЯ рдмрдирд╛рдиреЗ рдФрд░ рдЕрдкрдиреЗ рдЧреНрд░рд╛рд╣рдХреЛрдВ рдХреЛ рд╕рдВрдХреНрд░рдорд┐рдд рдирд╣реАрдВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдПред

рдкрд┐рдЫрд▓реЗ рд▓реЗрдЦ рдореЗрдВ, рдореИрдВрдиреЗ рджреЗрдЦрд╛ рдХрд┐ рдЖрдк рдПрдХ рдирдП рдирд┐рдпрдВрддреНрд░рдХ рдХреЗ рдкреНрд░рджрд░реНрд╢рди рдХреЛ рдХреИрд╕реЗ рдЪреЗрддрди рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред
рд╣рдо рдЗрд╕ рддрдереНрдп рдкрд░ рдмрд╕реЗ рд╣реИрдВ рдХрд┐ viewController
рдПрдирд┐рдореЗрдЯреЗрдб рд░реВрдк рд╕реЗ рджрд┐рдЦрд╛ рдФрд░ рдЫрд┐рдкрд╛ рд╕рдХрддрд╛ рд╣реИ:

рдЕрдм рд╣рдо рдЙрд╕реЗ рдЫреБрдкрд╛рдиреЗ рдХреЗ рдЗрд╢рд╛рд░реЗ рдкрд░ рдЬрд╡рд╛рдм рджреЗрдирд╛ рд╕рд┐рдЦрд╛рдПрдБрдЧреЗред
рд╕рдВрд╡рд╛рджрд╛рддреНрдордХ рд╕рдВрдХреНрд░рдордг
рдПрдХ рдХрд░реАрдмреА рдЗрд╢рд╛рд░рд╛ рдЬреЛрдбрд╝реЗрдВ
рдирд┐рдпрдВрддреНрд░рдХ рдХреЛ рдЕрдВрддрдГрдХреНрд░рд┐рдпрд╛рддреНрдордХ рд░реВрдк рд╕реЗ рдмрдВрдж рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕рд┐рдЦрд╛рдиреЗ рдХреЗ рд▓рд┐рдП, рдЖрдкрдХреЛ рдПрдХ рдЗрд╢рд╛рд░рд╛ рдЬреЛрдбрд╝рдиреЗ рдФрд░ рдЗрд╕реЗ рд╕рдВрд╕рд╛рдзрд┐рдд рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред рд╕рднреА рдХрд╛рдо TransitionDriver
рд╡рд░реНрдЧ рдореЗрдВ рд╣реЛрдВрдЧреЗ:
class TransitionDriver: UIPercentDrivenInteractiveTransition { func link(to controller: UIViewController) { presentedController = controller panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handle(recognizer:))) presentedController?.view.addGestureRecognizer(panRecognizer!) } private var presentedController: UIViewController? private var panRecognizer: UIPanGestureRecognizer? }
рдЖрдк рдкреИрдирд▓ рд╕рдВрдЪрд╛рд▓рдХ рдХреЗ рдЕрдВрджрд░, DimmPresentationController рдХреЗ рд╕реНрдерд╛рди рдкрд░ рдПрдХ рд╣реИрдВрдбрд▓рд░ рд╕рдВрд▓рдЧреНрди рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ:
private let driver = TransitionDriver() func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { driver.link(to: presented) let presentationController = DimmPresentationController(presentedViewController: presented, presenting: presenting) return presentationController }
рдЙрд╕реА рд╕рдордп, рдЖрдкрдХреЛ рдпрд╣ рдЗрдВрдЧрд┐рдд рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ рдХрд┐ рдЫрд┐рдкрд╛рдИ рдкреНрд░рдмрдВрдзрдиреАрдп рд╣реЛ рдЧрдИ рд╣реИ (рд╣рдордиреЗ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдкрд┐рдЫрд▓реЗ рд▓реЗрдЦ рдореЗрдВ рдРрд╕рд╛ рдХрд┐рдпрд╛ рд╣реИ):
рдЗрд╢рд╛рд░рд╛ рд╕рдВрднрд╛рд▓реЛ
рдЪрд▓реЛ рд╕рдорд╛рдкрди рдЗрд╢рд╛рд░реЗ рдХреЗ рд╕рд╛рде рд╢реБрд░реВ рдХрд░рддреЗ рд╣реИрдВ: рдпрджрд┐ рдЖрдк рдкреИрдирд▓ рдХреЛ рдиреАрдЪреЗ рдЦреАрдВрдЪрддреЗ рд╣реИрдВ, рддреЛ рд╕рдорд╛рдкрди рдПрдиреАрдореЗрд╢рди рд╢реБрд░реВ рд╣реЛ рдЬрд╛рдПрдЧрд╛, рдФрд░ рдЙрдВрдЧрд▓реА рдХреА рдЧрддрд┐ рдмрдВрдж рд╣реЛрдиреЗ рдХреА рдбрд┐рдЧреНрд░реА рдХреЛ рдкреНрд░рднрд╛рд╡рд┐рдд рдХрд░реЗрдЧреАред
UIPercentDrivenInteractiveTransition
рд╕рдВрдХреНрд░рдордг рдПрдиреАрдореЗрд╢рди рдХреЛ рдкрдХрдбрд╝рдиреЗ рдФрд░ рдореИрдиреНрдпреБрдЕрд▓ рд░реВрдк рд╕реЗ рдирд┐рдпрдВрддреНрд░рд┐рдд рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИред рдЗрд╕рдореЗрдВ update
, finish
, cancel
рддрд░реАрдХреЗ рд╣реИрдВред рдЗрд╕рдХреЗ рдЙрдкрд╡рд░реНрдЧ рдореЗрдВ рдЬреЗрд╕реНрдЪрд░ рдкреНрд░реЛрд╕реЗрд╕рд┐рдВрдЧ рдХрд░рдирд╛ рд╕реБрд╡рд┐рдзрд╛рдЬрдирдХ рд╣реИред
рдЬреЗрд╕реНрдЪрд░ рдкреНрд░рд╕рдВрд╕реНрдХрд░рдг
private func handleDismiss(recognizer r: UIPanGestureRecognizer) { switch r.state { case .began: pause()
.begin
рд╕рдмрд╕реЗ рд╕рд╛рдорд╛рдиреНрдп рддрд░реАрдХреЗ рд╕реЗ рдкреНрд░рд╡реЗрд╢ рд╢реБрд░реВ рдХрд░реЗрдВред рд╣рдордиреЗ рд▓рд┐рдВрдХ рдореЗрдВ link(to:)
рд╡рд┐рдзрд┐ link(to:)
рд╡рд┐рдзрд┐ рдореЗрдВ рд╕рд╣реЗрдЬрд╛ рд╣реИ
.changed
рд╡реЗрддрди рд╡реГрджреНрдзрд┐ рдХреА рдЧрдгрдирд╛ рдХрд░реЗрдВ рдФрд░ рдЗрд╕реЗ update
рд╡рд┐рдзрд┐ рдореЗрдВ рдкрд╛рд╕ рдХрд░реЗрдВред рд╕реНрд╡реАрдХреГрдд рдорд╛рди 0 рд╕реЗ 1 рддрдХ рднрд┐рдиреНрди рд╣реЛ рд╕рдХрддрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП рд╣рдо interactionControllerForDismissal(using:)
рдПрдиреАрдореЗрд╢рди interactionControllerForDismissal(using:)
рд╡рд┐рдзрд┐ рд╕реЗ рдПрдиреАрдореЗрд╢рди рдХреЗ рдкреВрд░рд╛ рд╣реЛрдиреЗ рдХреА рдбрд┐рдЧреНрд░реА рдХреЛ рдирд┐рдпрдВрддреНрд░рд┐рдд рдХрд░реЗрдВрдЧреЗред рдЧрдгрдирд╛ рдЗрд╢рд╛рд░реЛрдВ рдХреЗ рд╡рд┐рд╕реНрддрд╛рд░ рдореЗрдВ рдХреА рдЧрдИ рдереА, рддрд╛рдХрд┐ рдХреЛрдб рдХреНрд▓реАрдирд░ рд╣реЛ рдЬрд╛рдПред
рдЗрд╢рд╛рд░реЗ рдХреА рдЧрдгрдирд╛ private extension UIPanGestureRecognizer { func incrementToBottom(maxTranslation: CGFloat) -> CGFloat { let translation = self.translation(in: view).y setTranslation(.zero, in: nil) let percentIncrement = translation / maxTranslation return percentIncrement } }
рдЧрдгрдирд╛ maxTranslation
рдкрд░ рдЖрдзрд╛рд░рд┐рдд рд╣реИрдВ, рд╣рдо рдЗрд╕реЗ рдкреНрд░рджрд░реНрд╢рд┐рдд рдирд┐рдпрдВрддреНрд░рдХ рдХреА рдКрдВрдЪрд╛рдИ рдХреЗ рд░реВрдк рдореЗрдВ рдЧрдгрдирд╛ рдХрд░рддреЗ рд╣реИрдВ:
var maxTranslation: CGFloat { return presentedController?.view.frame.height ?? 0 }
.end
рд╣рдо рдЗрд╢рд╛рд░реЗ рдХреА рдкреВрд░реНрдгрддрд╛ рдХреЛ рджреЗрдЦрддреЗ рд╣реИрдВред рдкреВрд░реНрдгрддрд╛ рдХрд╛ рдирд┐рдпрдо: рдпрджрд┐ рдЖрдзреЗ рд╕реЗ рдЕрдзрд┐рдХ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рд╣реЛ рдЧрдпрд╛ рд╣реИ, рддреЛ рдмрдВрдж рдХрд░реЗрдВред рдЗрд╕ рдорд╛рдорд▓реЗ рдореЗрдВ, рдСрдлрд╕реЗрдЯ рдХреЛ рди рдХреЗрд╡рд▓ рд╡рд░реНрддрдорд╛рди рд╕рдордиреНрд╡рдп рджреНрд╡рд╛рд░рд╛, рдмрд▓реНрдХрд┐ velocity
рднреА рдорд╛рдирд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдПред рдЗрд╕рд▓рд┐рдП рд╣рдо рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЗ рдЗрд░рд╛рджреЗ рдХреЛ рд╕рдордЭрддреЗ рд╣реИрдВ: рд╡рд╣ рдмреАрдЪ рдореЗрдВ рд╕рдорд╛рдкреНрдд рдирд╣реАрдВ рд╣реЛ рд╕рдХрддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдмрд╣реБрдд рдиреАрдЪреЗ рд╕реНрд╡рд╛рдЗрдк рдХрд░реЗрдВред рдпрд╛ рдЗрд╕рдХреЗ рд╡рд┐рдкрд░реАрдд: рдиреАрдЪреЗ рд▓реЗ, рд▓реЗрдХрд┐рди рд╡рд╛рдкрд╕ рд▓реМрдЯрдиреЗ рдХреЗ рд▓рд┐рдП рд╕реНрд╡рд╛рдЗрдк рдХрд░реЗрдВред
рдкреНрд░рдХреНрд╖реЗрдкрд┐рдд рдЧрдгрдирд╛ private extension UIPanGestureRecognizer { func isProjectedToDownHalf(maxTranslation: CGFloat) -> Bool { let endLocation = projectedLocation(decelerationRate: .fast) let isPresentationCompleted = endLocation.y > maxTranslation / 2 return isPresentationCompleted } func projectedLocation(decelerationRate: UIScrollView.DecelerationRate) -> CGPoint { let velocityOffset = velocity(in: view).projectedOffset(decelerationRate: .normal) let projectedLocation = location(in: view!) + velocityOffset return projectedLocation } } extension CGPoint { func projectedOffset(decelerationRate: UIScrollView.DecelerationRate) -> CGPoint { return CGPoint(x: x.projectedOffset(decelerationRate: decelerationRate), y: y.projectedOffset(decelerationRate: decelerationRate)) } } extension CGFloat {
.cancelled
- рдпрджрд┐ рдЖрдк рдлреЛрди рд╕реНрдХреНрд░реАрди рдХреЛ рд▓реЙрдХ рдХрд░рддреЗ рд╣реИрдВ рдпрд╛ рдпрджрд┐ рд╡реЗ рдХреЙрд▓ рдХрд░рддреЗ рд╣реИрдВ рддреЛ рдХреНрдпрд╛ рд╣реЛрдЧрд╛ред рдЖрдк рдЗрд╕реЗ рдПрдХ .ended
рдмреНрд▓реЙрдХ рдХреЗ рд░реВрдк рдореЗрдВ рд╕рдВрднрд╛рд▓ рд╕рдХрддреЗ рд╣реИрдВ рдпрд╛ рдПрдХ рдХрд╛рд░реНрд░рд╡рд╛рдИ рдХреЛ рд░рджреНрдж рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред
.failed
- рдЕрдЧрд░ рдЗрд╢рд╛рд░реЗ рдХреЛ рджреВрд╕рд░реЗ рдЗрд╢рд╛рд░реЗ рд╕реЗ рд░рджреНрдж рдХрд░ рджрд┐рдпрд╛ рдЬрд╛рдП рддреЛ рдХреНрдпрд╛ рд╣реЛрдЧрд╛ред рдЗрд╕рд▓рд┐рдП, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдПрдХ рдбреНрд░реИрдЧ рдЬреЗрд╕реНрдЪрд░ рдПрдХ рдЯреИрдк рдЬреЗрд╕реНрдЪрд░ рдХреЛ рд░рджреНрдж рдХрд░ рд╕рдХрддрд╛ рд╣реИред
.possible
- рдЗрд╢рд╛рд░рд╛ рдХреА рдкреНрд░рд╛рд░рдВрднрд┐рдХ рд╕реНрдерд┐рддрд┐, рдЖрдорддреМрд░ рдкрд░ рдмрд╣реБрдд рдХрд╛рдо рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдирд╣реАрдВ рд╣реЛрддреА рд╣реИред
рдЕрдм рдкреИрдирд▓ рдХреЛ рдПрдХ рд╕реНрд╡рд╛рдЗрдк рдХреЗ рд╕рд╛рде рднреА рдмрдВрдж рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди dismiss
рдмрдЯрди dismiss
ред рдпрд╣ рдЗрд╕рд▓рд┐рдП рд╣реБрдЖ рдХреНрдпреЛрдВрдХрд┐ TransitionDriver
рдореЗрдВ рдПрдХ wantsInteractiveStart
рд╡рд╛рд▓рд╛ wantsInteractiveStart
рдЧреБрдг рд╣реИ, рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд░реВрдк рд╕реЗ рдпрд╣ true
ред рдпрд╣ рдПрдХ рд╕реНрд╡рд╛рдЗрдк рдХреЗ рд▓рд┐рдП рд╕рд╛рдорд╛рдиреНрдп рд╣реИ, рд▓реЗрдХрд┐рди рдпрд╣ рд╕рд╛рдорд╛рдиреНрдп dismiss
рдЕрд╡рд░реБрджреНрдз рдХрд░рддрд╛ рд╣реИред
рдЖрдЗрдП рдЗрд╢рд╛рд░реЗ рдХреА рд╕реНрдерд┐рддрд┐ рдХреЗ рдЖрдзрд╛рд░ рдкрд░ рд╡реНрдпрд╡рд╣рд╛рд░ рдХреЛ рддреЛрдбрд╝ рджреЗрдВред рдпрджрд┐ рдЗрд╢рд╛рд░рд╛ рд╢реБрд░реВ рд╣реБрдЖ рд╣реИ, рддреЛ рдпрд╣ рдПрдХ рдЗрдВрдЯрд░рдПрдХреНрдЯрд┐рд╡ рдХрд░реАрдм рд╣реИ, рдФрд░ рдЕрдЧрд░ рдпрд╣ рд╢реБрд░реВ рдирд╣реАрдВ рд╣реБрдЖ, рддреЛ рд╕рд╛рдорд╛рдиреНрдп рдПрдХ:
override var wantsInteractiveStart: Bool { get { let gestureIsActive = panRecognizer?.state == .began return gestureIsActive } set { } }
рдЕрдм рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдЫрд┐рдкрд╛рдиреЗ рдХреЛ рдирд┐рдпрдВрддреНрд░рд┐рдд рдХрд░ рд╕рдХрддрд╛ рд╣реИ:

рд░реБрдХрд╛рд╡рдЯ рд╕рдВрдХреНрд░рдордг
рдорд╛рдирд╛ рдХрд┐ рд╣рдордиреЗ рдЕрдкрдирд╛ рдХрд╛рд░реНрдб рдмрдВрдж рдХрд░рдирд╛ рд╢реБрд░реВ рдХрд░ рджрд┐рдпрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдЕрдкрдирд╛ рд╡рд┐рдЪрд╛рд░ рдмрджрд▓ рджрд┐рдпрд╛ рд╣реИ рдФрд░ рд╡рд╛рдкрд╕ рд▓реМрдЯрдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВред рдпрд╣ рд╕рд░рд▓ рд╣реИ: рдПрдХ .began
рд░рд╛рдЬреНрдп рдореЗрдВ, .began
pause()
рдХрд╣рддреЗ рд╣реИрдВред
рд▓реЗрдХрд┐рди рдЖрдкрдХреЛ рджреЛ рдкрд░рд┐рджреГрд╢реНрдпреЛрдВ рдХреЛ рдЕрд▓рдЧ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ:
- рдЬрдм рд╣рдо рдЗрд╢рд╛рд░реЗ рд╕реЗ рдЫрд┐рдкрдирд╛ рд╢реБрд░реВ рдХрд░рддреЗ рд╣реИрдВ;
- рдЬрдм рд╣рдо рд╡рд░реНрддрдорд╛рди рдХреЛ рдмрд╛рдзрд┐рдд рдХрд░рддреЗ рд╣реИрдВред
рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд░реЛрдХрдиреЗ рдХреЗ рдмрд╛рдж, percentComplete:
рдЬрд╛рдВрдЪ рдХрд░реЗрдВ percentComplete:
рдпрджрд┐ рдпрд╣ 0 рд╣реИ, рддреЛ рд╣рдо рдХрд╛рд░реНрдб рдХреЛ рдореИрдиреНрдпреБрдЕрд▓ рд░реВрдк рд╕реЗ рдмрдВрдж рдХрд░рдирд╛ рд╢реБрд░реВ рдХрд░рддреЗ рд╣реИрдВ, рд╕рд╛рде рд╣реА рд╣рдореЗрдВ dismiss
рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред рдпрджрд┐ рдпрд╣ 0 рдирд╣реАрдВ рд╣реИ, рддреЛ рдЫрд┐рдкрд╛рдирд╛ рдкрд╣рд▓реЗ рд╣реА рд╢реБрд░реВ рд╣реЛ рдЧрдпрд╛ рд╣реИ, рдпрд╣ рд╕рд┐рд░реНрдл рдПрдиреАрдореЗрд╢рди рдХреЛ рд░реЛрдХрдиреЗ рдХреЗ рд▓рд┐рдП рдкрд░реНрдпрд╛рдкреНрдд рд╣реИ:
case .began: pause()
рдореИрдВ рдмрдЯрди рджрдмрд╛рддрд╛ рд╣реВрдВ рдФрд░ рддреБрд░рдВрдд рдЫрд┐рдкрд╛рдиреЗ рдХреЗ рд▓рд┐рдП рд░рджреНрдж рдХрд░рддрд╛ рд╣реВрдВ:

рдирд┐рдпрдВрддреНрд░рдХ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдирд╛ рдмрдВрдж рдХрд░реЗрдВ
рд░рд┐рд╡рд░реНрд╕ рд╕реНрдерд┐рддрд┐: рдХрд╛рд░реНрдб рджрд┐рдЦрд╛рдИ рджреЗрдиреЗ рд▓рдЧрд╛, рд▓реЗрдХрд┐рди рд╣рдореЗрдВ рдЗрд╕рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдирд╣реАрдВ рд╣реИред рд╣рдо рдЗрд╕реЗ рдкрдХрдбрд╝рддреЗ рд╣реИрдВ рдФрд░ рдЗрд╕реЗ рдиреАрдЪреЗ рд╕реНрд╡рд╛рдЗрдк рдХрд░рдХреЗ рднреЗрдЬрддреЗ рд╣реИрдВред рдЖрдк рдПрдХ рд╣реА рдЪрд░рдгреЛрдВ рдореЗрдВ рдирд┐рдпрдВрддреНрд░рдХ рдкреНрд░рджрд░реНрд╢рди рдХреЗ рдПрдиреАрдореЗрд╢рди рдХреЛ рдмрд╛рдзрд┐рдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред
рдбреНрд░рд╛рдЗрд╡рд░ рдХреЛ рдПрдХ рдЗрдВрдЯрд░реИрдХреНрдЯрд┐рд╡ рдбрд┐рд╕реНрдкреНрд▓реЗ рдХрдВрдЯреНрд░реЛрд▓рд░ рдХреЗ рд░реВрдк рдореЗрдВ рд▓реМрдЯрд╛рдПрдБ:
func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return driver }
рдЗрд╢рд╛рд░реЗ рдХреЛ рд╕рдВрд╕рд╛рдзрд┐рдд рдХрд░реЗрдВ, рд▓реЗрдХрд┐рди рд░рд┐рд╡рд░реНрд╕ рдкреВрд░реНрд╡рд╛рдЧреНрд░рд╣ рдФрд░ рдкреВрд░реНрдгрддрд╛ рдореВрд▓реНрдпреЛрдВ рдХреЗ рд╕рд╛рде:
private func handlePresentation(recognizer r: UIPanGestureRecognizer) { switch r.state { case .began: pause() case .changed: let increment = -r.incrementToBottom(maxTranslation: maxTranslation) update(percentComplete + increment) case .ended, .cancelled: if r.isProjectedToDownHalf(maxTranslation: maxTranslation) { cancel() } else { finish() } case .failed: cancel() default: break } }
рдЕрд▓рдЧ рджрд┐рдЦрд╛рдиреЗ рдФрд░ рдЫрд┐рдкрд╛рдиреЗ рдХреЗ рд▓рд┐рдП, рдореИрдВрдиреЗ рд╡рд░реНрддрдорд╛рди рдПрдиреАрдореЗрд╢рди рджрд┐рд╢рд╛ рдХреЗ рд╕рд╛рде рдПрдирдо рдореЗрдВ рдкреНрд░рд╡реЗрд╢ рдХрд┐рдпрд╛:
enum TransitionDirection { case present, dismiss }
рд╕рдВрдкрддреНрддрд┐ TransitionDriver
рдореЗрдВ рд╕рдВрдЧреНрд░рд╣реАрдд рдХреА рдЬрд╛рддреА рд╣реИ рдФрд░ рдпрд╣ рдкреНрд░рднрд╛рд╡рд┐рдд рдХрд░рддреА рд╣реИ рдХрд┐ рдХреМрди рд╕рд╛ рдЗрд╢рд╛рд░рд╛ рд╣реИрдВрдбрд▓рд░ рдЗрд╕реНрддреЗрдорд╛рд▓ рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛:
var direction: TransitionDirection = .present @objc private func handle(recognizer r: UIPanGestureRecognizer) { switch direction { case .present: handlePresentation(recognizer: r) case .dismiss: handleDismiss(recognizer: r) } }
рдпрд╣ wantsInteractiveStart
рднреА рдЕрд╕рд░ wantsInteractiveStart
ред рд╣рдо рдирд┐рдпрдВрддреНрд░рдХ рдХреЛ рдЗрд╢рд╛рд░реЗ рд╕реЗ рджрд┐рдЦрд╛рдиреЗ рдХреА рдпреЛрдЬрдирд╛ рдирд╣реАрдВ рдмрдирд╛рддреЗ рд╣реИрдВ, рдЗрд╕рд▓рд┐рдП рд╣рдоред
override var wantsInteractiveStart: Bool { get { switch direction { case .present: return false case .dismiss: let gestureIsActive = panRecognizer?.state == .began return gestureIsActive } } set { } }
рдЦреИрд░, рдпрд╣ рдЙрд╕ рджрд┐рд╢рд╛ рдХреА рджрд┐рд╢рд╛ рдмрджрд▓рдиреЗ рдХреЗ рд▓рд┐рдП рд░рд╣рддрд╛ рд╣реИ рдЬрдм рдирд┐рдпрдВрддреНрд░рдХ рдкреВрд░реА рддрд░рд╣ рд╕реЗ рджрд┐рдЦрд╛рдпрд╛ рдЧрдпрд╛ рдерд╛ред рд╕рдмрд╕реЗ рдЕрдЪреНрдЫрд╛ рд╕реНрдерд╛рди PresentationController
:
override func presentationTransitionDidEnd(_ completed: Bool) { super.presentationTransitionDidEnd(completed) if completed { driver.direction = .dismiss } }
рдХреНрдпрд╛ рдпрд╣ рдПрдирдо рдХреЗ рдмрд┐рдирд╛ рд╕рдВрднрд╡ рд╣реИ?рдРрд╕рд╛ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рд╣рдо рдирд┐рдпрдВрддреНрд░рдХ рдХреЗ рдЧреБрдгреЛрдВ рдкрд░ рднрд░реЛрд╕рд╛ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рд▓реЗрдХрд┐рди рд╡реЗ рдХреЗрд╡рд▓ рдкреНрд░рдХреНрд░рд┐рдпрд╛ рджрд┐рдЦрд╛рддреЗ рд╣реИрдВ, рдФрд░ рд╣рдореЗрдВ рд╕рдВрднрд╛рд╡рд┐рдд рджрд┐рд╢рд╛рдУрдВ рдХреА рднреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ: рдЗрдВрдЯрд░реИрдХреНрдЯрд┐рд╡ рд╕рдорд╛рдкрди рдХреА рд╢реБрд░реБрдЖрдд рдореЗрдВ, рджреЛрдиреЛрдВ рдореВрд▓реНрдп false
, рдФрд░ рд╣рдореЗрдВ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдпрд╣ рдЬрд╛рдирдирд╛ рд╣реЛрдЧрд╛ рдХрд┐ рдпрд╣ рд╕рдорд╛рдкрди рдХреА рджрд┐рд╢рд╛ рд╣реИред рдпрд╣ рдирд┐рдпрдВрддреНрд░рдХреЛрдВ рдХреЗ рдкрджрд╛рдиреБрдХреНрд░рдо рдХреА рдЬрд╛рдВрдЪ рдХреЗ рд▓рд┐рдП рдЕрддрд┐рд░рд┐рдХреНрдд рд╢рд░реНрддреЛрдВ рджреНрд╡рд╛рд░рд╛ рд╣рд▓ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди enum
рдорд╛рдзреНрдпрдо рд╕реЗ рд╕реНрдкрд╖реНрдЯ рдЕрд╕рд╛рдЗрдирдореЗрдВрдЯ рдПрдХ рд╕рд░рд▓ рд╕рдорд╛рдзрд╛рди рд▓рдЧрддрд╛ рд╣реИред
рдЕрдм рдЖрдк рд╢реЛ рдХреЗ рдПрдиреАрдореЗрд╢рди рдХреЛ рдмрд╛рдзрд┐рдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдореИрдВ рдмрдЯрди рджрдмрд╛рддрд╛ рд╣реВрдВ рдФрд░ рддреБрд░рдВрдд рдиреАрдЪреЗ рд╕реНрд╡рд╛рдЗрдк рдХрд░рддрд╛ рд╣реВрдВ:

рдЗрд╢рд╛рд░реЗ рд╕реЗ рджрд┐рдЦрд╛рдирд╛
рдпрджрд┐ рдЖрдк рдХрд┐рд╕реА рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЗ рд▓рд┐рдП рдПрдХ рд╣реИрдордмрд░реНрдЧрд░ рдореЗрдиреВ рдмрдирд╛ рд░рд╣реЗ рд╣реИрдВ, рддреЛ рд╕рдмрд╕реЗ рдЕрдзрд┐рдХ рд╕рдВрднрд╛рд╡рдирд╛ рд╣реИ рдХрд┐ рдЖрдк рдЗрд╕реЗ рдЗрд╢рд╛рд░реЗ рд╕реЗ рджрд┐рдЦрд╛рдирд╛ рдЪрд╛рд╣реЗрдВрдЧреЗред рдпрд╣ рдЗрдВрдЯрд░реЗрдХреНрдЯрд┐рд╡ рдЫрд┐рдкрд╛рдиреЗ рдХреА рддрд░рд╣ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдПрдХ рдЗрд╢рд╛рд░рд╛ рдореЗрдВ, dismiss
рдмрдЬрд╛рдп dismiss
present
рдХрд╣рддреЗ present
ред
рдЪрд▓рд┐рдП рдЕрдВрдд рд╕реЗ рд╢реБрд░реВ рдХрд░рддреЗ рд╣реИрдВред handlePresentation(recognizer:)
рдирд┐рдпрдВрддреНрд░рдХ рджрд┐рдЦрд╛рдПрдВ:
case .began: pause() let isRunning = percentComplete != 0 if !isRunning { presentingController?.present(presentedController!, animated: true) }
рдЕрдВрддрдГрдХреНрд░рд┐рдпрд╛рддреНрдордХ рд░реВрдк рд╕реЗ рджрд┐рдЦрд╛рдПрдВ:
override var wantsInteractiveStart: Bool { get { switch direction { case .present: let gestureIsActive = screenEdgePanRecognizer?.state == .began return gestureIsActive case .dismiss: тАж }
рдХреЛрдб рдХреЛ рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, presentingController
рдФрд░ presentedController
presentingController
рд▓рд┐рдП рдкрд░реНрдпрд╛рдкреНрдд рд▓рд┐рдВрдХ рдирд╣реАрдВ рд╣реИрдВред рдЗрд╢рд╛рд░реЗ рдмрдирд╛рддреЗ рд╕рдордп рд╣рдо рдЙрдиреНрд╣реЗрдВ рдкрд╛рд╕ рдХрд░реЗрдВрдЧреЗ, UIScreenEdgePanGestureRecognizer
рдЬреЛрдбрд╝реЗрдВ:
func linkPresentationGesture(to presentedController: UIViewController, presentingController: UIViewController) { self.presentedController = presentedController self.presentingController = presentingController
PanelTransition
рдмрдирд╛рддреЗ рд╕рдордп рдЖрдк рдХрдВрдЯреНрд░реЛрд▓рд░ рдЯреНрд░рд╛рдВрд╕рдлрд░ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ:
class PanelTransition: NSObject, UIViewControllerTransitioningDelegate { init(presented: UIViewController, presenting: UIViewController) { driver.linkPresentationGesture(to: presented, presentingController: presenting) } private let driver = TransitionDriver() }
рдпрд╣ PanelTransition
рд╕реЗ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдмрдирд╛ PanelTransition
:
- рдЖрдЗрдП
viewDidLoad
рдореЗрдВ рдПрдХ child
рдХрдВрдЯреНрд░реЛрд▓рд░ рдмрдирд╛рдПрдВ, рдХреНрдпреЛрдВрдХрд┐ рд╣рдореЗрдВ рдХрд┐рд╕реА рднреА рд╕рдордп рдХрдВрдЯреНрд░реЛрд▓рд░ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛ рд╕рдХрддреА рд╣реИред PanelTransition
рдмрдирд╛рдПрдВред рдЙрдирдХреЗ рдирд┐рд░реНрдорд╛рддрд╛ рдореЗрдВ, рдЗрд╢рд╛рд░рд╛ рдирд┐рдпрдВрддреНрд░рдХ рд╕реЗ рдмрдВрдзрд╛ рд╣реБрдЖ рд╣реИред- рдмрдЪреНрдЪреЗ рдирд┐рдпрдВрддреНрд░рдХ рдХреЗ рд▓рд┐рдП рдЯреНрд░рд╛рдВрдЬреАрд╢рдирд┐рдВрдЧрдбреЗрд▓рдЧреЗрдЯ рдиреАрдЪреЗ рд░рдЦреЗрдВред
рдкреНрд░рд╢рд┐рдХреНрд╖рдг рдЙрджреНрджреЗрд╢реНрдпреЛрдВ рдХреЗ рд▓рд┐рдП, рдореИрдВ рдиреАрдЪреЗ рд╕реЗ рд╕реНрд╡рд╛рдЗрдк рдХрд░рддрд╛ рд╣реВрдВ, рд▓реЗрдХрд┐рди рдпрд╣ iPhone X рдФрд░ рдирд┐рдпрдВрддреНрд░рдг рдХреЗрдВрджреНрд░ рдкрд░ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЛ рдмрдВрдж рдХрд░рдиреЗ рдХреЗ рд╕рд╛рде рд╕рдВрдШрд░реНрд╖ рдХрд░рддрд╛ рд╣реИред рд╡рд░реАрдпрд╕реНрдХреНрд░реАрдирд╕реНрдХреНрд░реАрди рдХрд╛ рдЙрдкрдпреЛрдЧ preferredScreenEdgesDeferringSystemGestures
рд╕рд┐рд╕реНрдЯрдорд╕рд┐рд╕реНрдЯрдордЧреИрд╕реНрдЯ рдиреЗ рд╕рд┐рд╕реНрдЯрдо рдХреЛ рдиреАрдЪреЗ рд╕реЗ рд╕реНрд╡рд╛рдЗрдк рдХрд░рдиреЗ рдореЗрдВ рдЕрдХреНрд╖рдо рдХрд░ рджрд┐рдпрд╛ред
class ParentViewController: UIViewController { private var child: ChildViewController! private var transition: PanelTransition! override func viewDidLoad() { super.viewDidLoad() child = ChildViewController()
рдкрд░рд┐рд╡рд░реНрддрди рдХреЗ рдмрд╛рдж, рдпрд╣ рдкрддрд╛ рдЪрд▓рд╛ рдХрд┐ рдПрдХ рд╕рдорд╕реНрдпрд╛ рдереА: рдкреИрдирд▓ рдХреЗ рдкрд╣рд▓реЗ рд╕рдорд╛рдкрди рдХреЗ рдмрд╛рдж, рдпрд╣ рд╣рдореЗрд╢рд╛ рдХреЗ рд▓рд┐рдП TransitionDirection.dismiss
рдХреА рд╕реНрдерд┐рддрд┐ рдореЗрдВ рд░рд╣рддрд╛ рд╣реИред PresentationController
рдореЗрдВ рдирд┐рдпрдВрддреНрд░рдХ рдХреЛ рдЫрд┐рдкрд╛рдиреЗ рдХреЗ рдмрд╛рдж рд╕рд╣реА рд╕реНрдерд┐рддрд┐ рд╕реЗрдЯ рдХрд░реЗрдВ:
override func dismissalTransitionDidEnd(_ completed: Bool) { super.dismissalTransitionDidEnd(completed) if completed { driver.direction = .present } }
рдЗрдВрдЯрд░рдПрдХреНрдЯрд┐рд╡ рдбрд┐рд╕реНрдкреНрд▓реЗ рдХреЛрдб рдХреЛ рдПрдХ рдЕрд▓рдЧ рдереНрд░реЗрдб рдореЗрдВ рджреЗрдЦрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред рдпрд╣ рдЗрд╕ рддрд░рд╣ рджрд┐рдЦрддрд╛ рд╣реИ:

рдирд┐рд╖реНрдХрд░реНрд╖
рдирддреАрдЬрддрди, рд╣рдо рдирд┐рдпрдВрддреНрд░рдХ рдХреЛ рдмрд╛рдзрд┐рдд рдПрдиреАрдореЗрд╢рди рдХреЗ рд╕рд╛рде рджрд┐рдЦрд╛ рд╕рдХрддреЗ рд╣реИрдВ, рдФрд░ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЛ рд╕реНрдХреНрд░реАрди рдкрд░ рдХреНрдпрд╛ рд╣реЛ рд░рд╣рд╛ рд╣реИ, рдЗрд╕ рдкрд░ рдирд┐рдпрдВрддреНрд░рдг рд╣реИред рдпрд╣ рдмрд╣реБрдд рдЕрдЪреНрдЫрд╛ рд╣реИ, рдХреНрдпреЛрдВрдХрд┐ рдПрдиреАрдореЗрд╢рди рдЕрдм рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдХреЛ рдмреНрд▓реЙрдХ рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИ, рдЗрд╕реЗ рд░рджреНрдж рдпрд╛ рддреНрд╡рд░рд┐рдд рднреА рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред
рдПрдХ рдЙрджрд╛рд╣рд░рдг рдЧрд┐рддреБрдм рдкрд░ рджреЗрдЦрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ ред
рдбреЛрдбреЛ рдкрд┐рдЬреНрдЬрд╛ рдореЛрдмрд╛рдЗрд▓ рдЪреИрдирд▓ рдХреА рд╕рджрд╕реНрдпрддрд╛ рд▓реЗрдВ ред