Unable to tap background to close popover/actionSheet Swift 5 - Very weird - ios

The code below allows the user to do a 2 finger swipe down on an imageView and thus presenting a popover/actionSheet. That process works fine. Normally it is possible to tap outside the popover/actionSheet to close it.
The problem is that once the popover/actionSheet is presented, it doesn't allow tapping the background to close the popover/actionSheet. You actually need to tap inside the popover/actionSheet to close it.
There are other places in the app that present a popover/actionSheet but these are presented using a simple button tap.
Here's the really weird scenario. If I do the 2 finger swipe on the imageView and open the popover/actionSheet, the inability to tap the backGround is broken on all the other popover/actionSheet in the app too. If I bypass the 2 finger swipe on the imageView all of the other popover/actionSheet work as normal.
I've stripped out all the code other than what's needed to present the popover/actionSheet. And I created a new project with on VC and one imageView so as to eliminate any possible conflict with cocoa pod, etc.
What is wrong with this code?
class ViewController: UIViewController
{
#IBOutlet weak var imageView_Outlet: UIImageView!
override func viewDidLoad()
{
super.viewDidLoad()
imageView_Outlet.isUserInteractionEnabled = true
let swipeGuesture = UISwipeGestureRecognizer(target: self, action: #selector(imageViewSwiped(recognizer:)))
swipeGuesture.numberOfTouchesRequired = 2
swipeGuesture.direction = .down
imageView_Outlet.addGestureRecognizer(swipeGuesture)
}
#objc func imageViewSwiped(recognizer: UISwipeGestureRecognizer)
{
let theAlert = UIAlertController(title: "Welcome Image", message: "Only one image can be saved as your welcome screen. The current image will automatically be replaced." , preferredStyle: .actionSheet)
let chooseImage = UIAlertAction(title: "Choose a New Image", style: .default, handler: { (okAction) in
})
let deleteBtn = UIAlertAction(title: "Delete the Current Image", style: .destructive, handler: { (deleteAction) in
})
let cancelBtn = UIAlertAction(title: "Cancel", style: .cancel) { (cancelAction) in
}
theAlert.addAction(cancelBtn)
theAlert.addAction(chooseImage)
theAlert.addAction(deleteBtn)
let popOver = theAlert.popoverPresentationController
popOver?.sourceView = self.imageView_Outlet
popOver?.sourceRect = self.imageView_Outlet.bounds
popOver?.permittedArrowDirections = .any
present(theAlert, animated: true)
}
}

Related

How To Show Keyboard When UIAlert Present Which The UIAlert With Custom UITextLabel View

How to show keyboard when UIAlertController is present with Custom View and UITextField? I mean I want to keyboard automatically show without user touch the UITextField in alert view.
My code like below to make a Alert.
func callAlertConfirmation() {
let vc = UIViewController()
vc.preferredContentSize = CGSize(width: 250, height: 70)
let textBookmark = UITextField(frame: CGRect(x: 10, y: 0, width: 230, height: 40))
textBookmark.placeholder = "Typing folder name"
textBookmark.font = UIFont.systemFont(ofSize: 15)
textBookmark.textColor = UIColor.black
textBookmark.borderStyle = UITextField.BorderStyle.roundedRect
textBookmark.autocorrectionType = UITextAutocorrectionType.no
textBookmark.keyboardType = UIKeyboardType.alphabet
textBookmark.returnKeyType = UIReturnKeyType.done
textBookmark.contentVerticalAlignment = UIControl.ContentVerticalAlignment.center
textBookmark.textAlignment = NSTextAlignment.left
textBookmark.clearButtonMode = .whileEditing
vc.view.addSubview(textBookmark)
let alert = UIAlertController(title: "Create New Folder", message: nil, preferredStyle: .alert)
alert.setValue(vc, forKey: "contentViewController")
let actOKButton = UIAlertAction(title: "OK", style: .default) { (_) -> Void in
// action When User Okay
}
alert.addAction(actOKButton)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
alert.preferredAction = actOKButton
present(alert, animated: true)
}
And when I call
callAlertConfirmation
I get the result like this picture:
But I want to picture like below when I call
callAlertConfirmation
But when I use
alert.addTextField
I get keyboard automatically show when alert present.
Thanks in advance.
What
As #C4747N already said, you need to call
.becomeFirstResponder()
When
You want to call this method as you present the alert:
present(alert, animated: true) {
textBookmark.becomeFirstResponder()
}
The way you want to "read" this is like:
Present the alert and once you're done execute the completion body (make the alert's textField first responder)
I may be wrong but whenever I want a UITextView or UITextField to show a keyboard immediately i do:
textBookmark.becomeFirstResponder()

Conditional change between Child Views in PageView

