How can I show a UIActionsheet in iPad when I'm using my current code its giving me this error:
Your application has presented a UIAlertController (<UIAlertController: 0x7f9ec624af70>) of style UIAlertControllerStyleActionSheet. The modalPresentationStyle of a UIAlertController with this style is UIModalPresentationPopover. You must provide location information for this popover through the alert controller's popoverPresentationController. You must provide either a sourceView and sourceRect or a barButtonItem. If this information is not known when you present the alert controller, you may provide it in the UIPopoverPresentationControllerDelegate method -prepareForPopoverPresentation.
which is working totally fine in an iPhone :
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .ActionSheet)
let reminderAction = UIAlertAction(title: "Reminder", style: .Default, handler: {
(alert: UIAlertAction!) -> Void in }
optionMenu.addAction(reminderAction)
self.presentViewController(optionMenu, animated: true, completion: nil)
I came across some similar problems, the solution was this:
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .ActionSheet)
optionMenu.popoverPresentationController?.sourceView = self.view
optionMenu.popoverPresentationController?.sourceRect = self.view.bounds
but it didnt worked for me maybe because my ActionSheet's Sender is on a UItableviewCell.
I tired to set AlertController's Sourceview to tableView's Cell but its not correctly placed and sometime its partially visible this is what I tried:
optionMenu.popoverPresentationController?.sourceView = currentCell.contentView
optionMenu.popoverPresentationController?.sourceRect = currentCell.contentView.bounds
Any clue how can I fix this problem?
The sample code given below works both on iPhone and iPad.
guard let viewRect = sender as? UIView else {
return
}
let cameraSettingsAlert = UIAlertController(title: NSLocalizedString("Please choose a course", comment: ""), message: NSLocalizedString("", comment: ""), preferredStyle: .ActionSheet)
cameraSettingsAlert.modalPresentationStyle = .Popover
let photoResolutionAction = UIAlertAction(title: NSLocalizedString("Photo Resolution", comment: ""), style: .Default) { action in
}
let cameraOrientationAction = UIAlertAction(title: NSLocalizedString("Camera Orientation", comment: ""), style: .Default) { action in
}
let flashModeAction = UIAlertAction(title: NSLocalizedString("Flash Mode", comment: ""), style: .Default) { action in
}
let timeStampOnPhotoAction = UIAlertAction(title: NSLocalizedString("Time Stamp on Photo", comment: ""), style: .Default) { action in
}
let cancel = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .Cancel) { action in
}
cameraSettingsAlert.addAction(cancel)
cameraSettingsAlert.addAction(cameraOrientationAction)
cameraSettingsAlert.addAction(flashModeAction)
cameraSettingsAlert.addAction(timeStampOnPhotoAction)
cameraSettingsAlert.addAction(photoResolutionAction)
if let presenter = cameraSettingsAlert.popoverPresentationController {
presenter.sourceView = viewRect;
presenter.sourceRect = viewRect.bounds;
}
presentViewController(cameraSettingsAlert, animated: true, completion: nil)
If you want to show any ActionSheet on iPad so their use popoverPresentationController to show and at iPad don't show the cancel style button of action sheet.
Use this code in Swift 3:
#IBAction func ActionSheetShow(_ sender: UIButton) {
let actionSheet = UIAlertController(title: "Choose any option", message: "choose as you like here!", preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: "Click1", style: .cancel, handler: {
action in
print("first button")
}))
actionSheet.addAction(UIAlertAction(title: "Click2", style: .default, handler: {
action in
print("second button")
}))
actionSheet.addAction(UIAlertAction(title: "Click3", style: .destructive, handler: {
action in
print("third button")
}))
actionSheet.popoverPresentationController?.sourceView = self.view
actionSheet.popoverPresentationController?.sourceRect = sender.frame
present(actionSheet, animated: true, completion: nil)
}
Good Luck!
Swift 4.1 Solution:-
MAK Eextension FILE UIDEviceExtension.swift and with below code :-
import Foundation
import UIKit
public extension UIDevice {
public class var isPhone: Bool {
return UIDevice.current.userInterfaceIdiom == .phone
}
public class var isPad: Bool {
return UIDevice.current.userInterfaceIdiom == .pad
}
public class var isTV: Bool {
return UIDevice.current.userInterfaceIdiom == .tv
}
public class var isCarPlay: Bool {
return UIDevice.current.userInterfaceIdiom == .carPlay
}
}
Call your action Sheet on UIViewcontroller By this Separate common method :-
import Foundation
import UIKit
class Common: NSObject{
public class func showActionSheet(vc: UIViewController,sender:UIButton? = nil) {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: "Camera", style: .default, handler: { (alert:UIAlertAction!) -> Void in
//self.camera()
}))
actionSheet.addAction(UIAlertAction(title: "Gallery", style: .default, handler: { (alert:UIAlertAction!) -> Void in
//self.photoLibrary()
}))
actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
//if iPhone
if UIDevice.isPhone {
vc.present(actionSheet, animated: true, completion: nil)
}
else {
//In iPad Change Rect to position Popover
if let btn = sender{
actionSheet.popoverPresentationController?.sourceRect = btn.frame
actionSheet.popoverPresentationController?.sourceView = vc.view
}
vc.present(actionSheet, animated: true, completion: nil)
}
}
}
Use it from your UIButton Click for iPhone/iPad both :-
#objc func btnPicImageTaped(btn:UIButton){
print("it will work for both iPhone /ipad")
Common.showActionSheet(vc: self,sender: btn)
}
Add Following two lines before presentViewController. (Swift 3.2)
optionMenu.popoverPresentationController?.sourceView = self.view
optionMenu.popoverPresentationController?.sourceRect = (sender as AnyObject).frame
present(optionMenu, animated: true, completion: nil)
Updated for swift 5
extension UIDevice {
class var isPhone: Bool {
return UIDevice.current.userInterfaceIdiom == .phone
}
class var isPad: Bool {
return UIDevice.current.userInterfaceIdiom == .pad
}
class var isTV: Bool {
return UIDevice.current.userInterfaceIdiom == .tv
}
class var isCarPlay: Bool {
return UIDevice.current.userInterfaceIdiom == .carPlay
}
}
You can take sourceView & sourceRect from sender:
#IBAction func btMenuPressed(_ sender: Any) {
let Sender = sender as? UIButton
let actSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
actSheet.addAction(UIAlertAction(title: "settings", style: .default) { _ in self.doSettings() })
...
...
actSheet.popoverPresentationController?.sourceView = Sender!.superview!
actSheet.popoverPresentationController?.sourceRect = Sender!.frame
resent(actSheet, animated: true)
}
Swift 5+ solution Very smooth and easy just call this function and you will easy solve your problem
let IPAD = UIDevice.current.userInterfaceIdiom == .pad
//Mark:- Choose Action Sheet Methods
func actionSheet() {
var actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: UIAlertController.Style.actionSheet)
actionSheet.view.tintColor = UIColor.black
let button1 = UIAlertAction(title: "Button 1".localizableString(language: Defaults.selectedLanguageCode), style: .default, handler: {
(alert: UIAlertAction!) -> Void in
})
let button2 = UIAlertAction(title: "Button 2".localizableString(language: Defaults.selectedLanguageCode), style: .default, handler: {
(alert: UIAlertAction!) -> Void in
})
let cancelAction = UIAlertAction(title: "Cancel".localizableString(language: Defaults.selectedLanguageCode), style: .cancel, handler: {
(alert: UIAlertAction!) -> Void in
})
if IPAD {
//In iPad Change Rect to position Popover
actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: UIAlertController.Style.alert)
}
actionSheet.addAction(button1)
actionSheet.addAction(button2)
actionSheet.addAction(cancelAction)
print("Action Sheet Open")
self.present(actionSheet, animated: true, completion: nil)
}
Related
I have created a Image Picker UI where I want to do selection when user clicked on the button,
I have done it using UIAlertController and when I test it in iPhone then it works perfect but,
when I test it in iPad then after button Clicked, the app crashed.
How do I make the UIAlertController compatible for iPad also?
Extension for UIViewController
extension UIViewController{
public func addAlertForiPad(alert: UIAlertController) {
if let alertController = alert.popoverPresentationController {
alertController.sourceView = view
alertController.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
alertController.permittedArrowDirections = []
}
}
}
Usages
let alertController = UIAlertController(title: "Title", message: "Message", preferredStyle: .actionSheet)
if UIDevice.current.userInterfaceIdiom == .pad {
addAlertForiPad(alert: alertController)
}
alertController.popoverPresentationController?.sourceView = view
alertController.addAction(UIAlertAction(title: "Approve", style: .default , handler:{ (UIAlertAction)in
print("User click Approve button")
}))
alertController.addAction(UIAlertAction(title: "Dismiss", style: .cancel, handler:{ (UIAlertAction)in
print("User click Dismiss button")
}))
present(alertController, animated: true, completion: nil)
On an iPad a uialertcontroller must be shown relative to some other controller because you can’t just show it “anywhere on the screen”.
We can use
let ac = UIAlertController(title: nil, message: nil, preferredStyle: UIDevice.current.userInterfaceIdiom == .pad ? .alert : .actionSheet)
Full Code in swift
#IBAction func pickBTN(_ sender: UIButton) {
let ac = UIAlertController(title: nil, message: nil, preferredStyle: UIDevice.current.userInterfaceIdiom == .pad ? .alert : .actionSheet)
ac.addAction(UIAlertAction(title: "Gallery", style: .default, handler: { action in
self.gallery()
}))
ac.addAction(UIAlertAction(title: "Camera", style: .default, handler: { action in
self.camera()
}))
ac.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { action in
}))
present(ac, animated: true)
}
Functions that are used in above code (for camera & Gallery)
func gallery (){
imagePicker.allowsEditing = false
imagePicker.sourceType = .savedPhotosAlbum
present(imagePicker, animated: true, completion: nil)
}
func camera (){
imagePicker.allowsEditing = false
imagePicker.sourceType = .camera
present(imagePicker, animated: true, completion: nil)
}
I am trying to implement actionsheet in ipad with the help of popoverPresentationController.everything is working fine but i am facing one problem that Cancel
style button is not coming in the actionsheet.
here is my code for refernece
#IBAction func logoutAction(_ sender: UIButton) {
let alertController = UIAlertController(title: "", message: "do you want to logout", preferredStyle: .actionSheet)
let yesAction = UIAlertAction(title: "Logout", style: .destructive, handler: { (action) -> Void in
Singleton.sharedInstance.logoutButtonAction()
logEvent(label: "user_log_out")
})
let noAction = UIAlertAction(title: "cancel", style: .cancel, handler: { (action) -> Void in
self?.dismiss(animated: true, completion: nil)
})
alertController.addAction(yesAction)
alertController.addAction(noAction)
alertController.popoverPresentationController?.sourceView = self?.view
alertController.popoverPresentationController?.sourceRect = sender.frame
/* if let presenter = alertController.popoverPresentationController {
presenter.sourceView = self!.logoutButton
presenter.sourceRect = self!.logoutButton.bounds
}*/
self?.present(alertController, animated: true, completion: nil)
})
}
```
I am working on project. i have added a simple alertController on clicking signup button. when i click on the button my viewcontroller reloads and then it shows that alertController. It is happening on iOS 13 and swift 5 or above
let alert = UIAlertController(title: "Your title", message: "Your message", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: { action in
})
alert.addAction(ok)
let cancel = UIAlertAction(title: "Cancel", style: .default, handler: { action in
})
alert.addAction(cancel)
DispatchQueue.main.async(execute: {
self.present(alert, animated: true)
})
i resolved my problem by these lines of code
func alert(message: String, title: String = "") {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let alertAction = UIAlertAction(title: "OK", style: .default) { (action) in
print("Action")
}
alertController.addAction(alertAction)
(UIApplication.shared.delegate as! AppDelegate).alertWindow.isHidden = true
self.presentViewController(alertController: alertController)
// self.present(alertController, animated: true, completion: nil)
}
func presentViewController(alertController: UIAlertController, completion: (() -> Void)? = nil) {
if var topController = UIApplication.shared.keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
DispatchQueue.main.async {
topController.present(alertController, animated: true, completion: completion)
}
}
}
I have two buttons, one that shows the segmented control and one that tries to hide it. The problem is when I click the one to show it, it works. However, when I click the one to hide it, it doesn't work. Here is my code:
let delayHide = UIAlertAction(title: "Hide Delay", style: .default) { (action) in
self.segmentedHidden = 1
self.setupSegmented()
}
let delayShow = UIAlertAction(title: "Show Delay", style: .default) { (action) in
self.segmentedHidden = 0
self.setupSegmented()
}
Here is also the code for when I try to hide it:
if (segmentedHidden == 0) {
segmentedControl.isHidden = false
} else {
segmentedControl.isHidden = true
}
Where did I go wrong?
Use following code:
#IBAction func buttonTapped(_ sender: UIButton) {
let alert = UIAlertController(title: "Alert", message: "Segment", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Show", style: .default, handler: { (alertAction) in
self.showHideSegmentControl(isHidden: false)
}))
alert.addAction(UIAlertAction(title: "hide", style: .default, handler: { (alertAction) in
self.showHideSegmentControl(isHidden: true)
}))
self.present(alert, animated: true, completion: nil)
}
func showHideSegmentControl(isHidden: Bool) {
segmentedControl.isHidden = isHidden
}
I created a Utilities class to hold some common functions, one of which is an alertUser function that if called, will display an Alert box to the user with the provided title and message text. In another class file, I am validating some text field entries and if the validation doesn't pass, then I want to use the alertUser function from the Utilities class. However, when I do this, I get the following error message in the Xcode log:
Warning: Attempt to present <UIAlertController: 0x7f9c4be0b140> on <MyAppName.Utilities: 0x7f9c4be1cb60> whose view is not in the window hierarchy!
The calling code is in a UIViewController class file. Here's the code which is in the
class ItemSettingsVC: UIViewController:
private func validateNameField() -> Bool {
var passed = false
if (nameField.hasText) {
passed = true
} else {
Utilities().alertUser(strTitle: "Alert", strMessage: strInvalidNameFieldErrorMsg)
passed = false
}
return passed
}
Here's the alertUser function which is in the
class Utilities: UIViewController:
public func alertUser(strTitle: String, strMessage: String) {
let myAlert = UIAlertController(title: strTitle, message: strMessage, preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil)
myAlert.addAction(okAction)
self.present(myAlert, animated: true, completion: nil)
}
This is running on iOS. I'm using Xcode 8 and swift 3. Any help is greatly appreciated. Thanks.
This should do it:
public func alertUser(strTitle: String, strMessage: String) {
let myAlert = UIAlertController(title: strTitle, message: strMessage, preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil)
myAlert.addAction(okAction)
UIApplication.shared.delegate?.window??.rootViewController?.present(myAlert, animated: true, completion: nil)
}
You have to add an additional parameter in your alertUser function, which would be the VC that will present the alert controller.
for example:
public func alertUser(strTitle: String, strMessage: String, viewController: UIViewController) {
let myAlert = UIAlertController(title: strTitle, message: strMessage, preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil)
myAlert.addAction(okAction)
viewController.present(myAlert, animated: true, completion: nil)
}
But I would recommend that you just make an extension of UIViewController and add your func alertUser()* there because you would surely use this alertUser in different VCs and complexity wise in my opinion, this would be more optimized.
Like this:
extension UIViewController {
func showAlert(title: String, message: String, callback: #escaping () -> ()) {
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: {
alertAction in
callback()
}))
self.present(alert, animated: true, completion: nil)
}
//add additional functions here if necessary
//like a function showing alert with cancel
}
NOTE : Please don't make your Utilities class a subclass of UIViewController, it would also be better to make it a struct handling static functions and/or variables
Use this class for easy to show Alert or ActionSheet
UIAlertController Extension
public extension UIAlertController {
public func showAlert(animated: Bool = true, completionHandler: (() -> Void)? = nil) {
guard let rootVC = UIApplication.shared.keyWindow?.rootViewController else {
return
}
var forefrontVC = rootVC
while let presentedVC = forefrontVC.presentedViewController {
forefrontVC = presentedVC
}
forefrontVC.present(self, animated: animated, completion: completionHandler)
}
}
AppAlert Class Create For UIAlertController Show
public class AppAlert {
private var alertController: UIAlertController
public init(title: String? = nil, message: String? = nil, preferredStyle: UIAlertControllerStyle) {
self.alertController = UIAlertController(title: title, message: message, preferredStyle: preferredStyle)
}
public func setTitle(_ title: String) -> Self {
alertController.title = title
return self
}
public func setMessage(_ message: String) -> Self {
alertController.message = message
return self
}
public func setPopoverPresentationProperties(sourceView: UIView? = nil, sourceRect:CGRect? = nil, barButtonItem: UIBarButtonItem? = nil, permittedArrowDirections: UIPopoverArrowDirection? = nil) -> Self {
if let poc = alertController.popoverPresentationController {
if let view = sourceView {
poc.sourceView = view
}
if let rect = sourceRect {
poc.sourceRect = rect
}
if let item = barButtonItem {
poc.barButtonItem = item
}
if let directions = permittedArrowDirections {
poc.permittedArrowDirections = directions
}
}
return self
}
public func addAction(title: String = "", style: UIAlertActionStyle = .default, handler: #escaping ((UIAlertAction!) -> Void) = { _ in }) -> Self {
alertController.addAction(UIAlertAction(title: title, style: style, handler: handler))
return self
}
public func addTextFieldHandler(_ handler: #escaping ((UITextField!) -> Void) = { _ in }) -> Self {
alertController.addTextField(configurationHandler: handler)
return self
}
public func build() -> UIAlertController {
return alertController
}
}
Used For Open AlertBox
AppAlert(title: "Question", message: "Are you sure?", preferredStyle: .alert)
.addAction(title: "NO", style: .cancel) { _ in
// action
}
.addAction(title: "Okay", style: .default) { _ in
// action
}
.build()
.showAlert(animated: true)
Used For ActionSheet Open
if UIDevice.current.userInterfaceIdiom != .pad {
// Sample to show on iPhone
AppAlert(title: "Question", message: "Are you sure?", preferredStyle: .actionSheet)
.addAction(title: "NO", style: .cancel) {_ in
print("No")
}
.addAction(title: "YES", style: .default) { _ in
print("Yes")
}
.build()
.showAlert(animated: true)
} else {
// Sample to show on iPad
AppAlert(title: "Question", message: "Are you sure?", preferredStyle: .actionSheet)
.addAction(title: "Not Sure", style: .default) {
_ in
print("No")
}
.addAction(title: "YES", style: .default) { _ in
print("Yes")
}
.setPopoverPresentationProperties(sourceView: self, sourceRect: CGRect.init(x: 0, y: 0, width: 100, height: 100), barButtonItem: nil, permittedArrowDirections: .any)
.build()
.showAlert(animated: true)
}
First find out the topmost viewController on your window .
Get the top ViewController in iOS Swift
and then present your alert on that viewController.No need to pass any parameter.
public func alertUser(strTitle: String, strMessage: String) {
let myAlert = UIAlertController(title: strTitle, message: strMessage, preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil)
myAlert.addAction(okAction)
topmostVC().present(myAlert, animated: true, completion: nil)
}