How can I create a popup menu in iOS? - ios

How can I create a popup menu like the one present in WhatsApp?
Sorry for the dumb question, but I don't even know what to search. I'm pretty sure it's not a UIPickerView.

This is an action sheet. Here's the documentation about it in the iOS Human Interface Guidelines.
You can make one like this:
SwiftUI (iOS 15 and above)
Use confirmationDialog(). Here is the official documentation for it and here are some real-world examples, which are partially the source of the example code.
#State private var shouldShowActionSheet = false
<custom view>
.confirmationDialog("", isPresented: $shouldShowActionSheet) {
Button("Option 1") {
<handler>
}
Button("Option 2") {
<handler>
}
Button("Cancel", role: .cancel) { }
}
SwiftUI (iOS 13 and 14)
#State private var shouldShowActionSheet = false
[...]
<custom view>
.actionSheet(isPresented: $shouldShowActionSheet) {
ActionSheet(
title: Text(""),
buttons: [
.default(Text("Option 1")) {
<handler>
},
.default(Text("Option 2")) {
<handler>
},
.cancel()
]
)
}
UIKit
let alert = UIAlertController(
title: nil,
message: nil,
preferredStyle: .actionSheet
)
alert.addAction(
.init(title: "Action 1", style: .default) { _ in
<handler>
}
)
alert.addAction(
.init(title: "Action 1", style: .default) { _ in
<handler>
}
)
present(alert, animated: true)

Its UIAlertController with preferredStyle - UIAlertControllerStyle.actionSheet
https://developer.apple.com/documentation/uikit/uialertcontroller

Related

Dismissal of UIAlertController inside async function

i am struggeling with a issue as following:
i start some network processing inside a async function:
func upload(
object: object,
context: NSManagedObjectContext
) async -> Dictionary<String,String> {
}
therefor to inform the user that a time consuming process has just been started i display a alert controller:
let alert = UIAlertController(
title: "Loading",
message: "Please wait...",
preferredStyle: .alert
)
self.present(alert, animated: true, completion: nil)
at the end of that function if everything worked out as planed i do dismiss that alert controller successfully, and show another alert:
alert.dismiss(
animated: true,
completion: {
let messageAlert = UIAlertController(
title: "Success",
message: "Upload complete",
preferredStyle: .alert
)
messageAlert.addAction(
UIAlertAction(
title: "OK",
style: .default,
handler: { (action: UIAlertAction) in
//
}
)
)
self.present(
messageAlert, animated: true, completion: nil
)
}
)
but i also want to dismiss that alert and show a new one when an error occured, but the first alert controller never gets dismissed:
guard dataString.replacingOccurrences(
of: "\n",
with: ""
) != "no valid userID" else {
error = true
alert.dismiss(
animated: true,
completion: {
let messageAlert = UIAlertController(
title: "Warning",
message: "no valid userID received",
preferredStyle: .alert
)
messageAlert.addAction(
UIAlertAction(
title: "OK",
style: .default,
handler: { (action: UIAlertAction) in
//
}
)
)
self.present(
messageAlert,
animated: true,
completion: nil
)
}
)
returnDic = [
"result": "error",
"info": "no valid userID received"
]
return returnDic
}
i just can't wrap my head around why i am able to close it at the end of that function but not somewhere down the road..
Xcode is also complaining about not being able to present a new alert if one is actually being displayed right now..
[Presentation] Attempt to present <UIAlertController: 0x15880a200> on
<Project.ContainerViewController: 0x153708290> (
from <Project.LockerViewController: 0x155018200>)
while a presentation is in progress.
i already tried to work with an observer like:
var error = Bool() {
didSet {
alert.dismiss(animated: true)
}
}
it gets called, but the view does not get dismissed again :/
any ideas?
i've found a solution:
The problem was that the presenting of that alert controller has not finished yet, and so it could not be dismissed at that time.
a fix for this is f.e.:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
alert.dismiss(animated: true)
}
i've realized this with attaching a closure block to
self.present(alert,animated: true) { print("alert controller is visible") }
and at the time of dimissal that text has not been printed out yet ;)
i hope this is usefull for somebody else as well ;)

