How to display a ViewController when answering a call with CallKit - ios

I have followed the following tutorial to implement CallKit within my app:
https://www.raywenderlich.com/150015/callkit-tutorial-ios
But I would like to go further, and display my own ViewController while the call is active. I am doing a videocall service so I would like to have my own interface.
Is that possible at all? I have been trying to launch the ViewController from the method provider(CXProvider:CXAnswerCallAction) which is the one called when the user answers the call, but it seems to crash every time. I am trying to instantiate it with this (Swift 3):
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "VideoCallViewController") as! VideoCallViewController
UIApplication.shared.keyWindow?.rootViewController?.present(vc, animated: true, completion: nil)
It crashes on the second line without explanation. It shows lldb, I have tried to get the backtrace by entering bt but it doesn't return anything.

I figured it out:
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = mainStoryboard.instantiateViewController(withIdentifier: "VideoCallViewController") as! VideoCallViewController
then, either:
vc.view.frame = UIScreen.main.bounds
UIView.transition(with: self.window!, duration: 0.5, options: .transitionCrossDissolve, animations: {
self.window!.rootViewController = vc
}, completion: nil)
or:
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
According to https://stackoverflow.com/a/35226874/5798668, the first option is preferable because if you use the second one you will have multiple UIWindows active in your app at the same time.
NOTE: In my case the ProviderDelegate did not have a self.window attribute, this was passed to it by the AppDelegate.swift, in which a Push notification was executing the reportIncomingCall() of the delegate.

Related

Re-entering the application

After logging out the user, I use the code below after logging in again. The problem is that the application starts duplicated, all messages are duplicated. How to prevent it?
let appDelegate = UIApplication.shared.delegate! as! AppDelegate
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "view_controller_phone")
appDelegate.window?.rootViewController = initialViewController
appDelegate.window?.makeKeyAndVisible()
UIView.transition(with: appDelegate.window!, duration: 1.0, options: .transitionCrossDissolve, animations: nil, completion: nil)
It's a bit difficult to answer you without seeing what are you doing. But I'll give you 2 tips for look around ;)
First, check that your UIViewController are dismissed (it's your responsibility on doing that as Apple says) BEFORE you set the rootController again. A little trick is this:
https://stackoverflow.com/a/50333715/12277616
Second, take care on the observers if you have used them along with Notification Center. If this is the case I suggest you to remove the observers every time you dismiss a UIViewController so you are sure that they don't exist anymore in your runtime foreground.
Remember that dismiss(animated:true) can be closurable without parameters like this:
self.dismiss(animated:true) {
//Code to be executed when dismissing the controller
//Remove the observer
NotificationCenter.default.removeObserver(self, name: [observername],object:nil)
}

HomeViewController not loading after Firebase Google SignIN in AppDelegate.swift

I have successfully configured google sign in my iOS App using Firebase.
After a successful login, I need to make the UIViewController move to HomeViewController.
The AppDelegate class has "didSignInFor" method in which I have added the following code
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "HomeVC")
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
HomeVC is a storyboard id of HomeViewController and restoration id is also same.
But still, it doesn't take me to HomeViewController.
I have referred this stack overflow posts.
Opening view controller from app delegate using swift
Please tell me where I am going wrong. I have referred this video for building the app.
https://www.youtube.com/watch?v=20Qlho0G3YQ
Here's my AppDelegate.swift file https://pastebin.com/WfzhYAKH
Try:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "HomeVC")
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
This works for me.
Please comment if you have any questions. Happy to Help!

Swift go back to root view controller

I am new to swift but I have implemented FCM but I am having an issue. When I click the notification it loads the NotificationsViewController with the following code.
let sb = UIStoryboard(name: "Main", bundle: nil)
let otherVC = sb.instantiateViewController(withIdentifier: "NotificationsViewController") as! NotificationsViewController
self.window?.rootViewController = otherVC;
Once the NotificationsViewController is loaded I use the following code in on the back button to reassign the root view back to the normal ViewController.
let mainStoryBoard = UIStoryboard(name: "Main", bundle: nil)
let redViewController = mainStoryBoard.instantiateViewController(withIdentifier: "splashScreen") as! ViewController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = redViewController
Once I am on the splashScreen it should show a quick image and then move on to the main dashboard using this code.
let vw = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "DashboardViewController") as! DashboardViewController
self.navigationController?.pushViewController(vw, animated: true)
The problem I am having is the splashScreen code is not working when the app loads it from NotificationsViewController, it just hangs on that VC, but if I load the splashScreen without reassigning the root view it works.
So is there some other way I should be doing this? I just want to take the user to the NotificationsViewController when they click the notification and then back to the main part of the app when they click a back button.
If you just need to show the NotificationViewController, you just present it in fullscreen. You can set it by vc.modalPresentationStyle = .fullScreen. Then to go back to the main app, you just need to dismiss it. :)

How to present a view controller from appdelegate xcode 11 swift 5

