Created
August 24, 2024 10:13
-
-
Save vurgunmert/e888d708f1a404a8b3600b2d50c6ec79 to your computer and use it in GitHub Desktop.
SwiftUI Native and Custom Alerts Storybooks tvOS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// SwiftUIAlertsStorybooksView.swift | |
// TemplateAppTvOS | |
// | |
// Created by Mert Vurgun on 24.08.2024. | |
// | |
import SwiftUI | |
// MARK: - Main View: SwiftUIAlertsStorybooksView | |
struct SwiftUIAlertsStorybooksView: View { | |
@State private var showAlert = false | |
@State private var showActionSheet = false | |
@State private var showCustomAlert = false | |
@State private var currentAlert: Alert = Alert(title: Text("Default")) | |
@FocusState private var focusedField: FocusField? | |
// Enum to define non-focusable state when alert is active | |
enum FocusField: Hashable { | |
case none | |
} | |
var body: some View { | |
ZStack { | |
// Main content with buttons | |
VStack(spacing: 20) { | |
Button(action: showBasicAlert) { | |
Text("Basic Alert") | |
.font(.title2) | |
.padding() | |
.background(Color.blue) | |
.foregroundColor(.white) | |
.cornerRadius(10) | |
} | |
Button(action: showDestructiveAlert) { | |
Text("Destructive Alert") | |
.font(.title2) | |
.padding() | |
.background(Color.red) | |
.foregroundColor(.white) | |
.cornerRadius(10) | |
} | |
Button(action: toggleActionSheet) { | |
Text("Action Sheet") | |
.font(.title2) | |
.padding() | |
.background(Color.orange) | |
.foregroundColor(.white) | |
.cornerRadius(10) | |
} | |
Button(action: showTextInputAlert) { | |
Text("Text Input Alert") | |
.font(.title2) | |
.padding() | |
.background(Color.green) | |
.foregroundColor(.white) | |
.cornerRadius(10) | |
} | |
Button(action: { showCustomAlert.toggle() }) { | |
Text("Custom Alert") | |
.font(.title2) | |
.padding() | |
.background(Color.purple) | |
.foregroundColor(.white) | |
.cornerRadius(10) | |
} | |
} | |
.padding() | |
.alert(isPresented: $showAlert) { | |
currentAlert | |
} | |
.actionSheet(isPresented: $showActionSheet) { | |
ActionSheet(title: Text("Choose an Option"), | |
message: Text("Select one of the options below."), | |
buttons: [ | |
.default(Text("Option 1"), action: { print("Option 1 selected") }), | |
.default(Text("Option 2"), action: { print("Option 2 selected") }), | |
.default(Text("Option 3"), action: { print("Option 3 selected") }), | |
.cancel() | |
]) | |
} | |
.disabled(showCustomAlert) // Disable interaction with background content when custom alert is shown | |
// Overlay and Custom Alert Display | |
if showCustomAlert { | |
Color.black.opacity(0.5) // Semi-transparent overlay to focus attention on the alert | |
.ignoresSafeArea() | |
.onTapGesture { | |
// Prevent accidental dismissal by ignoring tap events | |
} | |
CustomAlertView(showAlert: $showCustomAlert, title: "Attention", message: "Are you sure you want to proceed?", cancelButtonTitle: "No", okButtonTitle: "Yes") | |
.transition(.move(edge: .bottom).combined(with: .opacity)) | |
.zIndex(1) // Ensure the alert appears above other content | |
} | |
} | |
.animation(.easeInOut, value: showCustomAlert) | |
.focused($focusedField, equals: showCustomAlert ? Optional.none : nil) // Prevent focus on main view when alert is shown | |
} | |
// MARK: - Alert Handling Methods | |
// Shows a basic alert | |
private func showBasicAlert() { | |
currentAlert = Alert(title: Text("Basic Alert"), | |
message: Text("This is a basic alert."), | |
dismissButton: .default(Text("OK"))) | |
showAlert = true | |
} | |
// Shows a destructive alert with delete and cancel options | |
private func showDestructiveAlert() { | |
currentAlert = Alert(title: Text("Delete Item"), | |
message: Text("Are you sure you want to delete this item?"), | |
primaryButton: .destructive(Text("Delete")), | |
secondaryButton: .cancel()) | |
showAlert = true | |
} | |
// Toggles the action sheet display | |
private func toggleActionSheet() { | |
showActionSheet = true | |
} | |
// Shows an alert indicating that text input is not supported in SwiftUI alerts | |
private func showTextInputAlert() { | |
currentAlert = Alert(title: Text("Text Input Alert"), | |
message: Text("Text input is not directly supported in SwiftUI alerts."), | |
dismissButton: .default(Text("OK"))) | |
showAlert = true | |
} | |
} | |
// MARK: - Preview | |
struct SwiftUIAlertsStorybooksView_Previews: PreviewProvider { | |
static var previews: some View { | |
SwiftUIAlertsStorybooksView() | |
} | |
} | |
struct CustomAlertView: View { | |
@Binding var showAlert: Bool | |
@FocusState private var focusedField: Field? | |
// Enum to define focusable fields (Cancel and OK buttons) | |
enum Field: Hashable { | |
case cancel, ok | |
} | |
var title: String = "Custom Alert" | |
var message: String = "This is a fully customized alert created with SwiftUI. You can add any content here." | |
var cancelButtonTitle: String = "Cancel" | |
var okButtonTitle: String = "OK" | |
var body: some View { | |
VStack(spacing: 20) { | |
// Title Section | |
Text(title) | |
.font(.system(size: 22, weight: .bold, design: .rounded)) | |
.foregroundColor(Color.blue) | |
.multilineTextAlignment(.center) | |
.padding(.horizontal) | |
// Message Section | |
Text(message) | |
.font(.system(size: 17, weight: .regular, design: .rounded)) | |
.foregroundColor(Color.gray) | |
.multilineTextAlignment(.center) | |
.padding(.horizontal) | |
// Buttons Section with Focus Management | |
HStack(spacing: 15) { | |
Button(action: { | |
showAlert = false | |
}) { | |
Text(cancelButtonTitle) | |
.font(.system(size: 18, weight: .semibold, design: .rounded)) | |
.frame(maxWidth: .infinity) | |
.padding(.vertical, 12) | |
.background(Color(UIColor.black)) | |
.foregroundColor(.primary) | |
.cornerRadius(12) | |
} | |
.focused($focusedField, equals: .cancel) // Manage focus for tvOS | |
Button(action: { | |
showAlert = false | |
}) { | |
Text(okButtonTitle) | |
.font(.system(size: 18, weight: .semibold, design: .rounded)) | |
.frame(maxWidth: .infinity) | |
.padding(.vertical, 12) | |
.background(LinearGradient(gradient: Gradient(colors: [Color.purple, Color.blue]), startPoint: .leading, endPoint: .trailing)) | |
.foregroundColor(.white) | |
.cornerRadius(12) | |
} | |
.focused($focusedField, equals: .ok) // Manage focus for tvOS | |
} | |
.padding(.horizontal) | |
} | |
.padding(.vertical, 20) | |
.background( | |
RoundedRectangle(cornerRadius: 25, style: .continuous) | |
.fill(Color(UIColor.red)) | |
.shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 5) | |
) | |
.frame(maxWidth: 300) | |
.padding(.horizontal, 40) | |
.onAppear { | |
focusedField = .ok // Initial focus on OK button | |
} | |
.onChange(of: showAlert) { _ in | |
if showAlert { | |
focusedField = .ok // Retain focus within the alert when shown | |
} | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment