Where to use popToRootViewController to pop extra VC off stack? - ios

I have a view controller with two buttons:
Button1 brings up the documentPickerViewController and lets user pick a file.
Button2 goes to a second view controller.
Button1 is linked to "openFile" in the code below.
Button2 calls the segue to the second view controller.
So this is how I get the problem:
Click Button1, document picker shows up.
If I pick a document, then document picker disappears and I'm back to view controller.
So far so good.
Now I press Button2. Second view controller shows up. All good. I exit and go back to 1st view controller.
Now I press Button1 again. Document picker shows up.
But this time I click "cancel". Document picker disappears but the SECOND view controller appears!
I get
"Unbalanced calls to begin/end appearance transitions for <_UIWaitingForRemoteViewContainerViewController: 0x122206480>." and
"[DocumentManager] The view service did terminate with error: Error Domain=_UIViewServiceErrorDomain Code=1 "(null)" UserInfo={Terminated=disconnect method}"
From researching I understand that I must have popped an extra 2nd view controller to the stack but I can't see where I would have done it and also where would be the appropriate place to pop it?
I've tried setting "animated: false" and that didn't make any difference.
Thanks in advance.
#IBAction func openFile(_ sender: Any) {
let documentPicker: UIDocumentPickerViewController = UIDocumentPickerViewController(documentTypes: ["public.text"], in: UIDocumentPickerMode.import)
documentPicker.delegate = self
documentPicker.modalPresentationStyle = UIModalPresentationStyle.fullScreen
self.present(documentPicker, animated: true, completion: nil)
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
self.dismiss(animated: true, completion: nil)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
if controller.documentPickerMode == UIDocumentPickerMode.import {
var textRead = ""
do {
if urls.count > 0 {
for i in 0...urls.count-1 {
textRead = try String(contentsOf: urls[i], encoding: .utf8)
textView.text = textView.text + textRead
}
}
}
catch {
/* error handling here */
print("There's a problem reading the file")
}
}
}

I'm not sure what you want to do in your implementation of documentPickerWasCancelled(_:) but know that if you just want to hide the document picker, you don't need to explicitely call self.dismiss(animated: true, completion: nil): when you tap 'Cancel' the document picker dismisses itself already.

After more experimenting and researching, I found this stopped the 2nd view controller from appearing, even though the two error messages are still appearing:
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
//self.dismiss(animated: true, completion: nil)
self.navigationController?.popToRootViewController(animated: true)
}
Would still appreciate if someone can suggest a more elegant solution which eliminates the error messages altogether.
EDIT:
After playing with it a bit more, I found the fix!
We only need this:
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
// do nothing. The picker is dismissed anyway.
}
So the dismiss statement was causing the problem. By leaving out any dismiss picker call, the picker window closes anyway.

Related

Modul should show modal/UIViewController in main thread but it doesnt work without delay

I trying to present UIViewController in my React native app from Swift module
I am presenting it like this
let screen = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
screen?.rootViewController?.present(payController!, animated: true, completion: nil);
and I get this error:
UIApplication.windows must be used from main thread only
ok I have to add it to my thread
DispatchQueue.main.async {
let screen = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
screen?.rootViewController?.present(payController!, animated: true, completion: nil);
}
and it works fine when I call this function with small delay
setTimeout(() => {
showMyWiew();
}, 500);
or delay into swift like this
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let screen = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
screen?.rootViewController?.present(payController!, animated: true, completion: nil);
}
but if I remove the delay then this modal is not shown. But it should be there. and i see this in log in swift which confirms my theory:
[Presentation] Attempt to present <PKAddPaymentPassViewController: 0x103d6b2e0> on <UIViewController: 0x103c25480> (from <UIViewController: 0x103c25480>) which is already presenting <RCTModalHostViewController: 0x10e21df40>.
PKAddPaymentPassViewController is my UIViewController
I don't know how to fix this issue...
UPDATE
Based on first comment I did this
DispatchQueue.main.async {
let screen = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
let controller = screen?.rootViewController?.presentedViewController;
if controller == nil {
screen?.rootViewController?.present(payController!, animated: true, completion: nil);
} else {
screen?.rootViewController?.presentedViewController?.present(payController!, animated: true, completion: nil)
}
}
and the good news is the modal will show up but immediately is close I see the open effect and then close effect ...
SOLUTION
all in the update section
refactoring RN code
It turned out There is open another modal before this new. The old modal is not closed before I open the new one ... so I close the old modal faster and then I show new and all is good
That is because at that exact moment screen?.rootViewController seems to already present another ViewController (RCTModalHostViewController). A VC that is already presenting some VC can not present yet another VC. What you could do is to call present on the VC that is being presented by the rootViewController at that moment: screen?.rootViewController.presentedViewController?.present(...). (Presenting on a presented ViewController does work. Presenting on a VC that presents another VC does not.)
If you do that you should also make sure that presentedViewController is actually set and not nil. If it is nil you can just call present on rootViewController as you did earlier.

