How to access my custom UIPresentationController from within presented controller? - ios

This is how I perform transitioning:
extension UIViewController: UIViewControllerTransitioningDelegate {
public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return OverlayPresentationController(presentedViewController: presented, presenting: presenting)
}
func presentOverlayController(_ controller: UIViewController) {
controller.modalPresentationStyle = .custom
controller.transitioningDelegate = self
present(controller, animated: true)
}
}
And then within my presented controller (AlertVC) at some point I need to access its presentation controller:
print(presentationController as? OverlayPresentationController) //nil
print(presentationController) //is ok, UIPresentationController
Why?
Presenting:
let controller = AlertVC.instantiate()
controller.update()
presentOverlayController(controller)
class AlertVC: UIViewController {
class func instantiate() -> AlertVC {
return UIStoryboard(name: "Alert", bundle: Bundle(for: LoginVC.classForCoder())).instantiateInitialViewController() as! AlertVC
}
func update() {
_ = view
print(presentationController as? OverlayPresentationController) //nil
}
}

You get the view controller that is presenting the current view controller by calling presentingViewController
// The view controller that presented this view controller (or its farthest ancestor.)
self.presentingViewController
If your presenting view controller is in a navigation controller that returns a UINavigationController. You can get the view controller you need like so:
let presentingNVC = self.presentingViewController as? UINavigationViewController
let neededVC = presentingNVC.viewControllers.last as? NeededViewController

Related

UINavigationControllerDelegate doesn't work when back twice

I use coordinator pattern with child coordinators. I have a problem with removing child coordinator of my child coordinator.
This is a sequence of my coordinators:
HomeCoordinator -> RoutineCoordinator (child of HomeCoordinator) -> ExerciseCoordinator (child of RoutineCordinator) -> CustomExerciseCoordinator (child of ExerciseCoordinator)
To get know when user pops view controllers I use method didShow from navigation controller delegate.
When I push view controllers, everything is ok, but I when I move back, method didShow is called only once. When I use back button twice, the second time didShow is not called.
Example:
I move back from CustomExerciseCoordinator to ExerciseCoordinator and didShow works properly. Then I immediately move back to previous coordinator (RoutineCoordinator) and didShow is not called.
I am not sure if it is needed to show all coordinators, because the code for each coordinator looks similar, but below it is shown.
class HomeCoordinator: NSObject, Coordinator {
var childCoordinators = [Coordinator]()
var navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
navigationController.navigationBar.prefersLargeTitles = true
navigationController.navigationBar.tintColor = .white
super.init()
navigationController.delegate = self
}
func start() {
let vc = HomeFactory.makeHomeScene(delegate: self)
navigationController.pushViewController(vc, animated: false)
}
}
extension HomeCoordinator: HomeCoordinatorDelegate {
func goToWorkoutCreating() {
let child = NewRoutineCoordinator(navigationController: navigationController, removeCoordinatorWith: removeChild)
child.passWorkoutToHomeDelegate = self
addChild(child: child)
child.start()
}
class NewRoutineCoordinator: NSObject, Coordinator {
var exerciseNumber: Int?
var childCoordinators = [Coordinator]()
var navigationController: UINavigationController
private var removeCoordinatorWhenViewDismissed: ((Coordinator) -> ())
weak var passWorkoutToHomeDelegate: PassWorkoutToHome?
init(navigationController: UINavigationController, removeCoordinatorWith removeCoordinatorWhenViewDismissed: #escaping ((Coordinator) -> ())) {
self.navigationController = navigationController
self.removeCoordinatorWhenViewDismissed = removeCoordinatorWhenViewDismissed
}
func start() {
navigationController.delegate = self
let vc = NewRoutineFactory.makeNewRoutineScene(delegate: self)
navigationController.navigationItem.largeTitleDisplayMode = .never
navigationController.pushViewController(vc, animated: true)
}
}
extension NewRoutineCoordinator: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
guard let fromViewController = navigationController.transitionCoordinator?.viewController(forKey: .from) else {
return
}
if navigationController.viewControllers.contains(fromViewController) {
return
}
if let vc = fromViewController as? NewRoutineViewController {
removeCoordinatorWhenViewDismissed(self)
}
}
}
class ExerciseCoordinator: NSObject, Coordinator {
var childCoordinators = [Coordinator]()
var navigationController: UINavigationController
private var removeCoordinatorWhenViewDismissed: ((Coordinator) -> ())
init(navigationController: UINavigationController, removeCoordinatorWith removeCoordinatorWhenViewDismissed: #escaping ((Coordinator) -> ())) {
self.navigationController = navigationController
self.removeCoordinatorWhenViewDismissed = removeCoordinatorWhenViewDismissed
}
func start() {
navigationController.delegate = self
let vc = NewExerciseFactory.newExerciseScene(delegate: self)
navigationController.pushViewController(vc, animated: true)
}
extension ExerciseCoordinator: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
guard let fromViewController = navigationController.transitionCoordinator?.viewController(forKey: .from) else {
return
}
if navigationController.viewControllers.contains(fromViewController) {
return
}
if let vc = fromViewController as? NewExerciseViewController {
removeCoordinatorWhenViewDismissed(self)
}
}
}
I guess something is nil the second time you navigate back. This often happens to me when I instantiate a view controller inside a function which it gets de-initialized so my delegation doesn't work anymore. What I usually do is declare a variable outside the function and assign the instantiated view controller to it.
For Instance try:
var newRoutineCoordinator: NewRoutineCoordinator?
func goToWorkoutCreating() {
let child = NewRoutineCoordinator(navigationController: navigationController, removeCoordinatorWith: removeChild)
newRoutineCoordinator = child
child.passWorkoutToHomeDelegate = self
addChild(child: child)
child.start()
}
Not sure the problem is there but this is just to give you an example.
You have UINavigationControllerDelegate inside an extension of NewRoutineCoordinator but It may be already nil by the time you navigate back.

