I am working on an iPad application that has both Portrait and Landscape orientation. The issue that I am facing is regarding the the popup presentation that iPad shows you for saving password while you log-in in the application.
Now, when the pop-up is presented, at that moment if I try to rotate the device, the application crashes with the message
Fatal Exception: NSInvalidArgumentException
Application tried to present modally a view controller <_SFAppPasswordSavingViewController: 0x107be7230> that is already being presented by <UIKeyboardHiddenViewController_Save: 0x107a49190>.
Now, the problem is that SFAppPasswordSavingViewController presentation is not controlled by me. Also UIKeyboardHiddenViewController_Save,is something I am not controlling.
Please help me out on how to solve this issue or let me know if I am missing anything.
This is how I am presenting Login screen in my application.
guard let loginVC = LoginVC.loadFromXIB() else {
Log.w("LoginVC could not be loaded")
return
}
let navVC = PopNavigationController(rootViewController: loginVC)
UIWindow.key?.replaceRootViewControllerWith(navVC, animated: true, completion: nil)
Here replaceRootViewControllerWith is a custom function that is an extension on UIWindow.
extension UIWindow {
func replaceRootViewControllerWith(_ replacementController: UIViewController,
animated: Bool,
completion: (() -> Void)?) {
let snapshotImageView = UIImageView(image: self.snapshot())
self.addSubview(snapshotImageView)
let dismissCompletion = { [weak self] in // dismiss all modal view controllers
self?.rootViewController = replacementController
self?.bringSubviewToFront(snapshotImageView)
if animated {
UIView.animate(withDuration: 0.4, animations: { () -> Void in
snapshotImageView.alpha = 0
}, completion: { (_) -> Void in
snapshotImageView.removeFromSuperview()
completion?()
})
} else {
snapshotImageView.removeFromSuperview()
completion?()
}
}
DispatchQueue.main.async { [weak self] in
if self?.rootViewController?.presentedViewController != nil {
self?.rootViewController?.dismiss(animated: false, completion: dismissCompletion)
} else {
dismissCompletion()
}
}
}
}
Related
I have some problems displaying a viewcontroller in my IOS app.
Sometimes it works and the view is displayed, but sometimes and I guess when the context is a bit different it will not work. No errors or warnings in the debugger and it can find the ViewController from the Main storyboard (at least it is not nil)
It use to work with self.present but that seems not to work anymore.
#IBAction func showHistoryButton(_ sender: MDCButton) {
let exercisesHistoryVC = ExercisesHistoryViewController.instantiate(from: .Main)
exercisesHistoryVC.modalPresentationStyle = .fullScreen
let appDeligate = UIApplication.shared.delegate as! AppDelegate
appDeligate.window?.rootViewController!.present(exercisesHistoryVC,animated: true,completion: nil)
// parent?.present(exercisesHistoryVC, animated: true, completion: nil)
}
Use the code like below,while Present New View Controller
#IBAction func showHistoryButton(_ sender: MDCButton) {
let exercisesHistoryVC = ExercisesHistoryViewController.instantiate(from: .Main)
exercisesHistoryVC.modalPresentationStyle = .fullScreen
UIApplication.topViewController()?.present(exercisesHistoryVC, animated: false, completion: nil)
}
extension UIApplication {
static func topViewController(base: UIViewController? = UIApplication.shared.delegate?.window??.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController, let selected = tab.selectedViewController {
return topViewController(base: selected)
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
This question already has answers here:
How to resolve: 'keyWindow' was deprecated in iOS 13.0
(27 answers)
Closed 1 year ago.
Currently, with iOS 14.6, I can call a function in my app that displays a share sheet using the following code:
func share(link: URL) {
let activityView = UIActivityViewController(activityItems: [link], applicationActivities: nil)
UIApplication.shared.windows.first?.rootViewController?.present(activityView, animated: true, completion: nil)
}
Since iOS 15 beta, Xcode tells me "'windows' was deprecated in iOS 15.0: Use UIWindowScene.windows on a relevant window scene instead". How can I update this so that my share sheet will work properly in this new version? Thanks!
(Tested with iOS 15.2 running on Xcode 13.2.1)
Improving on Rachid's answer, here is a Swiftier version:
extension UIApplication {
var keyWindow: UIWindow? {
// Get connected scenes
return UIApplication.shared.connectedScenes
// Keep only active scenes, onscreen and visible to the user
.filter { $0.activationState == .foregroundActive }
// Keep only the first `UIWindowScene`
.first(where: { $0 is UIWindowScene })
// Get its associated windows
.flatMap({ $0 as? UIWindowScene })?.windows
// Finally, keep only the key window
.first(where: \.isKeyWindow)
}
}
If you want to find the presented UIViewController in the key UIWindow , here is another extension you could find useful:
extension UIApplication {
var keyWindowPresentedController: UIViewController? {
var viewController = self.keyWindow?.rootViewController
// If root `UIViewController` is a `UITabBarController`
if let presentedController = viewController as? UITabBarController {
// Move to selected `UIViewController`
viewController = presentedController.selectedViewController
}
// Go deeper to find the last presented `UIViewController`
while let presentedController = viewController?.presentedViewController {
// If root `UIViewController` is a `UITabBarController`
if let presentedController = presentedController as? UITabBarController {
// Move to selected `UIViewController`
viewController = presentedController.selectedViewController
} else {
// Otherwise, go deeper
viewController = presentedController
}
}
return viewController
}
}
You can put this wherever you want, but I personally added it as an extension to UIViewController.
This allows me to add more useful extensions, like ones to present UIViewControllers more easily for example:
extension UIViewController {
func presentInKeyWindow(animated: Bool = true, completion: (() -> Void)? = nil) {
DispatchQueue.main.async {
UIApplication.shared.keyWindow?.rootViewController?
.present(self, animated: animated, completion: completion)
}
}
func presentInKeyWindowPresentedController(animated: Bool = true, completion: (() -> Void)? = nil) {
DispatchQueue.main.async {
UIApplication.shared.keyWindowPresentedController?
.present(self, animated: animated, completion: completion)
}
}
}
I am trying to dismiss a View Controller that has been presented as a modal .overCurrentContext.
The controller is presented like so
let vc= UIViewController()
vc.modalPresentationStyle = .overCurrentContext
present(vc, animated: true, completion: nil)
When I call dismiss inside the vc controller that has appeared it does nothing.
To give more detail about the parent ViewController, it is a ViewController inside a NavigationController and it is third on the NavigationController's VC stack.
So the actual solution was very obscure.
I was using a Pod called FloatingPanelController that was causing some issues with dismissing my entire view stack intermittently.
In order to resolve it I modified an extension in the Pod
public extension UIViewController {
#objc func fp_original_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
// Implementation will be replaced by IMP of self.dismiss(animated:completion:)
}
#objc func fp_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
// Call dismiss(animated:completion:) to a content view controller
if let fpc = parent as? FloatingPanelController {
if fpc.presentingViewController != nil {
self.fp_original_dismiss(animated: flag, completion: completion)
} else {
fpc.removePanelFromParent(animated: flag, completion: completion)
}
return
}
// Call dismiss(animated:completion:) to FloatingPanelController directly
if let fpc = self as? FloatingPanelController {
if fpc.presentingViewController != nil {
self.fp_original_dismiss(animated: flag, completion: completion)
} else {
fpc.removePanelFromParent(animated: flag, completion: completion)
}
return
}
// For other view controllers
self.fp_original_dismiss(animated: flag, completion: completion)
}
}
In particular I removed the last line:
self.fp_original_dismiss(animated: flag, completion: completion)
This is what caused the issue. I had no idea that this method was actually overriding the entire dismiss method. Once I re-inserted that line the issue was resolved.
This is likely very little use to anyone else!
I am using ReplayKit in an app to record the visible screen with some text and a video playing. The issue I am facing is that ReplayKit is working just fine for the first screen recording, but if I am to record again in the same session (ie without closing the app) it runs into this error:
MyViewController[423:39346] viewServiceDidTerminateWithError:: Error Domain=_UIViewServiceInterfaceErrorDomain Code=3 "(null)" UserInfo={Message=Service Connection Interrupted}
In this scenario, I am actually trying to screen record on the same ViewController (only with a different video being played and some text content altered). Below is my recording code:
#objc func startRecording() {
let recorder = RPScreenRecorder.shared()
recorder.startRecording{ [unowned self] (error) in
if let unwrappedError = error {
print(unwrappedError.localizedDescription)
print("NOT Recording")
} else {
self.video.play()
print("Recording")
self.isRecording = true
}
}
recordIcon.isHidden = true
ring.isHidden = true
}
#objc func stopRecording() {
let recorder = RPScreenRecorder.shared()
recorder.stopRecording( handler: { previewViewController, error in
if let error = error {
print("\(error.localizedDescription)")
}
// Handling iPads
if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad {
previewViewController?.modalPresentationStyle = UIModalPresentationStyle.popover
previewViewController?.popoverPresentationController?.sourceRect = CGRect.zero
previewViewController?.popoverPresentationController?.sourceView = self.view
}
if previewViewController != nil {
self.previewViewController = previewViewController
previewViewController?.previewControllerDelegate = self
}
self.present(previewViewController!, animated: true, completion: nil)
})
isRecording = false
recordIcon.isHidden = false
ring.isHidden = false
return
}
func previewControllerDidFinish(_ previewController: RPPreviewViewController) {
dismiss(animated: true)
}
Any help on this is greatly appreciated. I'd hate to force users to have to reopen the app before recording again.
It might be that your app keeps the screen recording longer than it should. If this is the case, try implementing the discardRecording(handler: #escaping () -> Void) function. Here are more details on the discardRecording.
I need to download some content from API when users login for the first time at my app and show them I'm doing that. I do this at my MainViewController:
override func viewDidAppear(_ animated: Bool) {
let alert = UIAlertController(title: nil, message: "Wait please...", preferredStyle: .alert)
let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
loadingIndicator.hidesWhenStopped = true
loadingIndicator.style = UIActivityIndicatorView.Style.gray
loadingIndicator.startAnimating();
alert.view.addSubview(loadingIndicator)
present(alert, animated: true, completion: nil)
let parceiroId = self.defaults.getParceiroId()
if !self.defaults.getDownloadInicialTipoEntrega() {
TipoEntregaAPI().loadTiposEntrega(parceiroId){ (dados) in
if dados != nil {
for tipoEntrega in dados!{
// Do some stuff, no errors
}
}
}
}
if !self.defaults.getDownloadInicialPedido() {
PedidoAPI().loadOrders(parceiroId){ (dados) in
if dados != nil {
for pedidos in dados!{
// Do some stuff, no errors
}
}
}
}
self.dismiss(animated: false, completion: { () in print("Done") })
}
The problem is that my alert with the loading never gets dismissed. It never prints "Done". Can anyone help me please?
I dont know if it is useful, but I always get this warning:
Warning: Attempt to dismiss from view controller <MyApp.MainViewController: 0x0000000> while a presentation or dismiss is in progress!
The problem is exactly what the error says. Your presentation call isn't completed at the moment you're calling self.dismiss(...). For further explanation, you're calling present(alert, animated: true, completion: nil) with animated: true parameter so it's not going to finish the presentation instantly. On the other hand, you are calling self.dismiss(animated: false, completion: { () in print("Done") }) on the same thread in the relatively short instruction block, so it gets executed before the iOS finishes the animated presentation of the dialog and that's why you get the error.
But furthermore, before actually fixing the issue you should ask yourself do you really want to dismiss the dialog as soon as it's presented. Judging from the code you posted, I'd assume you want it to be dismissed after either or both of the API calls are finished. If that's the case you need to move the dismiss method call within the completion block (closures) of your API calls.
Solution for me (Working fine and printing "Done"):
override func viewDidAppear(_ animated: Bool) {
if !self.defaults.getDownloadInicialTipoEntrega() || !self.defaults.getDownloadInicialPedido() || !self.defaults.getDownloadInicialVitrine() {
Functions.showAlertWaiting("Wait please...", self)
}
loadDeliveryTypesNOrders { (completed) in
if completed {
self.dismiss(animated: false, completion: { () in print("Done") })
}
}
}
func loadDeliveryTypesNOrders (completion: #escaping (Bool) -> ()) {
let parceiroId = self.defaults.getParceiroId()
if !self.defaults.getDownloadInicialTipoEntrega() {
TipoEntregaAPI().loadTiposEntrega(parceiroId){ (dados) in
if dados != nil {
for tipoEntrega in dados!{
// Do some stuff, no errors
}
}
}
}
if !self.defaults.getDownloadInicialPedido() {
PedidoAPI().loadOrders(parceiroId){ (dados) in
if dados != nil {
for pedidos in dados!{
// Do some stuff, no errors
}
}
}
}
completion(true)
}