This is my first application for iOS.
So I have a UIVIewController with a UITableView where I have integrated a UISearchBar and a UISearchController in order to filter TableCells to display
override func viewDidLoad() {
menuBar.delegate = self
table.dataSource = self
table.delegate = self
let nib = UINib(nibName: "ItemCellTableViewCell", bundle: nil)
table.registerNib(nib, forCellReuseIdentifier: "Cell")
let searchButton = UIBarButtonItem(barButtonSystemItem: .Search, target: self, action: "search:")
menuBar.topItem?.leftBarButtonItem = searchButton
self.resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
return controller
})()
self.table.reloadData()
}
I am using also a modal segue in order to open the element's ViewController where I will display details of the element.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.index = indexPath.row
self.performSegueWithIdentifier("ItemDetailFromHome", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "ItemDetailFromHome") {
let settingsVC = segue.destinationViewController as! ItemDetailViewController
settingsVC.parent = self
if self.isSearching == true && self.searchText != nil && self.searchText != "" {
settingsVC.item = self.filteredItems[self.index!]
} else {
settingsVC.item = self.items[self.index!]
}
}
}
That works fine until I try to display the ItemDetailViewController for a filtered element (through the UISearchController).
I have the following message :
Warning: Attempt to present <ItemDetailViewController: *> on <HomeViewController: *> which is already presenting (null)
At every time I am going to the ItemDetailViewController.viewDidLoad() function but after that when the search is activated I have the previous error.
Any idea ? I have tried to use the following async dispatch but without success
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.index = indexPath.row
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.performSegueWithIdentifier("ItemDetailFromHome", sender: self)
})
}
I have found out a solution.
I have add the following code in HomeViewController.viewDidLoad and that works !
definesPresentationContext = true
In my case, I found my code to present the new viewController (a UIAlertController) was being called twice.
Check this before messing about with definesPresentationContext.
In my case, I tried too early to show the new UIViewController before closing the previous one. The problem was solved through a call with a slight delay:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.callMethod()
}
The problem for me is that I was presenting two modals and I have to dismiss both and then execute some code in the parent window ... and then I have this error... I solved it seting this code in the dismiss o the last modal presented:
self.dismiss(animated: true, completion: {
self.delegate?.callingDelegate()
})
in other words instead of just dismiss two times .. in the completion block of the first dismiss call delegate that will execute the second dismiss.
What worked for me was to add the presentation of the alert to the main thread.
DispatchQueue.main.async {
self.present(alert, animated: true)
}
The presentation of the current viewController was not complete. By adding the alert to the main thread it can wait for the viewController's presentation to complete before attempting to present.
I got the same issue when i tried to present a VC which called inside the SideMenu(jonkykong).
first i tried inside the SideMenu and i called it from the delegate to the MainVC both had the same issue.
Solution: dismiss the SideMenu first and present the new VC after will works perfectly!.
This happened with me on our project. I was presenting our log in/log out ViewController as a pop-over. But whenever I tried to log back out again and display the pop-over again, I was getting this logged out in my console:
Warning: Attempt to present UIViewController on <MY_HOME_VIEW_CONTROLLER> which is already presenting (null)
My guess is that the pop-over was still being held by my ViewController even though it was not visible.
However you are attempting to display the new ViewController, the following code I used to solve the issue should work for you:
func showLoginForm() {
// Dismiss the Old
if let presented = self.presentedViewController {
presented.removeFromParentViewController()
}
// Present the New
let storyboard = UIStoryboard(name: "MPTLogin", bundle: Bundle(for: MPTLogin.self))
let loginVC = storyboard.instantiateViewController(withIdentifier: "LogInViewController") as? MPTLogInViewController
let loginNav = MPTLoginNav(rootViewController: loginVC!)
loginNav.modalPresentationStyle = .pageSheet;
self.present(loginNav, animated: true, completion: nil)
}
I faced the same kind of problem
What I did is from Interface builder selected my segue
Its kind was "Present Modally"
and its presentation was "Over current context"
i changed the presentation to "Default", and then it worked for me.
In my case I was trying to present a UIAlertController at some point in the app's lifetime after using a UISearchController in the same UINavigationController.
I wasn't using the UISearchController correctly and forgot to set searchController.isActive = false before dismissing. Later on in the app I tried to present the alert but the search controller, though not visible at the time, was still controlling the presentation context.
My problem was that (in my coordinator) i had presented a VC on a VC and then when i wanted to present the next VC(third one), presented the third VC from the first one which obviously makes the problem which is already presenting.
make sure you are presenting the third one from the second VC.
secondVC.present(thirdVC, animated: true, completion: nil)
Building on Mehrdad's answer: I had to first check if the search controller is active (if the user is currently searching):
if self.searchController.isActive {
self.searchController.present(alert, animated: true, completion: nil)
} else {
self.present(alert, animated: true, completion: nil)
}
where alert is the view controller to present modally.
This is what finally worked for me, as my project didn't exactly have a NavigationVC but instead, individual detached VC's. as xib files
This code produced the bug:
present(alertVC, animated: true, completion: nil)
This code fixed the bug:
if presentedViewController == nil{
navigationController?.present(alertVC, animated: true, completion: nil)
}
For me it was an alert that was interfering with the new VC that I was about to present.
So I moved the new VC present code into the OK part of my alert, Like this :
func showSuccessfullSignupAndGoToMainView(){
let alert = UIAlertController(title: "Alert", message: "Sign up was successfull.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { action in
switch action.style{
case .default:
// Goto Main Page to show businesses
let mainStoryboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let vc : MainViewController = mainStoryboard.instantiateViewController(withIdentifier: "MainViewController") as! MainViewController
self.present(vc, animated: false, completion: nil)
case .cancel:
print("cancel")
case .destructive:
print("destructive")
}}))
self.present(alert, animated: true, completion: nil)
}
My issue was that I was trying to present an alert from a view that wasn't on top. Make sure you present from the top-most viewController.
In my case this was an issue of a button which was duplicated in Interface Builder. The original button had a touch-up handler attached, which also presented a modal view. When I then attached a touch-up handler on the copied button, I forgot to remove the copied handler from the original, causing both handlers to be fired and thus creating the warning.
More than likely you have your Search button wired directly to the other view controller with a segue and you are calling performSegueWithIdentifier. So you are opening it twice, which generates the error that tells you "is already presenting."
So don't call performSegueWithIdentifier, and that should do the trick.
Make sure you Dismiss previous one before presenting new one!
Related
I am using MessageKit 3.0.0-swift5 branch for chats.
Clicking on the message, I am presenting the ViewController.
When Viewcontroller is dismissed, I am not able to access InputBar.
Has anybody come across this issue?
Check the video here.
Code:
// MessageCellDelegate
func didTapMessage(in cell: MessageCollectionViewCell) {
self.showFileInBrowser(withTitle: "", url: fileURL)
}
func showFileInBrowser(withTitle title: String? = nil, url: URL) {
self.fileBrowser = FileBrowserViewController(title: title, url: url)
let navigation = BaseNavigationController(rootViewController: fileBrowser!)
self.present(navigation, animated: true, completion: nil)
}
// FileBrowserViewController
#objc func closeButtonTapped() {
self.dismiss(animated: true, completion: nil)
}
I am also using IQKeyboardManager, but the below solution is not working.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
IQKeyboardManager.shared().isEnabled = false
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
IQKeyboardManager.shared().isEnabled = true
}
I had the same issue in my application, which is using just InputBarViewController (in some reasons I had to implement my own chat, not using MessageKit).
So, this issue is very easy to reproduce if you are trying to show some modal view controller not from current controller, or even moving first responder focus to another UITextField within current VC (e.g. search field).
For me working solution is just call becomeFirstResponder on current view controller instance just after resigning control from search field or inside viewDidAppear (or in some other place where you can be sure, chat UI is visible again).
P.S. I also faced this issue when UIContextMenu (long touch in cell) was dismissed. Solution was pretty the same.
I think that you have to before present the next ViewController disable Keyboard by TextView.resignFirstResponder() Because the problem begins while ViewController is Presenting
I too facing that issue before, but i tried to to present nextViewController as by adding the following code. hope it will work.
nextViewController.modalPresentationStyle = .overCurrentContext
nextViewController.modalTransitionStyle = .coverVertical
I have a stack of UIViewControllers like A -> B -> C. I want to go back to controller A from C. I'm doing it with below code:
DispatchQueue.global(qos: .background).sync {
// Background Thread
DispatchQueue.main.async {
self.presentingViewController?.presentingViewController?.dismiss(animated: false, completion: {
})}
}
It works but controller B seen on screen although I set animated to false. How can I dismiss two UIViewControllers without showing the middle one (B)?
P.S: I can't just directly dismiss from root controller and also I can't use UINavigationController
I searched the community but can't find anything about the animation.
Dismiss more than one view controller simultaneously
Try this.
self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)
Created a sample storyboard like this
The yellow view controller is type of ViewController and the button action is as follows
#IBAction func Pressed(_ sender: Any) {
self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)
}
Output
I've created example for dismissing B controller before showing C controller. You can try it.
let bController = ViewController()
let cController = ViewController()
aController.present(bController, animated: true) {
DispatchQueue.main.asyncAfter(wallDeadline: .now()+2, execute: {
let presentingVC = bController.presentingViewController
bController.dismiss(animated: false, completion: {
presentingVC?.present(cController, animated: true, completion: nil)
})
})
}
But on my opinion solution with using navigation controller would be the best for the case. For example you can put just B controller into navigation controller -> present the navController onto A controller -> then show C inside the navController -> then dismiss from C controller whole navController -> And you will see A controller again. Think about the solution too.
Another solution
I've checked another solution.
Here extension which should solve your problem.
extension UIViewController {
func dissmissViewController(toViewController: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
self.dismiss(animated: flag, completion: completion)
self.view.window?.insertSubview(toViewController.view, at: 0)
dissmissAllPresentedControllers(from: toViewController)
if toViewController.presentedViewController != self {
toViewController.presentedViewController?.dismiss(animated: false, completion: nil)
}
}
private func dissmissAllPresentedControllers(from rootController: UIViewController) {
if let controller = rootController.presentedViewController, controller != self {
controller.view.isHidden = true
dissmissAllPresentedControllers(from: controller)
}
}
}
Usage
let rootController = self.presentingViewController!.presentingViewController! //Pointer to controller which should be shown after you dismiss current controller
self.dissmissViewController(toViewController: rootController, animated: true)
// All previous controllers will be dismissed too,
// but you will not see them because I hide them and add to window of current view.
But the solution I think may not cover all your cases. And potentially there can be a problem if your controllers are not shown on whole screen, all something like that, because when I simulate that transition I don't consider the fact, so you need to fit the extension maybe to your particular case.
I have 3 ViewController : LoginViewController, CheckinViewController, & ProfileViewController
The flow is :
LoginVC --> CheckinVC --> ProfileVC
What i need is:
I want to dismiss "ProfileVC" & "CheckinVC" when click logout button in "ProfileVC" then go back to the "LoginVC"
LoginVC.swift
let checkinViewController = self.storyboard?.instantiateViewController(withIdentifier: "CheckinViewController") as! CheckinViewController
self.navigationController?.pushViewController(checkinViewController, animated: true)
JustHUD.shared.hide()
self.dismiss(animated: false, completion: nil)
CheckinVC.swift
if let profileView = self.storyboard?.instantiateViewController(withIdentifier: "ProfileViewController") {
profileView.providesPresentationContextTransitionStyle = true
profileView.definesPresentationContext = true
profileView.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext;
// profileView.view.backgroundColor = UIColor.init(white: 0.4, alpha: 0.8)
profileView.view.backgroundColor = UIColor.clear
profileView.view.isOpaque = false
self.present(profileView, animated: true, completion: nil)
Here is i'm trying to do
ProfileVC.swift
#IBAction func clickLogout(_ sender: Any) {
UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
UserDefaults.standard.synchronize()
self.dismiss(animated: false, completion: {
print("ProfileView : dismiss completed")
let loginViewController = self.storyboard?.instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController
self.navigationController?.pushViewController(loginViewController, animated: true)
self.dismiss(animated: false, completion: {
print("SUCCESS")
})
})
}
Well you need to do an Unwind Segue so you can go back in your LoginVC.
Follow these simple four steps to create Unwind segues:
In the view controller you are trying to go back to, LoginVC in your example, write this code:
#IBAction func unwindToVC1(segue:UIStoryboardSegue) { }
( Remember: It’s important to insert this method in the view
controller you are trying to go back TO! )
In the Storyboard, go to the screen you’re trying to unwind from ( ProfileVC in our case ), and control + drag the viewController icon to the Exit icon located on top.
3. Go to the document outline of the selected viewController in the Storyboard, select the unwind segue as shown below.
Now, go to the Attributes Inspector in the Utilities Pane and name the identifier of the unwind segue.
Finally, write this code where you want the unwind segue action to be triggered, ProfileVC in our case.
#IBAction func clickLogout(_ sender: Any) {
UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
UserDefaults.standard.synchronize()
performSegue(withIdentifier: "unwindSegueToVC1", sender: self)
}
For more information check Create Unwind Segues
While Enea Dume's solution is correct if you want to use storyboards, here is the explanation of the problem, and the solution if you want to do it in code like you have been so far.
The Issue
If we focus on the self.dismiss calls in the logoutFunction in ProfileVC, this is what happens.
The first time you call self.dismiss, ProfileVC will dismiss itself and be removed from the view stack.
In the completion delegate, you are pushing a new LoginVC to a navigation controller. However, the CheckIN VC is presented over the navigation controller so you can't see anything happen.
The second call to self.dismiss does nothing as ProfileVC is not presenting any other view controllers and it is not in the stack any longer.
The Solution
You need to keep a reference to LoginVC that presented the CheckInVC. If you call "reference to LoginVC".dismiss, it will dismiss the view controllers above it in the stack and take you back to the login view controller.
In the class CheckinVC.swift, in the function viewDidAppear, check if the user is still active or not based on the session that you are maintaining and the pop to login view controller accordingly. If user status is logged out, then it will go to login view controller. Else it will work as usual.
Problem is that you are trying to dismiss twice ProfileVC but what you actually need is to dismiss it and then pop CheckinVC.
Also after dismissing a view controller it no longer has a reference to the NavigationController so you need a reference to it in a temporal variable.
Change ProfileVC like this:
#IBAction func clickLogout(_ sender: Any) {
UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
UserDefaults.standard.synchronize()
let navigationController = self.navigationController
let loginViewController = storyboard?.instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController
self.dismiss(animated: false, completion: {
print("ProfileView : dismiss completed")
navigationController?.pushViewController(loginViewController, animated: true)
navigationController?.popViewController(animated: false)
})
}
I would like to show a popup (I just have UIAlertController in mind) like the the image below:
What I want to achieve
I found some kind of similar questions on the internet but they are code based, this way it is really hard to align the stuff in the position I want and handle the user interaction.
Is there a way that I kinda make a separate "Something" in the Storyboard and then here just pop that up? I thought of a whole ViewController but that covers the whole screen.
Any suggestions or solutions are highly appreciated. 🙏
You need to present it modally , so make the VC give it identifier and load it anywhere
let vc = self.storyboard?.instantiateViewController(withIdentifier: "popupID") as! PopupViewController
vc.sendedStr = "someContent"
vc.myImage = UIImage(named:"flag.png")
vc.providesPresentationContextTransitionStyle = true;
vc.definesPresentationContext = true;
vc.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
self.present(vc, animated:true, completion: nil)
//
class PopupViewController : UIViewController {
var sendedStr:String?
var myImage:UIImage?
}
More importantly is to set background of the view in IB like this
ViewController Doesn't fill the entire screen all the time. This is just the default setting.
Set .modalPresentationStyle property of your child controller to .overCurrentContext and present it modally.
Now, if you view Controller has an transparent background, it will be presented like an alert.
First design viewController in storyBoard and keep in mind your ViewController main view background color should be either clear or change its Alpha to like 0.5
Add below extension
extension UIViewController {
open func presentPOPUP(_ viewControllerToPresent: UIViewController, animated flag: Bool, modalTransitionStyle:UIModalTransitionStyle = .coverVertical, completion: (() -> Swift.Void)? = nil) {
viewControllerToPresent.modalPresentationStyle = .overCurrentContext
viewControllerToPresent.modalTransitionStyle = modalTransitionStyle
self.present(viewControllerToPresent, animated: flag, completion: completion)
}
}
After that you can use it like below
if let alerVC = self.myStoryBoard.instantiateViewController(withIdentifier: "AlertMessageVC") as? AlertMessageVC {
self.presentPOPUP(alerVC, animated: true, modalTransitionStyle: .crossDissolve, completion: nil)
}
you can also change modalTransitionStyle to below options
.coverVertical
.flipHorizontal
.crossDissolve
.partialCurl
Hope this help. :)
I was trying to implement the cancel bar button as you can see from the image, which return to the previous viewController using dismiss, but when I click the button, nothing appears, do you know why?
Here's my code to implement that:
#IBAction func cancel(_ sender: UIBarButtonItem) {
let isPresentingInAddTaskMode = presentingViewController is UINavigationController
if isPresentingInAddTaskMode {
dismiss(animated: true, completion: nil)
}
else if let owningNavigationController = navigationController{
owningNavigationController.popViewController(animated: true)
}
else {
fatalError("The AddTaskVC is not inside a navigation controller.")
}
}
Thanks in advance.
If I'm reading your question right, this is what your view controller hierarchy looks like:
For adding a new task:
Navigation Controller --(containing)--> "Managing Mode" --(modal)--> Navigation Controller --(containing)--> "Add New Task"
For editing a task:
Navigation Controller --(containing)--> "Managing Mode" --(push)--> "Add New Task"
The problem is that neither of your cases will actually work.
isPresentingInAddTaskMode
In the first case, we see this:
let isPresentingInAddTaskMode = presentingViewController is UINavigationController
if isPresentingInAddTaskMode {
dismiss(animated: true, completion: nil)
}
I'd assume this would handle the case involving the modal presentation.
This will never be true, because presentingViewController is the "Managing Mode" view controller, not the navigation controller containing it or the navigation controller containing the "Add New Task" view controller.
Another problem with this is that you have to dismiss the navigation controller containing "Add New Task", not "Add New Task" itself. This means that instead of
dismiss(animated: true, completion: nil)
you would do
navigationController?.dismiss(animated: true, completion: nil)
owningNavigationController
In the second case, we see this:
else if let owningNavigationController = navigationController{
owningNavigationController.popViewController(animated: true)
}
The first line makes sense: you're unwrapping the navigation controller. However, you then call popViewController(animated: true) on the navigation controller.
The problem with that is that both the modal and the push segues involve a navigation controller, so this case will work for both.
The Solution
You need to form a simpler cancel method using the above:
#IBAction func cancel(_ sender: UIBarButtonItem) {
guard let owningNavigationController = navigationController else {
fatalError("The AddTaskVC is not inside a navigation controller.")
}
if owningNavigationController.presentingViewController?.presentedViewController == owningNavigationController {
// modal
owningNavigationController.dismiss(animated: true, completion: nil)
} else {
// push
owningNavigationController.popViewController(animated: true)
}
}
This first unwraps the navigation controller and errors out if there is none, like what you did originally.
You then check, through a more complex way than before, if the modal segue occurred. This checks the navigation controller's presentingViewController and if it is itself. If so, it's modal and it dismisses itself. If not, it's a push segue and you pop the current view controller.