Present a View Controller modally from a tab bar controller

I want to build a view with a camera. Something just like Instagram where there is a button in the middle that the user can click and the camera view shows up.
I implemented a code for the TabViewController in the AppDelegate but nothing happens, no animation or Presentation of the new ViewController.
Here is my AppDelegate:
import UIKit
class AppDelegate: UIResponder, UIApplicationDelegate, UITabBarControllerDelegate {
var window: UIWindow?
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: ViewController) -> Bool {
if viewController is ViewController {
let storyboard = UIStoryboard(name: "Main.storyboard", bundle: nil)
if let controller = storyboard.instantiateViewController(withIdentifier: "cameraVC") as? ViewController {
controller.modalPresentationStyle = .fullScreen
tabBarController.present(controller, animated: true, completion: nil)
}
return false
}
return true
}
Here is my Storyboard:
Any ideas?
I suggest to create a custom class for your TabBarController, then assign the delegate to that.
You can either assign and check the restorationIdentifier of the view controller, or do a type check. I usually use storyboard identifier as the restoration identifier of the view controller(s).
class TabBarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if let identifier = viewController.restorationIdentifier, identifier == "cameraVC" {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "cameraVC") as! CameraViewController
present(vc, animated: true, completion: nil)
return false
}
return true
}
}
Here's a sample you can play with:
https://gist.github.com/emrekyv/3343aa40c24d7e54244dc09ba0cd95df
I just tried and It worked perfectly for me:
Create a Custom class for your TabBarController and assign it to your Controller in Storyboard.
After that override didSelect of tabBarController and write your presentation code there :
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
if let controller = self.viewControllers?[self.selectedIndex] as? ViewController {
controller.modalPresentationStyle = .fullScreen
self.present(controller, animated: true, completion: nil
}
}
Hope it helps!!

Present Modally Not Resizing View Properly

