Warning: Attempt to present ViewController on ViewController which is already presenting ViewController - swift

I have a view controller with a toolbar with 3 UIButtons that open a new view controller as a popover. I created the segues in Storyboard and selected "Present as Popover". The popovers work but when the user taps on another button while a popover is currently open, I get this error:
Warning: Attempt to present <Fingerpainter.OpacityViewController: 0x79095110> on <Fingerpainter.DrawingViewController: 0x7b278000> which is already presenting <Fingerpainter.BrushSizeViewController: 0x79573770>
Is there a way to like make sure all popovers are closed before opening a new one? Here's my prepareForSegue method in the main ViewController (containing the toolbar):
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let identifier = segue.identifier ?? ""
let popoverPresentationController = segue.destinationViewController.popoverPresentationController
popoverPresentationController!.delegate = self
switch identifier {
case Storyboard.BrushSizeSegueIdentifier:
if let brushSizeViewController = popoverPresentationController?.presentedViewController as? BrushSizeViewController {
// set properties in brushSizeViewController
}
case Storyboard.OpacitySegueIdentifier:
if let opacityViewController = popoverPresentationController?.presentedViewController as? OpacityViewController {
//set properties in opacityViewController
}
case Storyboard.ColorSegueIdentity:
if let colorViewController = popoverPresentationController?.presentedViewController as? ColorViewController {
//set properties in colorViewController
}
default:
break
}
}

Is there a way to like make sure all popovers are closed before opening a new one
It's the other way around. It's your job to make sure that while the popover is present, a button that summons another popover is not tappable. You can do this by disabling the button, but more commonly, in order to coordinate the disabling of the button with the presence of the popover, it's done by adjusting the popover presentation controller's passthroughViews.
Unfortunately there's a massive and long-standing bug where even setting the passthroughViews to nil doesn't prevent toolbar buttons from being tappable. The workaround is to do it with a delay. A lot of my popover code adds this sort of thing:
if let pop = popoverPresentationController {
delay(0.1) {
pop.passthroughViews = nil
}
}
(where delay is described here: https://stackoverflow.com/a/24318861/341994).

Related

How can I fix my navigation bar back button?

Hi I've been trying to change my back button in my application's navigation bar. The problem is that I see the change when I load the view twice.
I've searched for the answer several times but I don't see what I really want. In fact, I'm new at this language so it's difficult.
What I tried is putting the following lines in the viewWillAppear method and others*:
nav?.navigationBar.backItem?.title = "Messages"
The result is that when I enter the view I see the back button's title as Back. Then if I press on that button and enter the view again the title changes as I want. On the other hand, what I want is to change the title when I load the view first.
*-> I've tried the same line in viewDidLoad too see if that does anything and in viewWillDisappear of the previous view, but nothing happens.
You are using method a change back button in your current ViewController it is wrong, because your navigationBar configured in past controller. If you use Storyboard, please will try this method in your parent controller:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let backItem = UIBarButtonItem()
backItem.title = "Messages"
navigationItem.backBarButtonItem = backItem
}
If you init newController in your parentController, you must to modify your a backButton in parentController, example:
// It's action for any button witch present your newController
#objc private func presentDetailViewContorller() {
let weatherDetailController = WeatherDetailController()
let navController = NavigationController(rootViewController: weatherDetailController)
let backItem = UIBarButtonItem()
backItem.title = "Messages"
navController.backBarButtonItem = backItem
self.present(navController, animated: true, completion: nil)
}
Good luck!

How to check if UIViewController is already being displayed?

I'm working on an app that displays a today extension with some information. When I tap on the today extension, it opens the app and navigates to a subview from the root to display the information. Normally the user would then click the back arrow to go back to the main view, but there is no way to tell if this is actually done. It is possible for the user to go back to the today extension and tap again. When this is done, the subview is opened once again with new information. If this is done a bunch of times, I end up with a bunch of instances of the subview and I have to click the back button on each of them to get back to the main view.
My question: Is it possible to check if the subview is already visible? I'd like to be able to just send updated information to it, instead of having to display an entirely new view.
I am currently handling this by keeping the instance of the UIViewController at the top of my root. If it is not nil, then I just pass the information to it and redraw. If it is nil, then I call performSegue and create a new one.
I just think that there must be a better way of handling this.
Edit: Thanks to the commenter below, I came up with this code that seems to do what I need.
if let quoteView = self.navigationController?.topViewController as? ShowQuoteVC {
quoteView.updateQuoteInformation(usingQuote: QuoteService.instance.getQuote(byQuoteNumber: quote))
}
else {
performSegue(withIdentifier: "showQuote", sender: quote)
}
This is different from the suggested post where the answer is:
if (self.navigationController.topViewController == self) {
//the view is currently displayed
}
In this case, it didn't work because I when I come in to the app from the Today Extension, it goes to the root view controller. I needed to check whether a subview is being displayed, and self.navigationController.topViewcontroller == self will never work because I am not checking to see if the top view controller is the root view controller. The suggestions in this post are more applicable to what I am trying to accomplish.
u can use this extension to check for currently displayed through the UIApplication UIViewController:
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
and usage example:
if let topController = UIApplication.topViewController() {
if !topController.isKind(of: MainViewController.self) { //MainViewController- the controller u wish to equal its type
// do action...
}
}

Programmatically press back button for UIViewController with UITableView iOS swift