I have a PageViewController, P, that contains two child ViewControllers, A and B. Both A and B allow a user to enter some data into a form. If the user begins editing the form, I keep track in a boolean variable:
var formEdited = false;
In the event that the user would like to move away from the form, and formEdited is true, I'd like to warn them and say "Are you sure you want to abandon the changes you have in the form?". In the event that they are sure, I'd like to store their data. Otherwise, I'd let them discard the data and move on with their swiping.
As a result, I tried doing something like this in both A and B:
override func viewWillDisappear(_ animated: Bool) {
if (formEdited) {
let dialogMessage = UIAlertController(title: "Confirm", message: "Are you sure you want to abandon the changes you have in the form?", preferredStyle: .alert);
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
super.viewWillDisappear(animated);
})
let cancel = UIAlertAction(title: "Cancel", style: .cancel) { (action) -> Void in
// TODO:: what to do here
self.myCustomFuctionToStoreData();
super.viewWillAppear(true);
}
dialogMessage.addAction(ok);
dialogMessage.addAction(cancel);
self.present(dialogMessage, animated: true, completion: nil);
}
}
As a result, I can see my popup when I try to swipe away from the View. If I click "Cancel", the view remains. ( Which is what I want ) However, if I retry to swipe again, I no longer see the alert box, and the UI changes. ( Which is not what I want. I want it to re-prompt )
I believe that my code needs to react more appropriately when a viewWillDisappear. I think I need to somehow prevent the view from disappearing after this line above:
// TODO:: what to do here
Note: I've tried answers from a few other posts, like this: How do I Disable the swipe gesture of UIPageViewController? , Disable swipe gesture in UIPageViewController , or even these two : Disable UIPageViewController Swipe - Swift and Checking if a UIViewController is about to get Popped from a navigation stack? .
The last two might be most appropriate, but I don't want to disable any gestures nor do i see how i can inject a prevention. I simply want to make the swiping away from a child view a conditional function. How would I do this from my child view ( child of PageView ) in Swift 4 ?
It turns out that implementing conditional scroll operations in a UIPageView is trivial. These are the steps I've taken to solve the problem. ( Updates to this code to make it more efficient are encouraged )
For starters, your UIPageViewController must not be the dataSource. This means that during your scroll operations in child view controllers, nothing will register. ( Which is ok for now ) Instead, you'd want to implement logic for which view is shown when as functions that can be called by the children. These two methods can be added to your UIPageView :
func goToNextView(currentViewController : UIViewController) {
var movingIdx = 0;
let viewControllerIndex = orderedViewControllers.index(of: currentViewController) ?? 0;
if (viewControllerIndex + 1 <= (orderedViewControllers.count - 1)) {
movingIdx = viewControllerIndex + 1;
}
self.setViewControllers([orderedViewControllers[movingIdx]], direction: .forward, animated: true, completion: nil);
}
func goToPreviousView(currentViewController : UIViewController) {
var movingIdx = 0;
let viewControllerIndex = orderedViewControllers.index(of: currentViewController) ?? -1;
if (viewControllerIndex == -1) {
movingIdx = 0;
} else if (viewControllerIndex - 1 >= 0) {
movingIdx = viewControllerIndex - 1;
} else {
movingIdx = orderedViewControllers.count - 1;
}
self.setViewControllers([orderedViewControllers[movingIdx]], direction: .reverse, animated: true, completion: nil);
}
Notes:
It would make sense to update the lines containing ?? 0; to a way to trow an error, or show some default screen.
orderedViewControllers is a list of all child views that this UIPageView controller contains
These methods will be called from child views, so keeping them at this layer makes them very reusable
Lastly, in your child views, you'll need a way to recognize gestures and react on gestures:
override func viewDidLoad() {
super.viewDidLoad();
let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(handleGesture))
swipeLeft.direction = .left
self.view.addGestureRecognizer(swipeLeft)
let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(handleGesture))
swipeRight.direction = .right
self.view.addGestureRecognizer(swipeRight)
}
And handle the gesture:
#objc func handleGesture(gesture: UISwipeGestureRecognizer) -> Void {
if gesture.direction == UISwipeGestureRecognizerDirection.left {
parentPageViewController.goToNextView(currentViewController: self);
}
else if gesture.direction == UISwipeGestureRecognizerDirection.right {
parentPageViewController.goToPreviousView(currentViewController: self);
}
}
Notes:
In handleGesture function, you'd add your conditional checks to determine if goToNextView or goToPreviousView is ok.

UIAlertView's action sheet didn't separating cancel button from others in IOS 12.2

