Skip to content

Instantly share code, notes, and snippets.

@khorbushko
Created November 28, 2020 10:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save khorbushko/de42603c42b9e8dfa87c9e729af0bd09 to your computer and use it in GitHub Desktop.
Save khorbushko/de42603c42b9e8dfa87c9e729af0bd09 to your computer and use it in GitHub Desktop.
Alert for SwiftUI
extension View {
func uniAlert<Content>(
isShowing: Binding<Bool>,
@ViewBuilder content: @escaping () -> Content,
actions: [UniAlertButton]
) -> some View where Content: View {
UniAlert(
isShowing: isShowing,
displayContent: content(),
buttons: actions,
presentationView: self
)
}
}
struct UniAlert<Presenter, Content>: View where Content: View, Presenter: View {
@Binding private (set) var isShowing: Bool
let displayContent: Content
let buttons: [UniAlertButton]
var backgroundColor: Color = Color.gray
var contentBackgroundColor: Color = Color.white
var contentPadding: CGFloat = 16
var contentCornerRadius: CGFloat = 12
let presentationView: Presenter
private var requireHorizontalPositioning: Bool {
let maxButtonPositionedHorizontally = 2
return buttons.count > maxButtonPositionedHorizontally
}
var body: some View {
GeometryReader { geometry in
ZStack {
presentationView.disabled(isShowing)
backgroundColorView()
let expectedWidth = geometry.size.width * 0.7
VStack(spacing: 0) {
VStack {
displayContent
}
.padding(contentPadding)
buttonsPad(expectedWidth)
}
.background(contentBackgroundColor)
.cornerRadius(contentCornerRadius)
.shadow(radius: 1)
.opacity(self.isShowing ? 1 : 0)
.frame(
minWidth: expectedWidth,
maxWidth: expectedWidth
)
.background(Color.clear)
.animation(.easeInOut)
}
.edgesIgnoringSafeArea(.all)
.zIndex(Double.greatestFiniteMagnitude)
}
}
private func backgroundColorView() -> some View {
backgroundColor
.edgesIgnoringSafeArea(.all)
.opacity(self.isShowing ? 0.8 : 0)
}
private func buttonsPad(_ expectedWidth: CGFloat) -> some View {
VStack {
if requireHorizontalPositioning {
verticalButtonPad()
.padding([.bottom], 12)
} else {
horizontalButtonsPadFor(expectedWidth)
.padding([.bottom], 12)
}
}
}
private func verticalButtonPad() -> some View {
VStack {
ForEach(0..<buttons.count) {
Divider()
.padding([.leading, .trailing], -contentPadding)
let current = buttons[$0]
Button(action: {
current.action()
withAnimation {
self.isShowing.toggle()
}
}, label: {
current.content.frame(height: 30)
})
}
}
}
private func horizontalButtonsPadFor(_ expectedWidth: CGFloat) -> some View {
VStack {
Divider()
.padding([.leading, .trailing], -contentPadding)
HStack {
let sidesOffset = contentPadding * 2
let maxHorizontalWidth = expectedWidth - sidesOffset
Spacer()
ForEach(0..<buttons.count) {
Spacer()
if $0 != 0 {
Divider().frame(height: 50)
.padding([.top, .bottom], -8)
}
let current = buttons[$0]
Button(action: {
current.action()
withAnimation {
self.isShowing.toggle()
}
}, label: {
current.content.frame(height: 30)
.multilineTextAlignment(.center)
})
.frame(maxWidth: maxHorizontalWidth, minHeight: 30)
}
Spacer()
}
}
}
}
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
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment