Last active
August 7, 2021 15:09
-
-
Save dsandif/4d901c5f13d5abf9b83c977104ae9ba5 to your computer and use it in GitHub Desktop.
SwiftUI Popup modal with Blurred Background
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
// | |
// 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