how to pass function in parameter using closure in swift 5 [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
I am new in swift. I have made generic actionsheet
import Foundation
extension UIAlertController{
func action(mes:String,tit:String,tit2:String,operation1:(),operation2:()) {
let actionSheet = UIAlertController(title: "", message:mes, preferredStyle: .actionSheet)
let EButton = UIAlertAction(title:tit ,
style: .default,
handler: { _ in
operation1
})
let AllEButton = UIAlertAction(title:tit2,
style: .default ,
handler:{ _ in
operation2
})
let cancelAction = UIAlertAction(title: "Cancel",
style: .cancel,
handler: nil)
[EButton, AllEButton, cancelAction].forEach { $0.setValue(UIColor.red, forKey: "titleTextColor")
}
actionSheet.addAction(EButton)
actionSheet.addAction(AllEButton)
actionSheet.addAction(cancelAction)
present(actionSheet, animated: true, completion: nil)
}
}
I want to want to call this extension from viewControllerA
let actionView = UIAlertController()
class viewControllerA: UIViewController {}
private extension viewControllerA {
func alertBottomSheat() {
actionView.action(mes: "Update",tit: "Update only",tit2: "Update All", operation1:saveEvent(),operation2:saveEvent())
}
#IBAction func deleteEventButtonClicked(_ sender: Any) {
actionView.action(mes: "delete ",tit: "Delete only",tit2: "Delete All ",operation1:deleteEvent(),operation2:deleteEvent(deleteAll: true))
}
}
Q1- I this right way to call the extension from viewControllerA extension?
Q2- please tell me how to pass function in action function parameter using closure in this line?
actionView.action(mes: "delete ",tit: "Delete only",tit2: "Delete All ",operation1:deleteEvent(),operation2:deleteEvent(deleteAll: true))
and how to use closure in handler of action sheet in this line
let EButton = UIAlertAction(title:tit ,
style: .default,
handler: { _ in
operation1
})
You first need to create an extension for UIViewController instead of UIAlertController
Also, set the correct closure argument for the function and then call the function like this.
extension UIViewController {
func action(message: String, firstTitle: String, secondTitle: String, firstAction: (() -> Void)? = nil, secondAction: (() -> Void)? = nil) {
let actionSheet = UIAlertController(title: "", message: message, preferredStyle: .actionSheet)
let eButton = UIAlertAction(title: firstTitle ,
style: .default,
handler: {_ in firstAction?()})
let allEButton = UIAlertAction(title: secondTitle,
style: .default ,
handler: {_ in secondAction?()})
let cancelAction = UIAlertAction(title: "Cancel",
style: .cancel,
handler: nil)
[eButton, allEButton, cancelAction].forEach { $0.setValue(UIColor.red, forKey: "titleTextColor")}
actionSheet.addAction(eButton)
actionSheet.addAction(allEButton)
actionSheet.addAction(cancelAction)
present(actionSheet, animated: true, completion: nil)
}
}
Usage
private extension viewControllerA {
func alertBottomSheat() {
self.action(message: "Update", firstTitle: "Update only", secondTitle: "Update All", firstAction: saveEvent, secondAction: saveEvent)
}
#IBAction func deleteEventButtonClicked(_ sender: Any) {
self.action(message: "delete ", firstTitle: "Delete only", secondTitle: "Delete All ", firstAction: { self.deleteEvent()}, secondAction: { self.deleteEvent(deleteAll: true) })
}
func saveEvent() {
}
func deleteEvent(deleteAll: Bool = false) {
}
}
Note: Fixed coding standard rule and var names.

checkmark on a submenu option within UIMenu in Swift

I have the following code that generates a context menu when a user taps on a uibarbutton in a navigation bar:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGreen
title = "Hello"
let scribbleAction = UIAction(title: "Scribble", image: UIImage(systemName: "scribble"), state: .on, handler: { (_) in })
let textAction = UIAction(title: "Typing", image: UIImage(systemName: "textformat"), state: .off, handler: { (_) in })
let clockAction = UIAction(title: "Show Clock", image: UIImage(systemName: "clock"), state: .off, handler: { (_) in })
let calendarAction = UIAction(title: "Date Last Used", image: UIImage(systemName: "calendar"), state: .off, handler: { (_) in })
var showAsActions: [UIAction] {
return [
clockAction, calendarAction
]
}
var showAsMenu : UIMenu {
return UIMenu(title: "Show As", image: UIImage(systemName: "questionmark.circle.fill"), identifier: .none, children: showAsActions)
}
let menu = UIMenu(title: "",
image: nil,
identifier: nil,
options: [],
children: [showAsMenu, scribbleAction, textAction])
let barButtonRight = UIBarButtonItem(title: "Sort", image: UIImage(systemName: "ellipsis.circle.fill"), primaryAction: nil, menu: menu)
navigationItem.rightBarButtonItems = [barButtonRight]
}
}
This all works and produces the following when a user taps on the button in the navigation bar:
Tapping on the first menu item yields the desired effect:
But at times, I need to be able to also mark the first menu item (which is a submenu item) with a checkmark. I can't work out how to do this and have searched on Stackoverflow as well as the Apple Developer Forums etc and can't work it out. Any suggestions would be greatly appreciated.

