Quelqu'un sait-il comment créer une alerte dans SwiftUI qui contient un champ de texte ?
Réponses
Trop de publicités?Comme cela a déjà été mentionné Alert
ne fournit pas beaucoup de fonctionnalités et est donc presque inutile dans tous les cas non standard lorsqu'il est utilisé dans SwiftUI.
J'ai fini par trouver une solution un peu extensive - View
qui peut se comporter comme une alerte avec un niveau de personnalisation élevé.
-
Créer
ViewModel
pour le popUp :struct UniAlertViewModel { let backgroundColor: Color = Color.gray.opacity(0.4) let contentBackgroundColor: Color = Color.white.opacity(0.8) let contentPadding: CGFloat = 16 let contentCornerRadius: CGFloat = 12 }
-
nous devons également configurer les boutons, à cette fin, ajoutons un type supplémentaire :
struct UniAlertButton { enum Variant { case destructive case regular } let content: AnyView let action: () -> Void let type: Variant var isDestructive: Bool { type == .destructive } static func destructive<Content: View>( @ViewBuilder content: @escaping () -> Content ) -> UniAlertButton { UniAlertButton( content: content, action: { /* close */ }, type: .destructive) } static func regular<Content: View>( @ViewBuilder content: @escaping () -> Content, action: @escaping () -> Void ) -> UniAlertButton { UniAlertButton( content: content, action: action, type: .regular) } private init<Content: View>( @ViewBuilder content: @escaping () -> Content, action: @escaping () -> Void, type: Variant ) { self.content = AnyView(content()) self.type = type self.action = action } }
-
ajouter une vue qui peut devenir notre popUp personnalisable :
struct UniAlert<Presenter, Content>: View where Presenter: View, Content: View { @Binding private (set) var isShowing: Bool let displayContent: Content let buttons: [UniAlertButton] let presentationView: Presenter let viewModel: UniAlertViewModel private var requireHorizontalPositioning: Bool { let maxButtonPositionedHorizontally = 2 return buttons.count > maxButtonPositionedHorizontally } var body: some View { GeometryReader { geometry in ZStack { backgroundColor() VStack { Spacer() ZStack { presentationView.disabled(isShowing) let expectedWidth = geometry.size.width * 0.7 VStack { displayContent buttonsPad(expectedWidth) } .padding(viewModel.contentPadding) .background(viewModel.contentBackgroundColor) .cornerRadius(viewModel.contentCornerRadius) .shadow(radius: 1) .opacity(self.isShowing ? 1 : 0) .frame( minWidth: expectedWidth, maxWidth: expectedWidth ) } Spacer() } } } } private func backgroundColor() -> some View { viewModel.backgroundColor .edgesIgnoringSafeArea(.all) .opacity(self.isShowing ? 1 : 0) } private func buttonsPad(_ expectedWidth: CGFloat) -> some View { VStack { if requireHorizontalPositioning { verticalButtonPad() } else { Divider().padding([.leading, .trailing], -viewModel.contentPadding) horizontalButtonsPadFor(expectedWidth) } } } private func verticalButtonPad() -> some View { VStack { ForEach(0..<buttons.count) { Divider().padding([.leading, .trailing], -viewModel.contentPadding) let current = buttons[$0] Button(action: { if !current.isDestructive { current.action() } withAnimation { self.isShowing.toggle() } }, label: { current.content.frame(height: 35) }) } } } private func horizontalButtonsPadFor(_ expectedWidth: CGFloat) -> some View { HStack { let sidesOffset = viewModel.contentPadding * 2 let maxHorizontalWidth = requireHorizontalPositioning ? expectedWidth - sidesOffset : expectedWidth / 2 - sidesOffset Spacer() if !requireHorizontalPositioning { ForEach(0..<buttons.count) { if $0 != 0 { Divider().frame(height: 44) } let current = buttons[$0] Button(action: { if !current.isDestructive { current.action() } withAnimation { self.isShowing.toggle() } }, label: { current.content }) .frame(maxWidth: maxHorizontalWidth, minHeight: 44) } } Spacer() } } }
-
pour simplifier l'utilisation, ajoutons l'extension à
View
:extension View { func assemblyAlert<Content>( isShowing: Binding<Bool>, viewModel: UniAlertViewModel, @ViewBuilder content: @escaping () -> Content, actions: [UniAlertButton] ) -> some View where Content: View { UniAlert( isShowing: isShowing, displayContent: content(), buttons: actions, presentationView: self, viewModel: viewModel) } }
Et l'usage :
struct ContentView: View {
@State private var isShowingAlert: Bool = false
@State private var text: String = ""
var body: some View {
VStack {
Button(action: {
withAnimation {
isShowingAlert.toggle()
}
}, label: {
Text("Show alert")
})
}
.assemblyAlert(isShowing: $isShowingAlert,
viewModel: UniAlertViewModel(),
content: {
Text("title")
Image(systemName: "phone")
.scaleEffect(3)
.frame(width: 100, height: 100)
TextField("enter text here", text: $text)
Text("description")
}, actions: buttons)
}
}
}
Démonstration :
Voici un exemple basé sur l'interface SwiftUI Sheet
classe qui affiche une boîte de dialogue avec une invite, un champ de texte et les boutons classiques OK et Rejeter.
Tout d'abord, créons notre Dialog
qui apparaîtra lorsque l'utilisateur voudra modifier une valeur :
import SwiftUI
struct Dialog: View {
@Environment(\.presentationMode) var presentationMode
/// Edited value, passed from outside
@Binding var value: String?
/// Prompt message
var prompt: String = ""
/// The value currently edited
@State var fieldValue: String
/// Init the Dialog view
/// Passed @binding value is duplicated to @state value while editing
init(prompt: String, value: Binding<String?>) {
_value = value
self.prompt = prompt
_fieldValue = State<String>(initialValue: value.wrappedValue ?? "")
}
var body: some View {
VStack {
Text(prompt).padding()
TextField("", text: $fieldValue)
.frame(width: 200, alignment: .center)
HStack {
Button("OK") {
self.value = fieldValue
self.presentationMode.wrappedValue.dismiss()
}
Button("Dismiss") {
self.presentationMode.wrappedValue.dismiss()
}
}.padding()
}
.padding()
}
}
#if DEBUG
struct Dialog_Previews: PreviewProvider {
static var previews: some View {
var name = "John Doe"
Dialog(prompt: "Name", value: Binding<String?>.init(get: { name }, set: {name = $0 ?? ""}))
}
}
#endif
Maintenant, nous l'utilisons de cette façon dans la vue de l'appelant :
import SwiftUI
struct ContentView: View {
/// Is the input dialog displayed
@State var dialogDisplayed = false
/// The name to edit
@State var name: String? = nil
var body: some View {
VStack {
Text(name ?? "Unnamed").frame(width: 200).padding()
Button(name == nil ? "Set Name" : "Change Name") {
dialogDisplayed = true
}
.sheet(isPresented: $dialogDisplayed) {
Dialog(prompt: name == nil ? "Enter a name" : "Enter a new name", value: $name)
}
.onChange(of: name, perform: { value in
print("Name Changed : \(value)")
}
.padding()
}
.padding()
}
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
Étape 1 : Faire de la vue racine une pile Z
Étape 2 : Ajouter une variable pour afficher/masquer
@State var showAlert = false
Étape 3 : Ajoutez cette mise en page personnalisée dans la vue racine (ZStack).
if $showAlert.wrappedValue {
ZStack() {
Color.grayBackground
VStack {
//your custom layout text fields buttons
}.padding()
}
.frame(width: 300, height: 180,alignment: .center)
.cornerRadius(20).shadow(radius: 20)
}
Basé sur l'idée de la tanzolone
import Foundation
import Combine
import SwiftUI
class TextFieldAlertViewController: UIViewController {
/// Presents a UIAlertController (alert style) with a UITextField and a `Done` button
/// - Parameters:
/// - title: to be used as title of the UIAlertController
/// - message: to be used as optional message of the UIAlertController
/// - text: binding for the text typed into the UITextField
/// - isPresented: binding to be set to false when the alert is dismissed (`Done` button tapped)
init(isPresented: Binding<Bool>, alert: TextFieldAlert) {
self._isPresented = isPresented
self.alert = alert
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@Binding
private var isPresented: Bool
private var alert: TextFieldAlert
// MARK: - Private Properties
private var subscription: AnyCancellable?
// MARK: - Lifecycle
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presentAlertController()
}
private func presentAlertController() {
guard subscription == nil else { return } // present only once
let vc = UIAlertController(title: alert.title, message: alert.message, preferredStyle: .alert)
// add a textField and create a subscription to update the `text` binding
vc.addTextField {
// TODO:
// $0.placeholder = alert.placeholder
// $0.keyboardType = alert.keyboardType
// $0.text = alert.defaultValue ?? ""
$0.text = self.alert.defaultText
}
if let cancel = alert.cancel {
vc.addAction(UIAlertAction(title: cancel, style: .cancel) { _ in
// self.action(nil)
self.isPresented = false
})
}
let textField = vc.textFields?.first
vc.addAction(UIAlertAction(title: alert.accept, style: .default) { _ in
self.isPresented = false
self.alert.action(textField?.text)
})
present(vc, animated: true, completion: nil)
}
}
struct TextFieldAlert {
let title: String
let message: String?
var defaultText: String = ""
public var accept: String = "".localizedString // The left-most button label
public var cancel: String? = "".localizedString // The optional cancel (right-most) button label
public var action: (String?) -> Void // Triggers when either of the two buttons closes the dialog
}
struct AlertWrapper: UIViewControllerRepresentable {
@Binding var isPresented: Bool
let alert: TextFieldAlert
typealias UIViewControllerType = TextFieldAlertViewController
func makeUIViewController(context: UIViewControllerRepresentableContext<AlertWrapper>) -> UIViewControllerType {
TextFieldAlertViewController(isPresented: $isPresented, alert: alert)
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: UIViewControllerRepresentableContext<AlertWrapper>) {
// no update needed
}
}
struct TextFieldWrapper<PresentingView: View>: View {
@Binding var isPresented: Bool
let presentingView: PresentingView
let content: TextFieldAlert
var body: some View {
ZStack {
if (isPresented) {
AlertWrapper(isPresented: $isPresented, alert: content)
}
presentingView
}
}
}
extension View {
func alert(isPresented: Binding<Bool>, _ content: TextFieldAlert) -> some View {
TextFieldWrapper(isPresented: isPresented, presentingView: self, content: content)
}
}
Mode d'emploi
xxxView
.alert(isPresented: $showForm, TextFieldAlert(title: "", message: "") { (text) in
if text != nil {
self.saveGroup(text: text!)
}
})
func dialog(){
let alertController = UIAlertController(title: "Contry", message: "Write contrt code here", preferredStyle: .alert)
alertController.addTextField { (textField : UITextField!) -> Void in
textField.placeholder = "Country code"
}
let saveAction = UIAlertAction(title: "Save", style: .default, handler: { alert -> Void in
let secondTextField = alertController.textFields![0] as UITextField
print("county code : ",secondTextField)
})
let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: nil )
alertController.addAction(saveAction)
alertController.addAction(cancelAction)
UIApplication.shared.windows.first?.rootViewController?.present(alertController, animated: true, completion: nil)
}
Utilisation
Button(action: { self.dialog()})
{
Text("Button")
.foregroundColor(.white).fontWeight(.bold)
}
- Réponses précédentes
- Plus de réponses