Skip to content

Instantly share code, notes, and snippets.

@dsandif
Last active August 7, 2021 15:09
Show Gist options
  • Save dsandif/4d901c5f13d5abf9b83c977104ae9ba5 to your computer and use it in GitHub Desktop.
Save dsandif/4d901c5f13d5abf9b83c977104ae9ba5 to your computer and use it in GitHub Desktop.
SwiftUI Popup modal with Blurred Background
//
// PopupViewModifier.swift
//
// Created by Darien Sandifer on 8/7/21.
//
import Foundation
import SwiftUI
struct Popup<T: View>: ViewModifier {
let popup: T
let alignment: Alignment
let direction: Direction
let isPresented: Bool
init(isPresented: Bool, alignment: Alignment, direction: Direction, @ViewBuilder content: () -> T) {
self.isPresented = isPresented
self.alignment = alignment
self.direction = direction
popup = content()
}
func body(content: Content) -> some View {
content
.overlay(popupContent())
}
@ViewBuilder
private func popupContent() -> some View {
GeometryReader { geometry in
if isPresented {
popup
.animation(.spring())
.transition(.offset(x: 0, y: direction.offset(popupFrame: geometry.frame(in: .global))))
.frame(width: geometry.size.width, height: geometry.size.height, alignment: alignment)
}
}.background(isPresented ? BlurredBackground(style: .systemThinMaterial) : nil)
}
}
extension Popup {
enum Direction {
case top, bottom
func offset(popupFrame: CGRect) -> CGFloat {
switch self {
case .top:
let aboveScreenEdge = -popupFrame.maxY
return aboveScreenEdge
case .bottom:
let belowScreenEdge = UIScreen.main.bounds.height - popupFrame.minY
return belowScreenEdge
}
}
}
}
extension View {
func popup<T: View>(
isPresented: Bool,
alignment: Alignment = .center,
direction: Popup<T>.Direction = .bottom,
@ViewBuilder content: () -> T
) -> some View {
return modifier(Popup(isPresented: isPresented, alignment: alignment, direction: direction, content: content))
}
}
private extension View {
func onGlobalFrameChange(_ onChange: @escaping (CGRect) -> Void) -> some View {
background(GeometryReader { geometry in
Color.clear.preference(key: FramePreferenceKey.self, value: geometry.frame(in: .global))
})
.onPreferenceChange(FramePreferenceKey.self, perform: onChange)
}
func print(_ varargs: Any...) -> Self {
Swift.print(varargs)
return self
}
}
private struct FramePreferenceKey: PreferenceKey {
static let defaultValue = CGRect.zero
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
value = nextValue()
}
}
private extension View {
@ViewBuilder func applyIf<T: View>(_ condition: @autoclosure () -> Bool, apply: (Self) -> T) -> some View {
if condition() {
apply(self)
} else {
self
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment