Push view controller over PHPickerViewController - ios

Is that possible to push vc over the PHPickerViewController?
I'm trying to do that like this with no luck:
var configuration = PHPickerConfiguration()
configuration.filter = .any(of: [.images, .livePhotos])
photoPickerController = PHPickerViewController(configuration: configuration)
photoPickerController.delegate = self
present(self.photoPickerController, animated: true)
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
//Push segue
performSegue(withIdentifier: "showAddPost", sender: self)
}

Update - Using CocoaPods
I created a simple pod PhotoCropController that includes a basic photo crop controller to be presented from the PHPickerViewControllerDelegate. It provides transitions to push to a modally presented controller and to pop or dismiss. The aspect ratio of the crop view can be edited as well. To use conform your view controller to the PhotoCropDelegate protocol and present the PhotoCropController from your PHPickerViewControllerDelegate. Implementation would look something like the following:
extension ViewController: PHPickerViewControllerDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate, PhotoCropDelegate {
func browsePhotoLibrary() {
if #available(iOS 14, *) {
var config = PHPickerConfiguration()
config.filter = PHPickerFilter.images
config.selectionLimit = 1
config.preferredAssetRepresentationMode = .compatible
let picker = PHPickerViewController(configuration: config)
picker.delegate = self
let nav = UINavigationController(rootViewController: picker)
nav.setNavigationBarHidden(true, animated: false)
nav.setToolbarHidden(true, animated: true)
present(nav, animated: true) } else {
let picker = UIImagePickerController()
picker.delegate = self
picker.allowsEditing = true
present(picker, animated: true)
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let edited = info[UIImagePickerController.InfoKey.editedImage] as? UIImage {
image = edited
} else if let selected = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
image = selected
}
presentedViewController?.dismiss(animated: true)
}
#available(iOS 14, *)
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
if let provider = results.last?.itemProvider,
provider.canLoadObject(ofClass: UIImage.self) {
provider.loadObject(ofClass: UIImage.self) { [weak self] result, error in
if let image = result as? UIImage {
DispatchQueue.main.async { self?.select(image: image) }
} else if let error = error {
NSLog("Error picking image: %#", error.localizedDescription)
DispatchQueue.main.async { picker.dismiss(animated: true) }
}
}
} else { DispatchQueue.main.async { picker.dismiss(animated: true) } }
}
func select(image: UIImage) {
let destinationSize = AVMakeRect(aspectRatio: image.size, insideRect: view.frame).integral.size
//Best Performance
let resizedImage = UIGraphicsImageRenderer(size: destinationSize).image { (context) in
image.draw(in: CGRect(origin: .zero, size: destinationSize))
}
let cropController = PhotoCropController()
cropController.delegate = self
cropController.image = resizedImage
presentedViewController?.present(cropController, animated: true)
}
#objc func cropViewDidCrop(image: UIImage?) {
self.image = image
presentedViewController?.dismiss(animated: true) { [weak self] in
self?.presentedViewController?.dismiss(animated: true)
}
}
}
To present another controller modally in front of the PHPickerViewController:
The presentedViewController property of your view controller will be your photoPickerController so you can present another controller in front of it as follows:
present(photoPickerController, animated: true)
presentedViewController?.present(yourViewController, animated: true)
If dismissing from the presentedViewController, you will need to call twice, once to dismiss yourViewController and then again to dismiss the photoPickerController, if they are both presented:
presentedViewController?.dismiss(animated: true)
presentedViewController?.dismiss(animated: true)
Pushing a custom controller on top of the navigation stack
To create the appearance of pushing to the PHPickerViewController stack you can use custom UIPresentationController and UIViewControllerAnimatedTransitioning classes to present your view. The following classes will mimic a push to a modally presented navigation controller:
import UIKit
import PhotosUI
class SlideInModalPresentationController: UIPresentationController {
var offset: CGFloat = 0.0
override func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
presentedView?.frame.origin.y = offset
presentedView?.frame.size.height -= offset
presentedView?.layer.cornerRadius = 10
presentedView?.clipsToBounds = true
}
}
class SlideInTransition: NSObject, UIViewControllerAnimatedTransitioning {
private let duration = 0.3
var isPresenting: Bool = true
var dismissModally: Bool = false
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toController = transitionContext.viewController(forKey: .to),
let fromController = transitionContext.viewController(forKey: .from)
else { return}
if isPresenting {
toController.view.frame.origin.x = fromController.view.frame.width
transitionContext.containerView.addSubview(toController.view)
UIView.animate(withDuration: duration, animations: {
toController.view.frame.origin.x = 0
}, completion: { _ in
transitionContext.completeTransition(true)
})
} else if dismissModally {
var stack: UIView? = nil
if #available(iOS 14, *), toController is PHPickerViewController {
stack = toController.view.superview
toController.dismiss(animated: false)
} else if toController is UIImagePickerController {
stack = toController.view.superview
toController.dismiss(animated: false)
}
UIView.animate(withDuration: duration, animations: {
stack?.frame.origin.y = fromController.view.frame.height
fromController.view.frame.origin.y = fromController.view.frame.height
}, completion: { _ in
transitionContext.completeTransition(true)
fromController.view.removeFromSuperview()
})
} else {
UIView.animate(withDuration: duration, animations: {
fromController.view.frame.origin.x = fromController.view.frame.width
}, completion: { _ in
transitionContext.completeTransition(true)
fromController.view.removeFromSuperview()
})
}
}
}
To implement in your view controller:
class ViewController: UIViewController {
let slidInTransition = SlideInTransition()
}
extension ViewController: UIViewControllerTransitioningDelegate {
private func presentYourController(_ image: UIImage) {
let yourController = YourController()
yourController.image = image
yourController.modalPresentationStyle = .custom
yourController.transitioningDelegate = self
slidInTransition.dismissModally = false
presentedViewController?.present(yourController, animated: true)
}
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
let presentationController = SlideInModalPresentationController(presentedViewController: presented, presenting: presenting)
presentationController.offset = view.convert(source.view.frame, to: nil).origin.y + 10
return presentationController
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
slidInTransition.isPresenting = true
return slidInTransition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
slidInTransition.isPresenting = false
return slidInTransition
}
private func dismissPhotoStack() {
slidInTransition.dismissModally = true
presentedViewController?.dismiss(animated: true)
}
}
When you are ready to dismiss the whole stack you can call dismissPhotoStack.