I'm trying to present a ViewController (embedded in a NavigationController) from a button inside a TableViewController. The presented ViewController should be half the height of the TableViewController. I've tried with the following code below but it doesn't seem to work (Swift 3). Can someone kindly help? thanks!
class AddNewRecipeTableViewController: UITableViewController, UIViewControllerTransitioningDelegate {
#IBAction func popUpTest(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let pvc = storyboard.instantiateViewController(withIdentifier: "popUpTest") as! UINavigationController
pvc.modalPresentationStyle = UIModalPresentationStyle.custom
pvc.transitioningDelegate = self
self.present(pvc, animated: true, completion: nil)
}
func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController!, sourceViewController source: UIViewController) -> UIPresentationController? {
return HalfSizePresentationController(presentedViewController: presented, presenting: presentingViewController)
}
}
class HalfSizePresentationController : UIPresentationController {
override var frameOfPresentedViewInContainerView : CGRect {
return CGRect(x: 0, y: 0, width: containerView!.bounds.width, height: containerView!.bounds.height/2)
}
}
You have:
func presentationControllerForPresentedViewController(
presented: UIViewController,
presentingViewController presenting: UIViewController!,
sourceViewController source: UIViewController)
-> UIPresentationController? {
That method will never be called, because in Swift 3 it doesn't correspond to any method that Cocoa knows about. (I'm suprised you don't report getting a warning from the compiler about this.)
You probably meant to implement presentationController(forPresented:presenting:source:), like this:
func presentationController(
forPresented presented: UIViewController,
presenting: UIViewController?,
source: UIViewController)
-> UIPresentationController? {
But even that won't be called, because you have not set the presented view controller's modalPresentationStyle to .custom.

Why is my UIViewController's view nil?

I'm new to presenting UIViewcontrollers programmatically, and I'm having some issues presenting its view. Here is my code:
override func willBecomeActive(with conversation: MSConversation) {
let controller = instantiateUserStickersController()
self.addChildViewController(controller)
self.view.addSubview(controller.view) //This line throws the error
}
private func instantiateUserStickersController() -> UIViewController {
guard let controller = storyboard?.instantiateViewController(withIdentifier: "UserStickersViewController") as? UserStickersViewController else { fatalError("Unable to instantiate a UserStickersViewController from the storyboard") }
return controller
}
Why can't I access the controller's view? I'm sure I set the storyboard ID correctly
Here's my storyboard:
The view should not exist before you present the view controller to kick off its lifecycle. So only access the new controller's view once it has been presented:
self.addChildViewController(controller)
self.present(controller, animated: true) {
self.view.addSubview(controller.view)
}
This worked for me.

How to return two different presentation controllers for two different controllers from extension of UIViewController?

This is my extension:
extension UIViewController: UIViewControllerTransitioningDelegate {
func presentAssignBookToClassesViewController(controller: BWAssignBookToClassesViewController) {
controller.modalPresentationStyle = .Custom
controller.transitioningDelegate = self
controller.preferredContentSize = CGSizeMake(500, 575)
presentViewController(controller, animated: true, completion: nil)
}
func presentSettingsStoryboard() {
if let settingsController = UIStoryboard(name: "TeacherSettingsStoryboard", bundle: nil).instantiateInitialViewController() {
settingsController.modalPresentationStyle = .Custom
settingsController.transitioningDelegate = self
settingsController.preferredContentSize = CGSizeMake(500, 575)
presentViewController(settingsController, animated: true, completion: nil)
}
}
//MARK: - UIViewControllerTransitioningDelegate
public func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController? {
return BWOverlayPresentationController(presentedViewController: presented, presentingViewController: presenting)
}
}
Within presentationControllerForPresentedViewController: I need to return either BWOverlayPresentationController or BWSettingsPresentationController depending on what method was called. How to achieve this?
You can simply distinguish them via restorationIdentifier (you can set this simply using storyboard):
//MARK: - UIViewControllerTransitioningDelegate
public func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController? {
if presented.restorationIdentifier == BWSettingsRestorationIdentifier {
return BWSettingsPresentationController(presentedViewController: presented, presentingViewController: presenting)
} else {
return BWOverlayPresentationController(presentedViewController: presented, presentingViewController: presenting)
}
}
I would suggest you can create one BaseViewContoller with two viewController objects eg: BWOverlayPresentationController, BWSettingsPresentationController and based on condition you can return the the specific view controller.
public func presentationControllerForPresentedViewController(presented: UIViewController?, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController? {
let viewController = BaseViewController()
if (viewController.(somePropertyInViewController)) {
return BWOverlayPresentationController(presentedViewController: presented, presentingViewController: presenting)
}
else {
return BWSettingsPresentationController(presentedViewController: presented, presentingViewController: presenting)
}
public func presentationControllerForPresentedViewController(presented: UIViewController?, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController? {
// You can create some property in presented/presenting viewController.
// and check here to return specific viewContoller.
if (presented.(somePropertyInViewController)) {
return BWOverlayPresentationController(presentedViewController: presented, presentingViewController: presenting)
}
else {
return BWSettingsPresentationController(presentedViewController: presented, presentingViewController: presenting)
}
}

Resources