How to make actionsheet pop over a bar button on iPhone - swift

I know how to make an action sheet pop over a button on iPad but not for iPhone. it seems that on iphone the action sheet doesn't have popoverPresentationController:
#IBAction func addChannel(_ sender: Any) {
let sender = sender as? UIBarButtonItem
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let addChanel = UIAlertAction(title: "Add a Channel", style: .default) { (_) in
self.addChannel()
}
let addContact = UIAlertAction(title: "Add a Contact", style: .default){ _ in
self.addContact()
}
actionSheet.addAction(addChanel)
actionSheet.addAction(addContact)
actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel))
// actionSheet.popoverPresentationControlle
if let popover = actionSheet.popoverPresentationController{
actionSheet.popoverPresentationController?.barButtonItem = sender
actionSheet.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.down
}
self.present(actionSheet, animated: true)
}
I want the action sheet pop over a button just like what WeChat + button does. Other stackoverflow answers are too old and not workable
Any idea is welcome!

Source.
You cannot use UIAlertController as a popover on iPhones. One of alternatives is using a UITableView or something inside new UIViewController, which you can create like described below (Swift 5.0).
import UIKit
class ViewController: UIViewController, UIPopoverPresentationControllerDelegate {
#IBOutlet private weak var button: UIButton!
#IBAction func buttonAction() {
let vc = UIViewController()
vc.view.backgroundColor = UIColor.blue
vc.preferredContentSize = CGSize(width: 200, height: 200)
vc.modalPresentationStyle = .popover
let ppc = vc.popoverPresentationController
ppc?.permittedArrowDirections = .any
ppc?.delegate = self
ppc?.sourceView = button
present(vc, animated: true, completion: nil)
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
Also, popoverPresentationController has property called barButtonItem which you can assign to your nav bar button.

Related

Alert not showing on the modal that is presenting

On a view Controller when I click on the guard button -->
#IBAction func guardTapped(_ sender: Any) {
let vc = guardViewController.instanceFromNib()
vc.modalPresentationStyle = .overCurrentContext
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.present(vc, animated: true, completion: nil)
}
}
So In this guardViewController which is a presenting modal, I want to show an alert message when I click on the close button on guardViewController.
#IBAction func closeTapped(_ sender: Any) {
let alert = UIAlertController(title: "Alert", message: "Are You Sure", preferredStyle: .alert)
let proceedAction = UIAlertAction(title: "Proceed", style: .destructive) { (action) in
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.dismiss(animated: true)
}
}
alert.addAction(proceedAction)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (action) in
//
}
alert.addAction(cancelAction)
if let popoverController = alert.popoverPresentationController {
popoverController.sourceView = sender as? UIView
popoverController.sourceRect = CGRect(x: (sender as AnyObject).bounds.midX, y: (sender as AnyObject).bounds.midY, width: 0, height: 0)
popoverController.permittedArrowDirections = [.any]
}
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.present(alert, animated: true, completion: nil)
}
}
Try presenting simple Alert with this Alert helper:
struct Alert {
static func showErrorAlertWithOk(on vc: UIViewController, withTitle: String, andMessage: String) {
let alert = UIAlertController(title: withTitle, message: andMessage, preferredStyle: .alert)
let action = UIAlertAction(title: "OK", style: .cancel) {_ in }
alert.addAction(action)
vc.present(alert, animated: true)
}
}
Then use is as folows:
Alert.showErrorAlertWithOk(on: yourViewController, withTitle: "SampleAlert", andMessage: "YourMessage")

UINavigation controller: present and dismiss programmatically

I have a TableViewController which I want to present modally and I need it to have a NavigationBar.
To get that navbar, I have an embedded UINavigationController and as far as I know, that UINavigationController is what I have to present modally, so that's what I've done.
Everything works just fine, but I can't manage to dismiss that controller properly. Here is what I've got so far:
func presentErrorMessages(errorMessages: [String]) {
let storyBoard: UIStoryboard = UIStoryboard(name: "Message", bundle: nil)
let infoMessagesNavigationViewController = storyBoard.instantiateViewController(withIdentifier: "InfoMessagesNavigation") as! ModalNavigationController
let infoMessagesTableViewController = infoMessagesNavigationViewController.viewControllers[0] as! InfoMessagesTableViewController
infoMessagesTableViewController.errorMessages = errorMessages
self.navigationController?.present(infoMessagesNavigationViewController, animated: true)
}
I use that to present ModalNavigationController, and this to dismiss it:
class ModalNavigationController: BaseNavigationController {
var backNavItem = UINavigationItem()
var okNavItem = UINavigationItem()
override func viewDidLoad() {
super.viewDidLoad()
let backButton = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(dismissModal))
backNavItem.leftBarButtonItem = backButton
...
var items = [UINavigationItem]()
items.append(backNavItem)
self.navigationBar.items = items
}
#objc func dismissModal() {
self.dismiss(animated: true)
}
}
When I press that back button, there is no change but the navbar which gets blank (with no title). I have the feeling that the application just 'forgets' what is the NavigationController used before the new one is presented.
How can I solve this?
Try something like this:
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Back", style: .done, target: self, action: #selector(dismissModal))
...
}
#objc func dismissModal() {
self.dismiss(animated: true, completion: nil)
}
I managed to solve the problem by placing and invoking the dismissfunction on my TableViewController rather than my NavigationController:
...
public func setBackButton(){
if self.navigationController != nil {
let item = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(dismissModal))
self.navigationItem.leftBarButtonItem = item
}
}
#objc func dismissModal() {
self.dismiss(animated: true)
}