Related

How to dismiss camera controller from framework in iOS Swift?

I have implemented vision document scanner inside framework. When camera view controller is called and document captured. While save button tapped it should dismiss and return to viewController.
Here is the code inside framework:
public func showScanner(){
self.createTaskController()
// let scannerViewController = VNDocumentCameraViewController()
// scannerViewController.delegate = self
// present(scannerViewController, animated: true)
print("Called Build")
}
private func createTaskController(){
let scannerViewController = VNDocumentCameraViewController()
scannerViewController.delegate = self
self.clientView?.addChild(scannerViewController)
self.clientView?.view.addSubview(scannerViewController.view)
scannerViewController.didMove(toParent: clientView)
scannerViewController.dismiss(animated: true)
}
public func imageFromFile(result: #escaping (_ image: UIImage?) -> Void){
//the image
if imageNew != nil {
result(imageNew)
}
else{
//callback nil so the app does not pause infinitely if
//the error != nil
result(nil)
}
}
public func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) {
guard scan.pageCount >= 1 else {
controller.dismiss(animated: true)
return
}
let originalImage = scan.imageOfPage(at: 0)
let newImage = compressedImage(originalImage)
imageNew = newImage
print("new image::\(newImage.size)")
print("new imagei::\(newImage)")
controller.dismiss(animated: true)
}
public func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFailWithError error: Error) {
print(error)
controller.dismiss(animated: true)
}
public func documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController) {
controller.dismiss(animated: true)
}
func compressedImage(_ originalImage: UIImage) -> UIImage {
guard let imageData = originalImage.jpegData(compressionQuality: 1),
let reloadedImage = UIImage(data: imageData) else {
return originalImage
}
return reloadedImage
}
Here is the code where i have called framework inside sample project:
#IBAction func btnAction(_ sender: Any) {
A8Scan(self).showScanner()
p()
}
My issue is when tapping on save button it should dismiss camera controller (VNDocumentCameraViewController) and return to sample app. But, In my case its not returning.
Any help much appreciated pls...
You add it as a child here
let scannerViewController = VNDocumentCameraViewController()
private func createTaskController(){
scannerViewController.delegate = self
self.clientView?.addChild(scannerViewController)
self.clientView?.view.addSubview(scannerViewController.view)
scannerViewController.didMove(toParent: clientView)
/// scannerViewController.dismiss(animated: true) remove this line
}
then to remove do
scannerViewController.removeFromParent()
scannerViewController.view.removeFromSuperView()
OR
private func createTaskController(){
let scannerViewController = VNDocumentCameraViewController()
scannerViewController.delegate = self
self.clientView?.present(scannerViewController,animated:true,completion:nil)
}
Dismiss
controller.dismiss(animated: true)
To send the image create a function inside the clientView and call it
let newImage = compressedImage(originalImage)
self.clientView?.sendImage(newImage)

iOS 11 double tap on image dismisses UIImagePickerController and presenter view controller

We have a photo picker made with UIImagePickerController.
When making double tap (instead of one tap) the photo from gallery.
On iOS 10: UIImagePickerController is dismissed
On iOS 11: UIImagePickerController is dismissed and presenting view controller is dismissed as well :0
Is it iOS 11 bug or we have to adjust something?
Our code:
let vc = UIImagePickerController()
vc.delegate = self
vc.modalPresentationStyle = .overFullScreen
vc.allowsEditing = false
rootVC.present(vc, animated: true) // `rootVC` also presented modally.
Instead of self.dismiss(), use picker.dismiss()
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
{
//self.dismiss(animated: true, completion: nil)
picker.dismiss(animated: true, completion: nil)
}
This will only dismiss your picker view and not the view controller.
We end up with solution: Set delegate = nil right after delegate call in didFinishPickingMediaWithInfo.
public class ImagePicker: NSObject {
private lazy var viewController = setupActionSheet()
private var rootViewController: UIViewController?
private var completionHandler: (([String: Any]) -> Void)?
private var cancellationHandler: (() -> Void)?
}
extension ImagePicker {
public func present(on: UIViewController, completionHandler: #escaping (([String: Any]) -> Void)) {
rootViewController = on
self.completionHandler = completionHandler
cancellationHandler = nil
on.presentAnimated(viewController)
}
public func present(on: UIViewController,
completionHandler: #escaping (([String: Any]) -> Void),
cancellationHandler: #escaping (() -> Void)) {
rootViewController = on
self.completionHandler = completionHandler
self.cancellationHandler = cancellationHandler
on.presentAnimated(viewController)
}
}
extension ImagePicker {
private func setupActionSheet() -> UIAlertController {
let actionSheet = UIAlertController(actionSheetWithTitle: LocalizedString.Generic.ImagePicker.addPhoto)
if UIImagePickerController.isSourceTypeAvailable(.camera) {
actionSheet.addDefaultAction(LocalizedString.Generic.ImagePicker.takePhoto) { [weak self] _ in
self?.presentImagePicker(.camera)
}
}
actionSheet.addDefaultAction(LocalizedString.Generic.ImagePicker.selectPhoto) { [weak self] _ in
self?.presentImagePicker(.photoLibrary)
}
actionSheet.addCancelAction(LocalizedString.Generic.ButtonTitle.cancel) { [weak self] _ in
self?.cancellationHandler?()
}
return actionSheet
}
private func presentImagePicker(_ sourceType: UIImagePickerControllerSourceType) {
let vc = UIImagePickerController()
vc.delegate = self
vc.allowsEditing = false
vc.sourceType = sourceType
rootViewController?.present(vc, animated: true)
}
}
extension ImagePicker: UIImagePickerControllerDelegate {
public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String: Any]) {
picker.delegate = nil /// It prevents to call delegate when user taps on a few images very fast. seems iOS 11 issue only.
if picker.sourceType == .camera, let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
picker.dismiss(animated: true) {
self.completionHandler?(info)
}
}
public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true) {
self.cancellationHandler?()
}
}
}
Usage:
// Somewhere in view controller code.
imagePicker = ImagePicker()
imagePicker?.present(on: self) { [weak self] in
self?.imagePicker = nil
self?.viewModel.addImage($0)
}

Programmatically advance UIPageViewController - Swift

This is my first attempt at an IOS app, and I have no experience with swift, and a lot of the code is borrowed from the web and edited.
I am trying to create a set of slides. I go from the main Landing page to another View Controller, TestVC, that runs the slides. The landing page and the slides work. I can swipe back and forth. I am now trying to add a timer so that the slides auto advance every 5 or so seconds.
I believe that the code that needs to be run is:
pageViewController.setViewControllers(varPageVC, direction: UIPageViewControllerNavigationDirection.forward, animated: true, completion: nil)
I get an error :
test.swift:31:9: Ambiguous reference to member 'pageViewController(_:viewControllerBefore:)'.
I do not know how to interpret this error and move forward. The error is triggered in the test.swift, where a timer calls a function that tries to advance the slide. Advise is appreciated. If I am doing it wrong, please point me in the appropriate direction.
The landing page has a button, that opens a ViewController testVC. I have 2 files, test.swift and alphabetItemController.swift. The storyboard has, in addition to the landing page ViewController, a PageViewController called alphabetPVC, a ViewController called alphabetVC and a ViewController called TestVC.
Here is the code for alphabetItemController.swift ...
import UIKit
class alphabetItemController: UIViewController {
#IBOutlet weak var contentImageView2: UIImageView!
#IBOutlet weak var contentWordPn: UILabel!
var itemIndex: Int = 0
var imageName: String = ""
var wordPN: String = ""
var tTime: Timer!
override func viewDidLoad() {
super.viewDidLoad()
contentImageView2!.image = UIImage(named: imageName)
contentWordPn!.text = wordPN
}
}
Here is the code for test.swift ...
import Foundation
import UIKit
class testItemController: UIViewController, UIPageViewControllerDataSource {
var tTime: Timer!
override func viewDidLoad() {
super.viewDidLoad()
createPageViewController()
setupPageControl()
tTime = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(changeSlide), userInfo: nil, repeats: true)
//tTime = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(goToNextPage), userInfo: nil, repeats: true)
}
func changeSlide() {
pageViewController.setViewControllers(varPageVC, direction: UIPageViewControllerNavigationDirection.forward, animated: true, completion: nil)
}
// MARK: - Variables
private var varPageVC: UIPageViewController?
private let contentTextWordPN = ["A", "B", "C", "D", "E"]
private let contentCount = 5 //TODO ADJUST THIS FOR EACH COLLECTION
private func createPageViewController() {
let pageController = self.storyboard!.instantiateViewController(withIdentifier: "alphabetPVC") as! UIPageViewController
pageController.dataSource = self
if contentCount > 0 {
let firstController = getItemController(itemIndex: 0)!
let startingViewControllers = [firstController]
pageController.setViewControllers(startingViewControllers, direction: UIPageViewControllerNavigationDirection.forward, animated: false, completion: nil)
}
varPageVC = pageController
addChildViewController(varPageVC!)
self.view.addSubview(varPageVC!.view)
varPageVC!.didMove(toParentViewController: self)
}
private func setupPageControl() {
let appearance = UIPageControl.appearance()
appearance.pageIndicatorTintColor = UIColor.gray
appearance.currentPageIndicatorTintColor = UIColor.white
appearance.backgroundColor = UIColor.darkGray
}
func pageViewController(_ varPageVC: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let itemController = viewController as! alphabetItemController
if itemController.itemIndex > 0 {
return getItemController(itemIndex: itemController.itemIndex-1)
}
return nil
}
func pageViewController(_ varPageVC: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let itemController = viewController as! alphabetItemController
if itemController.itemIndex+1 < contentCount {
return getItemController(itemIndex: itemController.itemIndex+1)
}
return nil
}
private func getItemController(itemIndex: Int) -> alphabetItemController? {
if itemIndex < contentCount {
let pageItemController = self.storyboard!.instantiateViewController(withIdentifier: "alphabetVC") as! alphabetItemController
pageItemController.itemIndex = itemIndex
pageItemController.imageName = "alphabet_" + String(format: "%02d", (itemIndex + 1)) //alphabet_01
pageItemController.wordPN = contentTextWordPN[itemIndex]
return pageItemController
}
return nil
}
func presentationCountForPageViewController(varPageVC: UIPageViewController) -> Int {
return contentCount
}
func presentationIndexForPageViewController(varPageVC: UIPageViewController) -> Int {
return 0
}
func currentControllerIndex() -> Int {
let pageItemController = self.currentController()
if let controller = pageItemController as? alphabetItemController {
return controller.itemIndex
}
return -1
}
func currentController() -> UIViewController? {
if (self.varPageVC?.viewControllers?.count)! > 0 {
return self.varPageVC?.viewControllers![0]
}
return nil
}
}
extension UIPageViewController {
func goToNextPage(animated: Bool = true) {
guard let currentViewController = self.viewControllers?.first else { return }
guard let nextViewController = dataSource?.pageViewController(self, viewControllerAfter: currentViewController) else { return }
setViewControllers([nextViewController], direction: .forward, animated: animated, completion: nil)
}
func goToPreviousPage(animated: Bool = true) {
guard let currentViewController = self.viewControllers?.first else { return }
guard let previousViewController = dataSource?.pageViewController(self, viewControllerBefore: currentViewController) else { return }
setViewControllers([previousViewController], direction: .reverse, animated: animated, completion: nil)
}
}
There is even an extension UIPageViewController, but I do not know how to call the goToNextPage function.
I ended up changing the changeSlide() function... I had the next slide function in the extension already, and only the syntax to call it was eluding me... I found some examples on SO and used them as references:
func changeSlide() {
varPageVC?.goToNextPage()
}

3D Peek and POP in swift

I'm trying to implement 3D Touch Peek and Pop in my swift code. When the user presses deeper on the peek view, array of preview actions will appear (Share, Update, Delete).
What I need is when user select Update action will move to UpdateView controller, but it keeps on crashing.
Here is my code:
HomePeakViewController.swift
let item3 = UIPreviewAction(title: "Update", style: .Default) { (action:UIPreviewAction, vc:UIViewController) -> Void in
print("Update")
let nb:BookAppointmentViewController = BookAppointmentViewController(nibName: "BookAppointmentViewController", bundle: nil)
let root = UIApplication.sharedApplication().keyWindow?.rootViewController
root?.presentViewController(nb, animated: true, completion: nil)
POP method in HomeViewController.swift
func previewingContext(previewingContext: UIViewControllerPreviewing, commitViewController viewControllerToCommit: UIViewController) {
let Homepeak = HomePeakViewController()
showViewController(Homepeak, sender: self)
}
I tried this code as well to move to Update screen, but it gives me (fatal error: unexpectedly found nil while unwrapping an Optional value).
var top = UIApplication.sharedApplication().keyWindow?.rootViewController
let test = AppointmentDetailsViewController()
top!.presentViewController(test, animated: true, completion: {})
Maybe you need to deal with delegations:
For example:
extension MainViewController: UIViewControllerPreviewingDelegate {
func previewingContext(previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
if #available(iOS 9.0, *) {
previewingContext.sourceRect = myButton!.bounds //optional
}
let homePeakViewController = UIStoryboard.homePeakViewController()
homePeakViewController.delegate = self
return homePeakViewController
}
func previewingContext(previewingContext: UIViewControllerPreviewing, commitViewController viewControllerToCommit: UIViewController) {
let balanceViewController = viewControllerToCommit as! HomePeakViewController
navigationController?.pushViewController(balanceViewController, animated: true)
}
}
extension MainViewController: HomePeakViewControllerDelegate {
func homePeakViewControllerUpadateActionTapped() {
let bookAppointmentViewController = let nb:BookAppointmentViewController = BookAppointmentViewController(nibName: "BookAppointmentViewController", bundle: nil)
navigationController?.pushViewController(bookAppointmentViewController, animated: true) //present as you want
}
}
protocol HomePeakViewControllerDelegate {
func homePeakViewControllerUpadateActionTapped()
}
class HomePeakViewController {
var delegate: HomePeakViewControllerDelegate?
#available(iOS 9.0, *)
override func previewActionItems() -> [UIPreviewActionItem] {
let item3 = UIPreviewAction(title: "Update", style: .Default) { (action:UIPreviewAction, vc:UIViewController) -> Void in
delegate?.homePeakViewControllerUpadateActionTapped()
}
return [item3]
}
}

transitionCoordinator return nil for custom container view controller in iOS8