I'm trying to achieve something like this:
I'm follwing this Question's answer: Change background color of Cancel button for action sheet
and so far using the answer I have achived this:
But this is only working upto IOS 11, When I run the app on IOS 12.2 emulator the cancel button didn't separate from other buttons.
Here you can see:
I wanted to ask is it a latest IOS bug or Am I missing something for latest IOS?
Here is my Code:
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: "Weekly View", style: .default, handler: nil))
alertController.addAction(UIAlertAction(title: "Monthly View", style: .default, handler: nil))
if let firstSubview = alertController.view.subviews.first, let alertContentView = firstSubview.subviews.first {
for view in alertContentView.subviews {
view.backgroundColor = .blue
}
}
alertController.view.tintColor = UIColor(red: 1.0, green: 0.2, blue: 0.33, alpha: 1)
let cancelButtonViewController = CancelButtonViewController()
let cancelAction = UIAlertAction(title: "", style: .cancel, handler: nil)
cancelAction.setValue(cancelButtonViewController, forKey: "contentViewController")
alertController.addAction(cancelAction)
present(alertController, animated: true, completion: nil)
And CancelButtonViewController code:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
let buttonText = UILabel()
buttonText.text = "Cancel"
buttonText.font = UIFont(name: "AppleSDGothicNeo-Regular", size: 20)
buttonText.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(buttonText)
buttonText.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
buttonText.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
And one thing more I'm also getting a Constraints error by default whenever I create AlertView with .actionSheet style. Maybe that could be the reason. I have done some research and found this post in this post they suggested to ignore this error.
Error:
I hope my question didn't irritate you. As I'm a noob in IOS programming. Thanks

Add image on center of Alert ViewController with transparent background

How to display image in center of alert viewController with transparent background of alert viewController.
I wrote following code.
let image = UIImage(named: "ic_no_data")
let imageView = UIImageView(frame: CGRectMake(220, 10, 40, 40)
let alertMessage = UIAlertController(title: "image", message: "", preferredStyle: .Alert)
let backView = alertMessage.view.subviews.last?.subviews.last
backView?.layer.cornerRadius = 10.0
backView?.backgroundColor = UIColor.lightGrayColor()
alertMessage.view.addSubview(imageView)
let action = UIAlertAction(title:"", style: .Cancel, handler: nil)
action.setValue(image, forKey: "image")
alertMessage .addAction(action)
self.presentViewController(alertMessage, animated: true, completion: nil)
It looks like this.
Please help me to solve this problem and thanks to all.
UIAlertController doesn't support such changes in view hierarchy you'd be better using a custom extensible component that mimics the behavior of UIAlertController. The problem with UIAlertController is that you don't know how Apple will change its view hierarchy in the next version of iOS. And each time they change something you'll have to add code for a specific version of iOS which is really bad from the maintainability point of view.
For example CustomIOSAlertView: https://github.com/wimagguc/ios-custom-alertview.
you can store you reference of imageView an alertMessage, and in completion of the present viewController you have the exact size of the alert view, so you can adjust the image according to the current alert rect.
try this code is in swift3 :
self.present(alertMessage, animated: true, completion: {
var frame = self.alertMessage.view.frame
frame.origin = CGPoint(x: 0, y: 0) // add your location
self.imageView.frame = frame
})

UIAlertController/ActionSheet crashes on iPad, how to pass sender?

I want to display an ActionSheet on both, iPhone and iPad devices. While my code works properly on iPhone's, it doesn't on iPad's. The reason for this is that I need to specify a location where to display the popover. As a result, I tried to used the code proposed in this answer. The problem that I currently have is, that I do not know how to pass the argument sender to the method.
For example, I have a UITableViewRowAction which when clicked should display the ActionSheet:
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let rowAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Action", handler:{action, indexpath in
print("Action for element #\(indexPath.row).");
self.displayActionSheet()
});
return [rowAction];
}
Which argument is sender in my case? I am not able to pass rowAction to displayActionSheet(), because the variable is used within its own initial value. I also tried to pass self.displayDeleteLicencePlateActionSheet(self.items[indexPath.row]), but same result – I always end up in the else clause of the guard expression:
guard let button = sender as? UIView else {
print("sender empty")
return
}
I want to display also an ActionSheet when clicking on an UIBarButtonItem:
let myButton = UIBarButtonItem(title: "FooBar", style: .Plain, target: self, action: #selector(SecondViewController.displaySecondActionSheet(_:)))
But same result. How can this be done?
Please refer following code to fix your issue.
TARGET_OBJECT will be your sender where from you want to show an alert.
func showAlert() {
let alert = UIAlertController(title: "Title", message: "Message text", preferredStyle: .alert)
let actionOK = UIAlertAction(title: "OK", style: .default) { (alertAction) in
}
alert.addAction(actionOK)
if let popoverController = alert.popoverPresentationController {
popoverController.sourceView = self.TARGET_OBJECT // TARGET_OBJECT will be your sender to show an alert from.
popoverController.sourceRect = CGRect(x: self.TARGET_OBJECT.frame.size.width/2, y: self.TARGET_OBJECT.frame.size.height/2, width: 0, height: 0)
}
UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: true, completion: nil)
}
Please check attached image it will appear as you want.

Resources