I have a UIViewController that implements UITableViewDelegate, UITableViewDataSource and that contains a UITableView as a member variable. When a user click on one of the rows of that table, the app performs a storyboard segue to open the detail view controller. That detail view controller of course has a button in the top left of the screen that is the "back" button to go back up to the UIViewController with the UIViewTable.
So, suppose that I want to programmatically "click" that back button. How exactly would I do that in swift? This is the most recent version of swift (swift 4?) in XCode 10.1.
UPDATE:
So here is how I solved this. As the answers below show, it is possible to use self.navigationController?.popViewController(animated: true) to just return to the previous view controller. What I discovered I also wanted to do, however, was to call a specific method in that view controller so that it executed a certain behavior once it got shown. It turns out that is also possible, but in my case it was a bit tricky, since that prior view controller was actually a UITabBarController. Therefore I had to get the ViewController that I was interested in from the UITabBarController. I did it like this:
let numvc = navigationController!.viewControllers.count
let tvc:UITabBarController = navigationController!.viewControllers[numvc-2] as! UITabBarController
let my_vc: MyCustomVC = tvc.viewControllers![0] as! MyCustomVC
my_vc.some_function()
Here of course MyCustomV is my custom view controller class and some_function() is the method I want to call on that class. Hope this helps someone.
When You run a segue you perform a "pushViewController" method to the next view, so if you want to go back to the previous view programmatically you just have to do is pop the last view like so:
self.navigationController?.popViewController(animated: true)
UPDATE
You just need the if statement if you have multiple segues from that viewController, if not, you can delete and just cast the next view as you wish and set the properties, let the autocomplete write the *prepare(for segue... * method for you, so You don't run into any problems
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourSegueName" {
let destinationVC = segue.destination as! CustomViewController
destinationVC.labelExample.text = "Some text I'm sending"
}
}
Are you sure you need to "click" the button?
If all you need is to dismiss details view controller, you can just call navigationController?.popViewController(animated: true)
Or if you want to deal directly with button, you can tell it to send its actions: backButton.sendActions(for: .touchUpInside)
Or if you absolutely need to show button clicking animation, then you will need something like this (you should play and choose suitable delay):
backButton.isHighlighted = true
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) {
backButton.isHighlighted = false
backButton.sendActions(for: .touchUpInside)
}

Opening window + view from an other view controller

I've got a ViewControllerOne. ViewControllerOne is connected via Ctrl-Drag (in storyboard) to a menu-button mBtn (which means I don't know how it is implemented programmatically).
Clicking on this mBtn, a ViewOne appears (present modal). This ViewOne is bound to ViewControllerOne. ViewOne has a button btnOne.
Clicking on btnOne I want ViewOne to be dismissed and ViewTwo to be shown. ViewTwo belongs to ViewControllerTwo and to WindowControllerTwo.
The WindowControllerTwo-ViewControllerTwo-binding is the standard case as created on a new project.
I have the following code in the IBAction for button btnOne in ViewControllerOne:
#IBAction func onbtnOnePressed(sender: AnyObject){
let m_WindowControllerTwo = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil).instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("WindowControllerTwo")) as! NSWindowController // I have no custom class for the window controller as I don't really know what I can use it for ...
let m_ViewTwo = WindowControllerTwo.contentViewController as! ViewControllerTwo // my custom class for ViewTwo
m_ViewTwo.attributeIWantToPassToThisView = self.x // an attribute I want to pass from view a to view b
m_WindowControllerTwo.contentViewController = m_ViewTwo // passing the attribute from a to b
m_WindowControllerTwo.showWindow(self) // this does not work
self.dismiss(nil) // see NOTE
}
This code actually does not work. On debugging it step by step, I'm seeing the window/view flickering but not appearing...
NOTE: I could connect the button btnOne with a ctrl-drag to ViewControllerTwo. This works. But then the current ViewOne does not get dismissed!
Question: What am I doing wrong here? In iOS swift this also works. I don't quite get the WindowController stuff, so I'll need your advice on this.
Instead of this: m_WindowControllerTwo.showWindow(self)
use:
let application = NSApplication.shared()
application.runModal(for: wordCountWindow) //this will present WindowControllerTwo modally.
then to close your present controller add this line: PresentWindowControllerName.close()

How to make textfield with pop out table view

Hi I am looking to make a pop out table view similar to the one below and with selectable cells. Any help would be great thanks!
Here is what I would like to style it after
You will want to make use of UIPopoverPresentationController.
Make a tableviewController with the data you want to be able to select
Make a button that will send the action so you can present the popover
Do something with the item selected from the popover.
The second item above looks something like this (my own code example):
#IBAction func shareAction(_ sender: UIButton) {
let controller = ShareVC()
controller.socialPost = self.item
controller.modalPresentationStyle = .popover
// configure the Popover presentation controller
let popController: UIPopoverPresentationController? = controller.popoverPresentationController
popController?.permittedArrowDirections = [.up, .down]
popController?.delegate = self
// in case we don't have a bar button as reference
popController?.sourceView = sender
popController?.sourceRect = sender.frame
popController?.backgroundColor = .white
self.parentViewController?.present(controller, animated: true, completion: { })
}
In my case the 'ShareVC" is a UITableViewController with item sharing options. Pretty similar to your screenshot as far as layout goes.
This will present that popover from the button that was tapped. Then again, you just need to handle the selection of the items from that ViewController.