Crash after cancel on message form

I want to send an message through my app - everything works like it should, except, if the user aborts the Screen with the "Cancel" in the upper right corner, the App will get black and crashes.
I think it has something to do with the Views, so that the App View won't come back, but I don't know how and why. I've made breakpoints, but the app crashes before these.
Here is the whole code of this:
import UIKit
import MessageUI
class LoanTableCell: UITableViewCell, UIAlertViewDelegate, UINavigationControllerDelegate, MFMailComposeViewControllerDelegate, MFMessageComposeViewControllerDelegate {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
var imageData: Data?
var userEmail: String?
var userNumber: String?
var popUp: UIView?
// Label Outlets
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var amountLabel: UILabel!
#IBOutlet weak var currencyLabel: UILabel!
#IBOutlet weak var dateLabel: UILabel!
#IBOutlet weak var noteLabel: UILabel!
#IBOutlet weak var dueLabel: UILabel!
#IBOutlet weak var moreInfos: UIView!
#IBOutlet weak var showImageButton: UIButton!
#IBOutlet weak var remindButton: UIButton!
#IBAction func remindUser(_ sender: Any) {
let alert: UIAlertController = UIAlertController(title: "remind by:", message: nil, preferredStyle: .actionSheet)
let view = storyboard.instantiateViewController(withIdentifier: "LoanView") as! LoanViewController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let remindByEmail = UIAlertAction(title: "E-Mail", style: UIAlertActionStyle.default) {
UIAlertAction in
self.sendEmail()
} // Email senden....
let remindByNumber = UIAlertAction(title: "Phone Message", style: UIAlertActionStyle.default) {
UIAlertAction in
self.sendSMS()
} // SMS senden...
let cancelRemind = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel) {
UIAlertAction in
print("cancel")
} // Abbrechen
//Aktionen ausführen
if(userNumber != ""){
alert.addAction(remindByNumber)
}
if(userEmail != ""){
alert.addAction(remindByEmail)
}
alert.addAction(cancelRemind)
//Controller anzeigen
appDelegate.window?.rootViewController = view
view.present(alert, animated: true, completion: nil)
}
#IBAction func showImage(_ sender: Any) {
if (imageData?.isEmpty == false) {
popUp = UIView(frame: CGRect(x: 0, y: 20, width: (window?.frame.width)!, height: (window?.frame.height)!))
popUp?.backgroundColor = UIColor.black
window?.addSubview(popUp!)
popUp?.isHidden = false
let imageView = UIImageView(frame: CGRect(x: 15, y: 15, width: ((window?.frame.maxX)! - 30), height: ((window?.frame.maxY)! - 50)))
imageView.image = UIImage(data: imageData!)
imageView.contentMode = .scaleAspectFit
popUp?.addSubview(imageView)
let closeButton = UIButton(frame: CGRect(x: ((window?.frame.maxX)! - 40), y: 5, width: 35, height: 35))
closeButton.setImage( imageLiteral(resourceName: "closeImage"), for: .normal)
closeButton.addTarget(self, action: #selector(closeImageView), for: .touchUpInside)
popUp?.addSubview(closeButton)
}
}
#objc func closeImageView() {
popUp?.isHidden = true
}
func sendEmail() {
let view = storyboard.instantiateViewController(withIdentifier: "LoanView") as! LoanViewController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
if MFMailComposeViewController.canSendMail() {
let reminderMessage: String = „Some Text„
let mail = MFMailComposeViewController()
mail.mailComposeDelegate = self
mail.setToRecipients([userEmail!])
mail.setSubject("Urgent Loan-Reminder")
mail.setMessageBody(reminderMessage, isHTML: true)
appDelegate.window?.rootViewController = view
view.present(mail, animated: true, completion: nil)
} else {
let alert = UIAlertController(title: "No E-Mail Account found...", message: "to send E-Mails, you have to configure at least one E-Mail account on your Device.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil))
appDelegate.window?.rootViewController = view
view.present(alert, animated: true, completion: nil)
}
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
print("mail sent")
}
func sendSMS() {
let view = storyboard.instantiateViewController(withIdentifier: "LoanView") as! LoanViewController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
if MFMessageComposeViewController.canSendText() {
let reminderMessage: String = „Some Text„
let message = MFMessageComposeViewController()
message.messageComposeDelegate = self
message.recipients = [userNumber!]
message.body = reminderMessage
appDelegate.window?.rootViewController = view
view.present(message, animated: true, completion: nil)
} else {
let alert = UIAlertController(title: "No Message Account...", message: "to send Messages, you need a Phone account.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil))
appDelegate.window?.rootViewController = view
view.present(alert, animated: true, completion: nil)
}
}
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
controller.dismiss(animated: true, completion: nil)
print("message sent")
}
}
If I hit the cancel button in the upper right, I will get this error in the AppDelegate file:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x5a1b7831c08)
Seems like I've forgotten something.
A UITableViewCell should not be responsible for such actions, the cell should send a notification or it should be connected with a delegate method. Then the UIViewController should perform these actions.
protocol RemindUserDelegate: class {
func buttonPressed()
}
class LoanTableCell: UITableViewCell {
weak var delegate: RemindUserDelegate?
}
class ViewController: UIViewController, RemindUserDelegate,MFMailComposeViewControllerDelegate, MFMessageComposeVie wControllerDelegate {
func buttonPressed() {
}
}
and at cellForRow add:
cell.delegate = self
and on the cell perform : self.delegate?.buttonPressed()
then the UIViewController can take it from there. You can also pass info with userData.

