Disable Dismiss Popover On Background Tap Swift - swift

I have a main view controller called TestViewController that has a button and when you tap the button, it opens a popover view controller. When you tap on the background, the popover gets dismissed which is what I want to disable. I have this code in my popover view controller and it should run but it's not running.
extension TestViewController: UIPopoverPresentationControllerDelegate {
func popoverPresentationControllerShouldDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) -> Bool {
print ("TEST") //This does not show up in console
return false
}
}
EDIT:
This is the code that I use to open the popover.
let popover = storyboard?.instantiateViewController(withIdentifier: "PopoverVC") as! PopOverViewController
popover.modalPresentationStyle = .popover
popover.popoverPresentationController?.sourceView = self.view
popover.popoverPresentationController?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
popover.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
popoverPresentationController?.passthroughViews = nil
popover.dimView2 = self.dimView2
dimView2.isHidden = false
self.present(popover, animated: false)
}

Set the delegate.
popover.popoverPresentationController?.delegate = self

popoverPresentationControllerShouldDismissPopover function is deprecated in iOS 14.
For latest version you should use following code
extension TestViewController: UIPopoverPresentationControllerDelegate {
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
return false
}
}

Related

How to present a ViewController as a Modal Sheet without the background shadow

Is there any way to present a ViewController as a Modal Sheet without the background shadow as shown in the first image below using swift. Is there an easy way or should we need to write custom UIPresentationController? [![The required output][1]][1]
[1]: https://i.stack.imgur.com/QAEEn.png![enter image description here](https://i.stack.imgur.com/q4JD5.jpg)
You can use a smaller size view controller as per your need. Firstly add a class.
class SmallSizePresentationController : UIPresentationController {
override var frameOfPresentedViewInContainerView: CGRect {
get {
guard let theView = containerView else {
return CGRect.zero
}
return CGRect(x: 0, y: theView.bounds.height * (281 / 896), width: theView.bounds.width, height: theView.bounds.height)
}
}
}
Then when you want to present this type of view controller just add the extension of your current view controller.
extension YourCurrentViewController: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return SmallSizePresentationController(presentedViewController: presented, presenting: presenting)
}
}
Then present your new view controller like this
let VC = withoutShadowVC()
VC.transitioningDelegate = self
VC.modalPresentationStyle = .custom
self.present(VC, animated: true, completion: nil)
You can modify the view controller height.

How to handle a built in AlertController / Email Prompt from appearing behind my view

My app contains a modal UIView that can be presented from anywhere. How this works is the present method attaches the view as a subview on the key window:
func present(_ completion: ((Bool) -> ())? = { _ in }) {
guard !isPresented else {
return
}
if !isBackgroundReady {
initializeBackground()
}
UIApplication.shared.keyWindow?.addSubview(backgroundView)
UIApplication.shared.keyWindow?.addSubview(self)
UIView.animate(withDuration: 0.3, animations: {
self.backgroundView.alpha = 0.35
self.alpha = 1.0
}, completion: { _ in
self.isPresented = true
completion?(true)
})
}
private func initializeBackground() {
backgroundView.backgroundColor = UIColor.black
backgroundView.alpha = 0.0
backgroundView.frame = CGRect(x: UIScreen.main.bounds.midX, y: UIScreen.main.bounds.midY, width: UIScreen.main.bounds.width * 1.2, height: UIScreen.main.bounds.height * 1.2)
backgroundView.center = CGPoint(x: UIScreen.main.bounds.midX, y: UIScreen.main.bounds.midY)
}
This modal contains an email link that users can click that opens up an email prompt (or an email action sheet if they long press it). This link is added by using the an NSAttributedString and it's .link attribute on a UITextView:
let supportString = NSMutableAttributedString(
string: "general.supportEmail".localized(),
attributes: [
.link: "mailto:\("general.supportEmail".localized())",
]
)
supportTextView.attributedText = supportString
However, when the email prompt or action sheet appears, it is displayed behind the modal view:
Is it possible to get the prompt/action sheet to appear above the modal view with the current way I present the modal, or will I need to add some sort of recognizer somewhere that detects when one of these views appears and temporarily dismiss the modal until my app view comes back into focus? If it's the later, how would I accomplish that?
The quick answer as to why this is happening is that you are presenting your custom modal view on top of the Window, which will be on top of everything, and your UIAlertController will be presented on the UIViewController presenting it (which is below your custom view).
One quick solution would be to always add your custom view as a subview on the current "top" UIViewController. You can do that with a UIViewController extension - something like this:
extension UIViewController {
static func topViewController(_ viewController: UIViewController? = nil) -> UIViewController? {
let viewController = viewController ?? UIApplication.shared.keyWindow?.rootViewController
if let navigationController = viewController as? UINavigationController, !navigationController.viewControllers.isEmpty {
return self.topViewController(navigationController.viewControllers.last)
} else if let tabBarController = viewController as? UITabBarController,
let selectedController = tabBarController.selectedViewController
{
return self.topViewController(selectedController)
} else if let presentedController = viewController?.presentedViewController {
return self.topViewController(presentedController)
}
return viewController
}
}
This extension will handle any UIViewController that is "on top", whether it's in a UINavigationController, a UITabBarController, or just presented modally, etc. Should cover all cases.
After that you can adjust your present method to take this into account:
func present(_ completion: ((Bool) -> ())? = { _ in }) {
guard !isPresented else {
return
}
if !isBackgroundReady {
initializeBackground()
}
guard let topViewController = UIViewController.topViewController() else { return }
topViewController.view.addSubview(backgroundView)
topViewController.view.addSubview(self)
UIView.animate(withDuration: 0.3, animations: {
self.backgroundView.alpha = 0.35
self.alpha = 1.0
}, completion: { _ in
self.isPresented = true
completion?(true)
})
}
Instead of adding in the window, add the modal in the navicontroller -> topviewcontroller.
Link: https://developer.apple.com/documentation/uikit/uinavigationcontroller/1621849-topviewcontroller.
This might help you.