when reference transitionCoordinator on child vc, it used to called transitionCoordinator on custom container class in iOS7, but in iOS8 this wasn't the case. Now it return nil and I have no clue what should I change to make this work.
I guess its about UIPresentationController introduced in iOS8, but can't find proper implementation for custom container view controller.
As Matt said in this previous SO question:
So, since you can't even get a transition coordinator in a situation
where you are allowed to write a custom transition animation for a
built-in parent view controller, obviously the chances of your getting
one in a situation where you're trying to do your own parent view
controller are zero
But, according to transitionCoordinator, overriding it is allowed:
Container view controllers can override this method but in most cases
should not need to. If you do override this method, first call super
to see if there is an appropriate transition coordinator to return,
and, if there is, return it.
So, I would try to create my own coordinator for my own VC container. If you use a UIViewPropertyAnimator to manipulate the VC container's children, it's almost straightforward.
Here is an example:
class PropertyAnimatorTransitionCoordinator: NSObject, UIViewControllerTransitionCoordinator, UIViewControllerTransitionCoordinatorContext {
private let parentView: UIView
private let fromViewController: UIViewController?
private let toViewController: UIViewController
private let animator: UIViewPropertyAnimator
// MARK: - Life Cycle
init(parentView: UIView,
fromViewController: UIViewController?,
toViewController: UIViewController,
animator: UIViewPropertyAnimator) {
self.parentView = parentView
self.fromViewController = fromViewController
self.toViewController = toViewController
self.animator = animator
}
// MARK: - UIViewControllerTransitionCoordinator
func animate(alongsideTransition animation: ((UIViewControllerTransitionCoordinatorContext) -> Void)?,
completion: ((UIViewControllerTransitionCoordinatorContext) -> Void)? = nil) -> Bool {
var isSuccessful = false
if let animation = animation {
animator.addCompletion { [weak self] _ in
guard let context = self else { return }
animation(context)
}
isSuccessful = true
}
if let completion = completion {
animator.addCompletion { [weak self] _ in
guard let context = self else { return }
completion(context)
}
isSuccessful = true
}
return isSuccessful
}
func animateAlongsideTransition(in view: UIView?, animation: ((UIViewControllerTransitionCoordinatorContext) -> Void)?, completion: ((UIViewControllerTransitionCoordinatorContext) -> Void)? = nil) -> Bool {
return animate(alongsideTransition: animation, completion: completion)
}
func notifyWhenInteractionEnds(_ handler: #escaping (UIViewControllerTransitionCoordinatorContext) -> Void) {
animator.addCompletion { [weak self] _ in
guard let context = self else { return }
handler(context)
}
}
func notifyWhenInteractionChanges(_ handler: #escaping (UIViewControllerTransitionCoordinatorContext) -> Void) {
animator.addCompletion { [weak self] _ in
guard let context = self else { return }
handler(context)
}
}
// MARK: - UIViewControllerTransitionCoordinatorContext
var isAnimated: Bool {
return true
}
var presentationStyle: UIModalPresentationStyle {
return .none
}
var initiallyInteractive: Bool {
return false
}
var isInterruptible: Bool {
return animator.isInterruptible
}
var isInteractive: Bool {
return animator.isUserInteractionEnabled
}
var isCancelled: Bool {
return !animator.isRunning
}
var transitionDuration: TimeInterval {
return animator.duration
}
var percentComplete: CGFloat {
return animator.fractionComplete
}
var completionVelocity: CGFloat {
return 0
}
var completionCurve: UIView.AnimationCurve {
return animator.timingParameters?.cubicTimingParameters?.animationCurve ?? .linear
}
var targetTransform: CGAffineTransform {
return .identity
}
var containerView: UIView {
return parentView
}
func viewController(forKey key: UITransitionContextViewControllerKey) -> UIViewController? {
switch key {
case .from:
return fromViewController
case .to:
return toViewController
default:
return nil
}
}
func view(forKey key: UITransitionContextViewKey) -> UIView? {
switch key {
case .from:
return fromViewController?.view
case .to:
return toViewController.view
default:
return nil
}
}
}
In my custom container, I would use it like this:
class CustomContainerViewController: UIViewController {
private var customTransitionCoordinator: UIViewControllerTransitionCoordinator?
override var transitionCoordinator: UIViewControllerTransitionCoordinator? {
if let coordinator = super.transitionCoordinator {
return coordinator
}
return customTransitionCoordinator
}
override var shouldAutomaticallyForwardAppearanceMethods: Bool {
return false
}
func insertNewChild(_ viewController: UIViewController) {
let animator = UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut)
customTransitionCoordinator = PropertyAnimatorTransitionCoordinator(
parentView: view,
fromViewController: nil,
toViewController: viewController,
animator: animator
)
animator.addCompletion { [weak self] _ in
guard let parent = self else { return }
viewController.didMove(toParent: parent)
self?.customTransitionCoordinator = nil
}
addChild(viewController)
viewController.beginAppearanceTransition(true, animated: true)
view.addSubview(viewController.view)
let target = view.bounds
viewController.view.frame = target
viewController.view.frame.origin.x = -target.width
view.layoutIfNeeded()
animator.addAnimations {
viewController.view.frame = target
}
animator.addCompletion { [weak self] _ in
guard let parent = self else { return }
viewController.endAppearanceTransition()
viewController.didMove(toParent: parent)
self?.customTransitionCoordinator = nil
}
animator.startAnimation()
}
}
Of course, some edge cases are not handled. It's a really basic example.

Resources