1

I am trying to implement a VoIP app using call Kit. A particular scenario is accepted incoming call and end already speaking call. Once call gets answered I show my connected call interface in OutgoingCallController.

So in the particular scenario, I am trying to dismiss the already connected OutgoingCallController and then trying to present new OutgoingCallController again with new Incoming call Details.

The logic I am trying to present OutgoingCallController is these lines of code. I am calling this method in CXProviderDelegate class.

func displayOutgoingScreen() throws {

        SwiftyBeaver.verbose("Starting displayOutgoingScreen!! ")
        let storyBoard = UIStoryboard.init(name: DiallerProperties.storyBoard, bundle: nil)
        let incomingcallController:OutgoingCallWithAllController = storyBoard.instantiateViewController(withIdentifier: "outgoingWithAll") as! OutgoingCallWithAllController
        //incomingcallController.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
        incomingcallController.number = AppDelegate().getIncomingCallNumber()
        SwiftyBeaver.verbose("Incoming call number is: \(incomingcallController.number)")
        DispatchQueue.main.async(execute: { () -> Void in

            let keyWindow =  UIApplication.shared.keyWindow
            var controller:UIViewController = (keyWindow?.rootViewController)!
            while  controller.presentedViewController != nil {
                controller=controller.presentedViewController!
            }
            let dateFormatter : DateFormatter = DateFormatter()
            dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
            let date = Date()
            let dateString = dateFormatter.string(from: date)
            let interval = date.timeIntervalSince1970
            print("presenting view here \(dateString) \(interval)!!")
            controller.present(incomingcallController, animated: true, completion: nil)
        });
    }

The particular date code is used for logging purposes to see the time this line of code is getting executed.

In OutgoingCallWithAllController viewWillDisappear method, I used the same date logic to see when that particular method is getting executed.

This is the output I'm receiving.

viewWillDisappear test 2019-03-01 13:34:32 1551427472.314188!!
presenting view here 2019-03-01 13:34:32 1551427472.324421!!

This is the warning message which I am getting below above lines in my debugger log.

2019-03-01 13:34:32.344713+0530 [710:214818] Warning: Attempt to present <OutgoingCallWithAllController: 0x1040e9600> on <OutgoingCallWithAllController: 0x104042600> whose view is not in the window hierarchy!

This is the piece of code I'm using to dismiss my old OutgoingCallController. outgoingvc contains reference to my OutgoingCallController

AppDelegate.shared.outgoingvc?.dismiss(animated: false, completion: nil)

Since I'm trying to dismiss the old outgoingCallController and present it again I can understand that I'm trying to present new outgoingCallController on the old one which has been already dismissed.

So I tried to delay the presenting new controller using below piece of code.

DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
                //providing delay so that outgoing call controller has been closed
                action.fulfill()
                SwiftyBeaver.verbose("Executing after delay!!")

            }

But still, I don't have control. How can I fix this particular issue?

If in case already looked into this so questions:

Warning: Attempt to present * on * whose view is not in the window hierarchy - swift

Swift 3 Attempt to present whose view is not in the window hierarchy

I do understand I'm getting the old OutgoingCallController as a top view.

halfer
  • 19,824
  • 17
  • 99
  • 186
Jeeva
  • 1,791
  • 2
  • 23
  • 42
  • Try to check if `controller ` is really the top viewController or not, print it and use view debugging to see which one is the top viewController – Tj3n Mar 01 '19 at 08:43
  • Seems like you are dismissing and presenting view controllers at same time. Try present the viewcontroller in the completion of dismiss view controller – Akhilrajtr Mar 01 '19 at 08:51
  • @Akhilrajtr yes i understand that but i still cant prevent it.how to achieve my desired result – Jeeva Mar 01 '19 at 09:15
  • can you update the question with code used to dismiss the previous ongoing view controller? – Akhilrajtr Mar 01 '19 at 09:23
  • @Akhilrajtr Added code – Jeeva Mar 01 '19 at 09:50
  • from where you are calling this `AppDelegate.shared.outgoingvc?.dismiss(animated: false, completion: nil)`? and try presenting the new VC in the completion block of dismiss. – Akhilrajtr Mar 01 '19 at 10:39
  • @Akhilrajtr akhil displaying is called from perform action: CXAnswerCallAction – Jeeva Mar 01 '19 at 10:48
  • @Akhilrajtr i just called the presenting outgoing controller again if i see the top view is outgoingcall controller and now it seems to work – Jeeva Mar 01 '19 at 10:49
  • so you are checking top view controller in loop? – Akhilrajtr Mar 01 '19 at 10:50
  • see the question above i have a while loop which gets me top view controller – Jeeva Mar 01 '19 at 10:51
  • im calling the function again if i see outgoingcontroller is on top after a delay of one second – Jeeva Mar 01 '19 at 10:52

3 Answers3

0

I fixed it by trying to fetch the top most view controller after interval of 0.5 seconds.
ie Im calling my displayOutgoingScreen function after interval of 0.5 seconds.
During that time my old Outgoing controller happens to be dismissed completely and I'm able to see my outgoing screen.

Jeeva
  • 1,791
  • 2
  • 23
  • 42
0

I think that you are trying to present the Outgoing view controller instance before another instance is dismissed completely.

By dispatching with a delay, you have only masked this issue.

I would suggest to you that if you already have a modally presented view controller is stack, and you dismiss it, you call your presenting function inside a completion block of dismiss function. That way, the next view controller will be presented when the previous one is removed completely from the view hierarchy.

But i still don't think it is a good User experience.

Miki
  • 903
  • 7
  • 26
  • thanks miki for your suggestion but my problem is it happens for end and accept call through using callkit delegate so im stuck there and i went with my solution – Jeeva Mar 07 '19 at 05:35
  • but let me give a try with your suggestion – Jeeva Mar 07 '19 at 05:35
-1

You can get top view controller using below extension:

extension UIApplication {
    class func getTopMostViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
        if let nav = base as? UINavigationController {
            return getTopMostViewController(base: nav.visibleViewController)
        }
        if let tab = base as? UITabBarController {
            if let selected = tab.selectedViewController {
                return getTopMostViewController(base: selected)
            }
        }
        if let presented = base?.presentedViewController {
            return getTopMostViewController(base: presented)
        }
        return base
    }
 }

Use this extension to get top most view controller in navigation stack using below code:

guard let topVC = UIApplication.getTopMostViewController() else {return}

Preset your view controller using top view controller:

DispatchQueue.main.async(execute: { () -> Void in
    topVC.present(incomingcallController, animated: true, completion: nil)
}
Hardik Halani
  • 290
  • 2
  • 12