NavigationController doesn't see right bar button item

I'm trying to understand presentation view controllers by allowing the user to add new outcomes of a fortune telling app. When the user taps on "Add New Outcome" on the top right of the navigation bar a new view controller pops up and this is where the user can enter a new outcome. I'm trying to add a done button to the top right of the presentation controller but I'm getting an error saying Value of type 'UINavigationController?' has no member 'rightBarButtonItem'.
I thought it was because of referencing the topViewController was the problem but I'm doing this all in 1 ViewController.
#IBAction func actionButtonTapped(_ sender: UIBarButtonItem) {
let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "AddNewOptionController")
viewController.modalPresentationStyle = .popover
let popover : UIPopoverPresentationController = viewController.popoverPresentationController!
popover.barButtonItem = sender
popover.delegate = self
present(viewController, animated: true, completion: nil)
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .fullScreen
}
func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
let navigationController = UINavigationController(rootViewController: controller.presentedViewController)
let doneButton = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(ViewController.dismissViewController))
//error below
navigationController.topViewController?.navigationController.rightBarButtonItem = doneButton
return navigationController
}
func dismissViewController() {
self.dismiss(animated: true, completion: nil)
}
Instead of:
navigationController.topViewController?.navigationController.rightBarButtonItem = doneButton
Try:
navigationController.topViewController?.navigationItem.rightBarButtonItem = doneButton
you can use this code:
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named:"YOUR_IMAGE_NAME"), style: .plain, target: self, action: #selector(YOUR_FUNCTION))
hope this work for you

UIPopoverPresentationController popover displays over the entire view

I am trying to create a rightBarButtonItem that appears throughout my app. When this barItem is clicked I want to show a modal popup using UIPopoverPresentationController. I have been able to get the button to show up on the barItem on all the views. However when i click on the button the xib takes over the entire view (including nav bar, see image below). Please see the class below:
class MyAppsNavigationController: UINavigationController, UINavigationControllerDelegate, UIPopoverPresentationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
self.navigationBar.barTintColor = Colors.Red01.color()
self.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName : UIColor.white]
}
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
viewController.navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "Ellipsis"), style: .plain, target: self, action: #selector(displayMenu(sender:)))
}
func displayMenu(sender: UIBarButtonItem)
{
let filterVC = DropdownMenuController(nibName: "DropdownMenuController", bundle: nil)
let nav = UINavigationController(rootViewController: filterVC)
nav.modalPresentationStyle = UIModalPresentationStyle.popover
//nav.isNavigationBarHidden = true
nav.preferredContentSize = CGSize(width: 200, height: 300)
let popover = nav.popoverPresentationController! as UIPopoverPresentationController
popover.permittedArrowDirections = .up
popover.delegate = self
popover.barButtonItem = self.navigationItem.rightBarButtonItem
popover.sourceView = self.view;
var frame:CGRect = (sender.value(forKey: "view")! as AnyObject).frame
frame.origin.y = frame.origin.y+20
popover.sourceRect = frame
popover.delegate = self
self.present(nav, animated: true, completion: nil)
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
Result when clicked on the button:
When clicked the popup takes over entire view:
Any chance you're not using the right delegate method? I think this looks better:
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
Also, in this case sourceView and sourceRect is not needed: specifying a barButtonItem for the popover presentation controller is sufficient.
https://developer.apple.com/documentation/uikit/uipopoverpresentationcontroller/1622314-barbuttonitem