SWIFT 4 Present viewcontroller

I am trying to building a app using the master details template.
in the Master view controller I added a button called Catalogue : this button showing a tabbar controller called Catalogue.
I don't use Segue to show the catalogue, I use the code below to show the tab controller
From Master form I called the Tabbar controller :
#IBAction func Btn_Catalogue(_ sender: Any) {
let AddCatalogueVC = self.storyboard?.instantiateViewController(withIdentifier: "CatalogueVC") as! CatalogueVC
present(AddCatalogueVC, animated: true, completion: nil)
}
From CategorieVC I use the code below to show
#IBAction func Btn_AddCategorie(_ sender: Any) {
self.Mode = "New"
let AddCategorieViewController = self.storyboard?.instantiateViewController(withIdentifier: "AddCategorieVC") as! AddCategorieVC
present(AddCategorieViewController, animated: true, completion: nil)
}
I dismiss the AddCategorieVC using the code below
#IBAction func Btn_Save(_ sender: Any)
{
if self.Txt_CategorieName.text != ""{
self.Mysave = true
self.MyCategorieName = self.Txt_CategorieName.text!
self.dismiss(animated: true, completion: nil)
}
}
I have unwind SEGUE from Save button to a function in categorieVC
#IBAction func FctSaveCategories(_ sender: UIStoryboardSegue) {
let sendervc = sender.source as! AddCategorieVC
if self.Mode == "New" && sendervc.Mysave == true { // Mode insert
let MyCategories = TblCategorie(context: Mycontext)
MyCategories.categorie_Name = sendervc.MyCategorieName
do {
try Mycontext.save()
} catch {
debugPrint ("there is an error \(error.localizedDescription)")
}
}
}
The problem is when I hit the save button in categorieVC the catalogueVC is also dismissing at the same time returning me to the master control.
I am almost sure that the problem came from the Unwind segue but I don't know why.
Update: I activate the Cancel button in AddCategorieVC with the code below
self.dismiss(animated: true, completion: nil)
and when I clicked on it only the AddCategorieVC is being dismissed and I go back to CatalogueVC. The difference between the save button and the Cancel Button is only the UNWIND segue on the Save Button.
And when I add UnWIND segue to the cancel Button (just to test the behavior) it took me back to the master form instead CatalogueVC.
How can I solve that?
And yesss I found it
It look like that unwind segue automaticly handled dismiss contrĂ´le
So all I need to do is remove the dismiss code from the save button this way the unwind segue will took me back to catalogueVC.
.

How do I prevent a navigationController from returning to root when dismissing MFMailComposeViewController

When I dismiss an instance of MFMailComposeViewController or MFMessageComposeViewController that is presented modally from the third viewController in a navigation stack, the navigation stack is reset, and the root VC is reloaded. How can I prevent this behavior and remain on the original presenting viewController (third VC in the stack)? I get the same behavior whether I call dismiss from the presenting VC, the presented VC, or the navigationController.
This has been asked before, but I have not seen a solution.
App Structure looks like this:
TabBarController
Tab 1 - TripsNavController
-> Trips IntroductionVC (root VC) segue to:
-> TripsTableViewController segue to:
-> TripEditorContainerVC
- TripEditorVC (child of ContainerVC)
- HelpVC (child of ContainerVC)
Tab 2...
Tab 3...
Tab 4...
In the TripEditorVC I present the MFMailComposeViewController. The functions below are declared in an extension to UIViewController that adopts the MFMailComposeViewControllerDelegate protocol
func shareWithEmail(message: NSAttributedString) {
guard MFMailComposeViewController.canSendMail() else {
showServiceError(message: "Email Services are not available")
return
}
let composeVC = MFMailComposeViewController()
composeVC.setSubject("My Trip Plan")
composeVC.setMessageBody(getHTMLforAttributedString(attrStr: message), isHTML: true)
composeVC.mailComposeDelegate = self
present(composeVC, animated: true, completion: nil)
}
Then in the delegate method I dismiss the MFMailComposeVC:
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
switch result {
case .sent:
print("Mail sent")
case .saved:
print("Mail saved")
case .cancelled:
print("Mail cancelled")
case .failed:
print("Send mail failed")
}
if error != nil {
showServiceError(message: "Error: \(error!.localizedDescription)")
}
dismiss(animated: true, completion: nil)
}
I have tried the following to present and dismiss and get the same behavior, i.e.: the TripsNavController clears the nav stack and reloads the TripsIntroductionVC as its root VC:
self.present(composeVC, animated: true, completion: nil)
self.parent?.present(composeVC, animated: true, completion: nil)
self.parent?.navigationController?.present(composeVC, animated: true, completion: nil)
self.navigationController?.present(composeVC, animated: true, completion: nil)
You can also check with presentingViewController?.dismiss method to get the solution.
I have tried with following navigation stack.
I am able to send email successfully from Container VC's Send Email button using your code only.
Can you please check and verify navigation flow?
Please let me know if you still face any issue.
dismiss(animated: true, completion: nil)
to
self.dismiss(animated: true, completion: nil)
Try this
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let secondViewController = storyboard.instantiateViewControllerWithIdentifier("secondViewControllerId") as! SecondViewController
self.presentViewController(secondViewController, animated: true, completion: nil)
or
https://stackoverflow.com/a/37740006/8196100
Just Use Unwind Segue its Pretty simple and perfect in your case
I have used many times..
just look at this Unwind segue's example
. If u still don't able to find answer or not able to understand the Unwind segue then just reply to me.
Hope this will solve your problem.
I found the problem with my navigation stack today. I built a simple
project that duplicated the tabBarController/NavigationControler architecture of my problem project, component by component, until dismissing a MFMailComposeViewController caused my navigation stack to reset as described in my original post.
That immediately pointed to the source of the bug. In my subclassed UINavigationCotroller I was instantiating the root viewController in code so that I could skip an introductory view if the user had set a switch in the apps settings. In order to pick up changes in that switch setting I was calling my instantiation code in viewDidAppear of the navigationController. That worked fine EXCEPT when dismissing the mailComposeVC. The fix was to add a guard statement in viewDidAppear to return if the navControllers viewController collection was not empty, and send and respond to an NSNotification when the switch was changed.
class TopNavigationController: UINavigationController {
var sectionType: SectionType?
var defaults = UserDefaults.standard
var showIntroFlag: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
// Handle initial load of the tab bar controller where we are not sent a sectionType
if sectionType == nil {
sectionType = .groups
}
setShowIntroFlag()
NotificationCenter.default.addObserver(self, selector: #selector(resetControllers), name: NSNotification.Name(rawValue: "kUserDidChangeShowIntros"), object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
guard self.viewControllers.isEmpty else {
return
}
loadControllers()
}
func setShowIntroFlag() {
showIntroFlag = true
// Check NSUserDefaults to see if we should hide the Intro Views for all sections
if defaults.bool(forKey: "SHOW_SECTION_INTROS") == false {
showIntroFlag = false
}
}
func loadControllers() {
if showIntroFlag == true {
showIntro()
} else {
skipIntro()
}
}
func resetControllers() {
setShowIntroFlag()
loadControllers()
}

Dismiss pushed view controller

So I have a view controller which I display as follows:
func showProfileForTrainer(trainer: Trainers) {
let viewPTProfileVC = ViewPTProfileVC()
viewPTProfileVC.trainer = trainer
self.navigationController?.pushViewController(viewPTProfileVC, animated: true)
}
But when trying to dismiss the view, I cannot get it to work. It has a back button in a navigation bar which functions fine, but when trying to dismiss the view via a button for example, it does nothing. I have currently tried:
func handleMessageTrainer() {
dismiss(animated: true) {
print(1)
self.tabBarVC?.showChatLogForTrainer(trainer: self.trainer!)
}
self.dismiss(animated: true) {
print(2)
self.tabBarVC?.showChatLogForTrainer(trainer: self.trainer!)
}
navigationController?.dismiss(animated: true, completion: {
print(3)
self.tabBarVC?.showChatLogForTrainer(trainer: self.trainer!)
})
self.navigationController?.dismiss(animated: true, completion: {
print(4)
self.tabBarVC?.showChatLogForTrainer(trainer: self.trainer!)
})
print(5)
}
As you can see I have tried varying ways and none work, and the console just outputs 5.
Frustratingly, elsewhere in my app I presented a view in the same way as shown at the beginning and it dismissed fine using dismiss(animated: true) { ... }
Any idea why it won't work here?
You must pop the view controller from the corresponding navigation controller:
self.navigationController?.popViewController(animated: true)
If you are using pushviewcontroller method then to dismiss you have to use popviewcontroller method.
Try this:
self.navigationController?.popViewController(animated: true)

Presenting view controllers on detached view controllers is discouraged - screen view turns black

I have two view controllers: VC1 and VC2. From VC1, press a button will navigate app to VC2. VC2 has a network request function in viewDidLoad() which shows an alert if the request is failed.
If everything is fine, no request failure on VC2, when I move back to VC1, it would call to deinit function of VC2.
However, if the request failed and an error alert is shown, the deinit function (of VC2) wouldn't be called when I move back VC1. Moreover, it causes an error of "Presenting view controllers on detached view controllers is discouraged" while it's trying to show that alert after the screen displays VC1, screen then turns black except for navigation bar and the error alert of VC2 is shown on VC1 (The reason is when the VC2 is going to present the error alert, user suddenly press the back button on navigation bar to move back to previous screen). My alert is a global variable.
Here is the code I handle request and show alert on VC2:
func sendRegisterRequest() {
registerAPI.request(parameters: parameters) {
[weak self] (response) in
if let strongSelf = self {
strongSelf.handleResponse(response)
}
}
}
func handleResponse(response: Response<AnyObject, NSError>) {
let result = ResponseHandler.responseHandling(response)
if result.messageCode != MessageCode.Success {
// Show alert
handleResponseError(LocalizedStrings.registerFailedTitle, message: result.messageInfo, requestType: .Register)
return
}
}
func handleResponseError(title: String, message: String?, requestType: RequestType?) {
alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
let action = UIAlertAction(title: LocalizedStrings.okButton, style: UIAlertActionStyle.Default) { (action) -> Void in
self.handleAlertViewAction(requestType)
}
alert.addAction(action)
dispatch_async(dispatch_get_main_queue(), {
self.presentViewController(self.alert, animated: true, completion: nil)
})
}
I attach the screenshot here:
Could anyone have solution for this issue? Any help could be appreciated,
Lucy Nguyen.
I've got the same problem when I build my app. To solve this problem, I tried many changes and finally removed the error message.
I made an alert window in first VC to give user some notice. And I write the alert control code in - (void)viewDidLoad. I think you did it same or in - (void)viewWillAppear.
Just move your alert code to - (void)viewDidAppear. Then, error will be gone.

Resources