How to create an alert with DatePicker in SwiftUI

I want to display DatePicker in alert view or action sheet view , but I could not find any resources to do it.
I want the following view.
Thanks for the help
What you want is actually discouraged by Apple (according to this answer). This is probably why you can't find any examples yourself.
Here is a possible solution:
struct ContentView: View {
#State var selectedDate = Date()
static let formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.setLocalizedDateFormatFromTemplate("yyMMddhhmm")
return formatter
}()
var body: some View {
VStack {
Text("Selected date: \(selectedDate, formatter: Self.formatter)")
Button("Show action sheet") {
self.showDatePickerAlert()
}
}
}
func showDatePickerAlert() {
let alertVC = UIAlertController(title: "\n\n\n\n\n\n\n\n\n", message: nil, preferredStyle: .actionSheet)
let datePicker: UIDatePicker = UIDatePicker()
alertVC.view.addSubview(datePicker)
let okAction = UIAlertAction(title: "OK", style: .default) { _ in
self.selectedDate = datePicker.date
}
alertVC.addAction(okAction)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
alertVC.addAction(cancelAction)
if let viewController = UIApplication.shared.windows.first?.rootViewController {
viewController.present(alertVC, animated: true, completion: nil)
}
}
}
This uses the "\n\n\n\n\n\n\n\n\n" hack from this answer.

How to present alert to User?

I would like to ask you how can I show alert to user.
I just tried:
.navigationBarItems(trailing: Button(action: {
let alert = Alert(title: Text("Add category"), message: Text("Do you want add category?"), primaryButton: Alert.Button.default(Text("Yes"), onTrigger: {
self.sceneries[0].sceneries.append(Scenery(name: "Name", imageName: "1"))
}), secondaryButton: Alert.Button.cancel())
self.presentation(self.$isShownAlert) { () -> Alert in
return alert
}
}, label: {
Text("Add category")
}))
But it shows me that it's unused and alert didn't appear...
You need to call presentation API on top of the view that should display the alert.
The best way to accomplish this is to have a #State variable, that tells SwiftUI whether the alert should be displayed or not.
The Button action would then set it to true, thus invalidating body, and triggering a view rebuilding.
struct ContentView : View {
#State var showAlert = false
var body: some View {
NavigationView {
List(0...10) { value in
Text(verbatim: "\(value)")
}
.navigationBarItems(leading: EmptyView(), trailing: Button(action: {
self.showAlert = true
}) {
Text(verbatim: "Show alert")
})
.navigationBarTitle(Text(verbatim: "A List"))
}
.presentation($showAlert) {
return Alert(title: Text(verbatim: "An Alert"))
}
}
}
In this example, the button sets the #State to true,
and presentation is called on the navigation view.
Result:
To display an alert with two buttons you can do like below:
#State var showAlert = false
let alert = Alert(title: Text("Title"), message: Text("Alert message"),
primaryButton: Alert.Button.default(Text("OK"),
onTrigger: {
print("OK button tapped")
}
),
secondaryButton: Alert.Button.cancel()
)
var body: some View {
NavigationView {
Text("Content")
.navigationBarItems(trailing: Button(action: {
self.showAlert = true
}, label: {
Text("Show Alert")
}).presentation(self.$showAlert, alert: {
return alert
})
)
}
}
Result:
Answers above are already deprecated. Use this instead:
#State var showsAlert = false
var body: some View {
NavigationView {
List {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
.navigationBarTitle("My List", displayMode: .inline)
.navigationBarItems(trailing:
Button(action: {
self.showsAlert = true
}, label: {
Text("Show Alert")
}).alert(isPresented: self.$showsAlert) {
Alert(title: Text("Hello World"))
}
)
}
}
Note the use of .alert instead of .presentation.

Resources