Popup.swift 1.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. import SwiftUI
  2. struct Popup<T: View>: ViewModifier {
  3. let popup: T
  4. let isPresented: Bool
  5. let alignment: Alignment
  6. let direction: Direction
  7. init(isPresented: Bool, alignment: Alignment, direction: Direction, @ViewBuilder content: () -> T) {
  8. self.isPresented = isPresented
  9. self.alignment = alignment
  10. self.direction = direction
  11. popup = content()
  12. }
  13. func body(content: Content) -> some View {
  14. content
  15. .overlay(popupContent())
  16. }
  17. @ViewBuilder private func popupContent() -> some View {
  18. GeometryReader { geometry in
  19. if isPresented {
  20. popup
  21. .animation(.spring())
  22. .transition(.offset(x: 0, y: direction.offset(popupFrame: geometry.frame(in: .global))))
  23. .frame(width: geometry.size.width, height: geometry.size.height, alignment: alignment)
  24. }
  25. }
  26. }
  27. }
  28. private extension GeometryProxy {
  29. var belowScreenEdge: CGFloat {
  30. UIScreen.main.bounds.height - frame(in: .global).minY
  31. }
  32. }
  33. extension Popup {
  34. enum Direction {
  35. case top
  36. case bottom
  37. func offset(popupFrame: CGRect) -> CGFloat {
  38. switch self {
  39. case .top:
  40. let aboveScreenEdge = -popupFrame.maxY
  41. return aboveScreenEdge
  42. case .bottom:
  43. let belowScreenEdge = UIScreen.main.bounds.height - popupFrame.minY
  44. return belowScreenEdge
  45. }
  46. }
  47. }
  48. }
  49. extension View {
  50. func popup<T: View>(
  51. isPresented: Bool,
  52. alignment: Alignment = .center,
  53. direction: Popup<T>.Direction = .bottom,
  54. @ViewBuilder content: () -> T
  55. ) -> some View {
  56. modifier(Popup(isPresented: isPresented, alignment: alignment, direction: direction, content: content))
  57. }
  58. }