How to show and hide the keyboard with a subview

I have a custom UIView that is a subview on a UIViewController.
I have it added in my storyboard and set it to Hidden.
My subview is also within another UIView that I'm using as a 'blur view' which is also initially Hidden.
I have functions that will unhide & hide the subviews.
My custom subview has a UITextField. I can show the keyboard and move the subview up with no problems. When I type in the keyboard or dismiss it my subview moves up and to the left. When I try to show my subview again it shows incorrectly (up and to the left).
The custom subview starts at the center of my screen.
The goal is move it up when the keyboard shows so it will not cover the subview or the UITextField, allow the user to type in the UITextField, and then dismiss the keyboard and move the custom subview back to the center.
In my UIViewController:
// Showing the custom sub view
func displayCustomSubView() {
if let window = UIApplication.shared.keyWindow {
self.blurView.isHidden = false
self.customSubView.isHidden = false
self.blurView.frame = window.frame
self.customSubView.center = window.center
window.addSubview(self.blurView)
UIApplication.shared.keyWindow?.bringSubviewToFront(self.blurView)
}
}
// Hiding the custom sub view
// the custom sub view has a button I tap to hide
#objc func dismissCustomSubView() {
self.blurView.isHidden = true
self.customSubView.isHidden = true
}
// Show Keyboard
// Since I am using the window to make sure my blur view expands to the full frame, I have tried just moving the window up
#objc func keyboardWillShow(sender: NSNotification) {
if let window = UIApplication.shared.keyWindow {
window.frame.origin.y = -75
}
}
// Hide Keyboard
#objc func keyboardWillHide(sender: NSNotification) {
if let window = UIApplication.shared.keyWindow {
window.frame.origin.y = 0
}
}
// Custom Subview Extension
extension CustomSubView: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
Added the Custom Subview Extension above.
First add this notification within your viewDidLoad(). And make a global variable called var keyboardH: CGFloat = 0:
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillShow),
name: UIResponder.keyboardWillShowNotification,
object: nil
)
And this function below:
#objc func keyboardWillShow(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
self.keyboardH = keyboardHeight
}
This function is called every time the keyboard is present and will reveal the keyboard height and later we can use this variable.
So in your code:
#objc func keyboardWillShow(sender: NSNotification) {
if let window = UIApplication.shared.keyWindow {
let position = window.frame.origin.y - keyboardH
window.frame.origin.y = position
}
}

Possible to create a UITableView that appears from the corner of a View Controller

I would like to model a table view after one found in the Todoist app (see below), is it possible to do this without and additional framework? If so how would I go about doing this?
You can create a view controller with a UITableView and present it as UIPopoverPresentationController
let vc = UIViewController()
vc.modalPresentationStyle = .popover
vc.preferredContentSize = CGSize(width: 200, height: 200)
let popUp = vc.popoverPresentationController
popUp?.permittedArrowDirections = .up
popUp?.delegate = self
popUp?.sourceView = sender as! UIView // here set the frame of the button that the arrow points to when popup is shown
present(vc, animated: true, completion: nil)
//
Inside the vc that you presents the popup make it implements the delegate UIPopoverPresentationControllerDelegate and write this method
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}

Swift 3 Popover Dim Background

I have read multiple places with suggestions on how to accomplish this. I went with adding a UI view in the background and setting it to disable and then after showing the popover, setting the view to enable.
As you can see it looks to work nicely:
But I do have two problems. The first one is once the popover is presented, you can tap anywhere on the background to dismiss the popover. Is there anywhere to block this from happening? I assumed my background UIView would block any inputs.
Also, after the popover is dismissed, the screen is still dim. I tried the following but neither of them load after dismissing the popover so the View never gets set back to disable:
override func viewDidAppear(_ animated: Bool) {
dimView.isHidden = true
}
override func viewWillAppear(_ animated: Bool) {
dimView.isHidden = true
}
EDIT:
This is the code that I use to present the popover:
let popover = storyboard?.instantiateViewController(withIdentifier: "PopoverVC")
popover?.modalPresentationStyle = .popover
popover?.popoverPresentationController?.delegate = self as? UIPopoverPresentationControllerDelegate
popover?.popoverPresentationController?.sourceView = self.view
popover?.popoverPresentationController?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
popover?.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
dimView.isHidden = false
self.present(popover!, animated: false)
I realize that, dimView is not in PopoverVC, add it into PopoverVC and handle dismiss when tap on it.After the popover is dismissed viewDidAppear and viewWillAppear will not be called. So your screen is still blurry.If you add dimView into Popover, hope you can solve these issuses
I think you could solve your two problems with the UIPopoverPresentationControllerDelegate and a protocol/ delegate to tell the presenting viewcontroller when your are dismissing and hide your dimView.
The first issue can be implemented like this:
extension YourViewController: UIPopoverPresentationControllerDelegate {
func popoverPresentationControllerShouldDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) -> Bool {
return false
}
For the second issue you can pass a function through delegation. Hopefully this link will help with that.
https://matteomanferdini.com/how-ios-view-controllers-communicate-with-each-other/
Cheers