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

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.

Related

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

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)
}
}

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()

Programmatically grab a changing textField in an UIAlertController

Generally, to grab the text of a UITextField every time it is changed, you just drag an IBOutlet from the Storyboard file, and select the "editing has changed" option.
However, I want to grab the text of a UITextField in a UIAlertController, so the text field is created programmatically, not via storyboard.
How do I programmatically create a function that runs every time a textfield (in the Alert window) has changed?
What I need:
func textHasChanged(textField: TextField) {
print(textField.text)
}
If input is "cat"
Needed Output needs to be:
"c"
"ca"
"cat"
How would I achieve this?
Note: I've tried doing the .addtarget method:
textField.addTarget(self, action: Selector(self.textFieldDidChange(_:)), for: UIControlEvents.editingChanged)
with this function in the same file:
func textFieldDidChange(txtField: UITextField)
But I keep getting this error:
Value of type "name of my viewcontroller" has no member "textFieldDidChange"
I've looked up this question but most give me outdated answers. Thanks!
Try this:
#IBAction func displayAlert(_ sender : AnyObject) {
let alertController = UIAlertController(title: "Enter some text", message: "Please enter some text in the field below", preferredStyle: .alert)
alertController.addTextField()
alertController.addAction(UIAlertAction(title: "OK", style: .default) { action in
print("User pressed OK")
})
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel) { action in
print("User pressed Cancel")
})
if let textField = alertController.textFields?.first {
textField.addTarget(self, action: #selector(ViewController.textHasChanged), for: .editingChanged)
}
self.present(alertController, animated: true)
}

Share Sheet Crashing on iPad

I'm creating a share sheet where someone can share the text from a text field. I've got it working on the iPhone, but the app crashes on the iPad. I know that it has to be in a popover form and have had this issue back in the day of Objective-C, but I can't seem to figure it out on Swift. This is my code that I've got going for the share sheet:
#IBAction func myShareButton(sender: AnyObject) {
// Hide the keyboard
textView.resignFirstResponder()
// Check and see if the text field is empty
if (textView.text == "") {
// The text field is empty so display an Alert
displayAlert("Warning", message: "You haven't written anything yet!")
} else {
// We have contents so display the share sheet
displayShareSheet(textView.text!)
}
}
// Show Warning
func displayAlert(title: String, message: String) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)
alertController.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
presentViewController(alertController, animated: true, completion: nil)
return
}
// Display Share Sheet
func displayShareSheet(shareContent:String) {
let activityViewController = UIActivityViewController(activityItems: [shareContent as NSString], applicationActivities: nil)
presentViewController(activityViewController, animated: true, completion: {})
}
I implemented share sheets today as well and had the exact same issue. You need to add this before presenting.
if let popoverPresentationController = activityController.popoverPresentationController {
popoverPresentationController.sourceView = self.view
popoverPresentationController.sourceRect = CGRect(x: CGRectGetMidX(view.bounds), y: CGRectGetMidY(view.bounds), width: 0, height: 0)
popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirection.init(rawValue: 0) //Removes arrow as I dont want it
}
Line 1 sets the source view.
Line 2 I use to Center the popover right in the middle (I use it in SpriteKit and the popover is not attached to anything)
Line 3 I use to remove the arrow as I don't want it.
Hope this helps
for Swift 3/4
Depends on #crashoverride777 Answer
if let popoverPresentationController = activityVC.popoverPresentationController {
popoverPresentationController.sourceView = self.view
popoverPresentationController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirection.init(rawValue: 0) //Removes arrow as I dont want it
}

UIAlert w/ text field to table

My objective is to create a new row in the table in the master view titled with the input from the UIAlert stored in namePrompt. How would I achieve this?
Currently, when I try to make a new row (using the + button in the top-right hand corner of the iPad simulator, running iOS 8) it crashes the app. I'm not really sure what I have done wrong, and any help at all is appreciated.
Code:
func insertNewObject(sender: AnyObject) {
//prompt for kid name
var namePrompt = UIAlertController(title: "Add Child", message: "Please enter child name", preferredStyle: UIAlertControllerStyle.Alert)
namePrompt.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil))
namePrompt.addAction(UIAlertAction(title: "Add Child", style: UIAlertActionStyle.Default, handler: nil))
namePrompt.addTextFieldWithConfigurationHandler({(textField: UITextField!) in
textField.placeholder = "Johnny"
})
self.presentViewController(namePrompt, animated: true, completion: nil)
let childName: AnyObject = namePrompt.textFields![0]
//add new object w/ above name
objects.insertObject(childName, atIndex: 0)
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
I think that the problem is arising from the let childName: Any Object line, and/or the objects.insertObject line.
--EDIT--
When I build/run the project, no errors are given to me in the code, but when I try to add a new row, as soon as I press the + button, I get 0x1fbaff4: nopw %cs:(%eax,%eax) from Thread 1 . This is related to the line let object = objects[indexPath.row] as NSDate (in below snippet) line (the default for the objects.insertObject line above was an NSDAte). I changed the NSDate to NSString and the same thing happened.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
let object = objects[indexPath.row] as NSString
cell.textLabel?.text = object.description
return cell
}
You seem to be assuming that the textFields of the UIAlertController will be filled in immediately after the alert is presented. That doesn't give the user any time to actually fill out the fields.
Put the let childName = namePrompt.textFields![0] line (and following) in one of your button actions instead and wait for the user to tap that button before reading the textField.
--EDIT--
Here is your code with my suggested modification. I made as few changes to your code as possible. Note that there is an if statement to guard against empty childNames, but it won't guard against someone entering a bunch of spaces. You should probably make that part more robust.
func insertNewObject(sender: AnyObject) {
//prompt for kid name
var namePrompt = UIAlertController(title: "Add Child", message: "Please enter child name", preferredStyle: UIAlertControllerStyle.Alert)
namePrompt.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil))
namePrompt.addAction(UIAlertAction(title: "Add Child", style: UIAlertActionStyle.Default, handler: { (alertAction) in
let childName = (namePrompt.textFields![0] as UITextField).text as String
if childName != "" {
self.objects.insert(childName, atIndex: 0)
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
}))
namePrompt.addTextFieldWithConfigurationHandler({(textField: UITextField!) in
textField.placeholder = "Johnny"
})
self.presentViewController(namePrompt, animated: true, completion: nil)
}

Resources