I have been searching all day on how to present a view controller from within the appdelegate. It appears that in xcode 11 the window property was moved to the scenedelegate which has been confusing me. I want to present a view controller from within the appdelegate from the didReceiveRemoteNotification function so when the user receives a notification it takes them to a separate view controller with information. I have tried to do:
self.window?.rootViewController?.present(LoginViewController(), animated: false, completion: nil)
within the appdelegate which used to work in a previous application of mine but it does not seem to work anymore. Any help would be much appreciated.
I was able to solve this issue by using shared windows to get the window from scenedelegate to present the view controller on.
UIApplication.shared.windows.first?.rootViewController?.present(vc, animated: false, completion: nil)
Best approach to present view controller through app delegate is without falling for hierarchy like below:
if let vc = UIStoryboard(name: "YOURSTORYBOARD", bundle: nil).instantiateViewController(withIdentifier: "YOURVIEWCONTROLLER") as? YOURVIEWCONTROLLER {
if let window = self.window, let rootViewController = window.rootViewController {
var currentController = rootViewController
while let presentController = currentController.presentedViewController {
currentController = presentController
}
currentController.present(vc, animated: true, completion: nil)
}
}

Attempt to present UINavigationController whose view

AppDelegate
initialViewController = storyboard.instantiateViewControllerWithIdentifier("LoginViewController")as! UIViewController
}
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
Takes me to LoginViewController
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("CardsNavController") as? UIViewController
self.presentViewController(vc!, animated: true, completion: nil)
I click on the login button takes me to CardsViewController
func goToProfile(button: UIBarButtonItem) {
pageController.goToPreviousVC()
}
Clicking on back button which is part of the UINavigationController runs goToProfile (above) and takes me to ViewController.swift because that's where the pageController is declared.
let pageController = ViewController(transitionStyle: UIPageViewControllerTransitionStyle.Scroll, navigationOrientation: UIPageViewControllerNavigationOrientation.Horizontal, options: nil)
The error shows up here
func goToPreviousVC() {
//let currentControllers = self.navigationController?.viewControllers
if viewControllers.isEmpty {
setViewControllers([profileVC], direction: UIPageViewControllerNavigationDirection.Reverse, animated: true, completion: nil)
pageController.presentViewController(profileVC, animated: true, completion: nil)
else {
let previousVC = pageViewController(self, viewControllerBeforeViewController: viewControllers[0] as! UIViewController)!
setViewControllers([previousVC], direction: UIPageViewControllerNavigationDirection.Reverse, animated: true, completion: nil)
}
Error:
Warning: Attempt to present <UINavigationController: 0x15ce314e0> on <MyApp.ViewController: 0x15cd2c6f0> whose view is not in the window hierarchy!
Basically the flow is like this AppDelegate -> LoginViewController -> ViewController and I need to get to ProfileViewController.
ProfileView has ProfileNavController while CardsView has CardsNavController
ViewController has a storyboardID of pageController.
Both ProfileView and and CardsView are embedded within UINavigationControllers (hence the NavController extension).
The second time I run the app after a fresh install it works perfectly (all the controllers get loaded okay). Should I push viewControllers in AppDelegate?
I've checked your code using Xcode 7, which may not be ideal for resolving this issue because I had to covert your code to Swift 2.0, but here was what I found out.
ISSUE
First time opening the app, this block:
if currentUser() != nil {
initialViewController = pageController
}
else {
initialViewController = storyboard.instantiateViewControllerWithIdentifier("LoginViewController") as UIViewController
}
self.window?.rootViewController = initialViewController
Will initialize LoginViewController and make it the current window's rootViewController.
At this point there is no pageController initialized
When user taps on the button to go to the Profile screen, this method will be called
func goToProfile(button: UIBarButtonItem) {
pageController.goToPreviousVC()
}
At this point, pageController is initialized, and off course, there is NOTHING in the viewControllers array. Let's see what happen in the goToPreviousVC method:
Original method looks like this:
let nextVC = pageViewController(self, viewControllerAfterViewController: viewControllers[0] as UIViewController)!
setViewControllers([nextVC], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: nil)
One thing you can see obviously is: calling viewControllers[0] could give you a crash because viewControllers is an empty array.
If you use Swift 2.0, it doesn't even let you compile your code :)
SOLUTION
Let's go directly to the solution: Ensure that the pageController is available before trying to call it's viewControllers.
I blindly tried fixing you code in Swift 2.0 and found out that this method would work for you:
BEFORE: In LoginViewController.swift line 63
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("CardsNavController") as? UIViewController
self.presentViewController(vc!, animated: true, completion: nil)
AFTER: Let's fix it like this
let navc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("CardsNavController") as! UINavigationController
if let viewControllers = pageController.viewControllers where viewControllers.count == 0 {
pageController.setViewControllers([navc.viewControllers[0]], direction: .Forward, animated: false, completion: nil)
}
self.presentViewController(pageController, animated: true, completion: nil)
It's working well here and probably I don't need to show you how the screen transition should look like :)
In case you would like to have the fixed source code as well, please find it HERE. Basically I converted your code to Swift 2.0 and ignored unnecessary parts like Facebook authentication for faster investigation.
Good luck & Happy coding!

Resources