Skip to content

Commit

Permalink
Initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
stleamist committed Jun 5, 2022
1 parent 3ad5a37 commit 2b251c2
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 35 deletions.
23 changes: 5 additions & 18 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,28 +1,15 @@
// swift-tools-version: 5.6
// The swift-tools-version declares the minimum version of Swift required to build this package.
// swift-tools-version:5.1

import PackageDescription

let package = Package(
name: "FullScreenOverlay",
platforms: [.iOS(.v13), .macOS(.v10_15), .tvOS(.v13), .watchOS(.v6)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "FullScreenOverlay",
targets: ["FullScreenOverlay"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.library(name: "FullScreenOverlay", targets: ["FullScreenOverlay", "Previews"])
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "FullScreenOverlay",
dependencies: []),
.testTarget(
name: "FullScreenOverlayTests",
dependencies: ["FullScreenOverlay"]),
.target(name: "FullScreenOverlay", dependencies: []),
.target(name: "Previews", dependencies: ["FullScreenOverlay"])
]
)
6 changes: 0 additions & 6 deletions Sources/FullScreenOverlay/FullScreenOverlay.swift

This file was deleted.

17 changes: 17 additions & 0 deletions Sources/FullScreenOverlay/FullScreenOverlayContainer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import SwiftUI

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
class FullScreenOverlayContainer: ObservableObject {

static let shared: FullScreenOverlayContainer = .init()

@Published private(set) var overlays: [PresentationSpace: [AnyHashable: AnyView]] = .init()

func updateOverlay<ID: Hashable, Overlay: View>(_ overlay: Overlay, for id: ID, in presentationSpace: PresentationSpace) {
self.overlays[presentationSpace, default: [:]].updateValue(AnyView(overlay), forKey: id)
}

func removeOverlay<ID: Hashable>(for id: ID, in presentationSpace: PresentationSpace) {
self.overlays[presentationSpace, default: [:]].removeValue(forKey: id)
}
}
22 changes: 22 additions & 0 deletions Sources/FullScreenOverlay/FullScreenOverlayPresenter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import SwiftUI

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
struct FullScreenOverlayPresenter: ViewModifier {

var presentationSpace: PresentationSpace

@ObservedObject private var container: FullScreenOverlayContainer = .shared

func body(content: Content) -> some View {
content.overlay(
ZStack {
ForEach(
container.overlays[presentationSpace, default: [:]].sorted(by: { $0.key.hashValue < $1.key.hashValue }),
id: \.key
) { _, overlay in
overlay
}
}
)
}
}
38 changes: 38 additions & 0 deletions Sources/FullScreenOverlay/FullScreenOverlaySetter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import SwiftUI

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
struct FullScreenOverlaySetter<Overlay: View>: ViewModifier {

var overlay: Overlay
var presentationSpace: PresentationSpace

@State private var id: UUID = .init()
@State private var isAppearing: Bool = false

func body(content: Content) -> some View {
return content
.onAppear { isAppearing = true; updateOverlay() }
.onDisappear { isAppearing = false; removeOverlay() }
.onUpdate { isAppearing ? updateOverlay() : removeOverlay() }
// onDisappear 이후에 onUpdate가 실행되는 경우도 있기 때문에, isAppearing 상태를 저장해 오버레이가 다시 추가되는 것을 방지한다.
}

private func updateOverlay() {
FullScreenOverlayContainer.shared.updateOverlay(overlay, for: id, in: presentationSpace)
}

private func removeOverlay() {
FullScreenOverlayContainer.shared.removeOverlay(for: id, in: presentationSpace)
}
}

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
private extension View {

func onUpdate(perform action: (() -> Void)? = nil) -> some View {
if let action = action {
DispatchQueue.main.async(execute: action)
}
return self
}
}
6 changes: 6 additions & 0 deletions Sources/FullScreenOverlay/PresentationSpace.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import SwiftUI

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public enum PresentationSpace: Equatable, Hashable {
case named(AnyHashable)
}
17 changes: 17 additions & 0 deletions Sources/FullScreenOverlay/View+FullScreenOverlay.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import SwiftUI

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public extension View {

func fullScreenOverlayPresentationSpace<T: Hashable>(name: T) -> some View {
self
.modifier(FullScreenOverlayPresenter(presentationSpace: .named(name)))
}

@ViewBuilder func fullScreenOverlay<Overlay: View>(
presentationSpace: PresentationSpace,
@ViewBuilder content: () -> Overlay
) -> some View {
self.modifier(FullScreenOverlaySetter(overlay: content(), presentationSpace: presentationSpace))
}
}
63 changes: 63 additions & 0 deletions Sources/Previews/Previews.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import SwiftUI
import FullScreenOverlay

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
private struct RootView: View {

@State private var isPresentingBottomSheet: Bool = false

var body: some View {
List {
Button(
"Present Bottom Sheet",
action: { isPresentingBottomSheet = true }
)
.fullScreenOverlay(presentationSpace: .named("RootView")) {
if isPresentingBottomSheet {
BottomSheet(onDismiss: { isPresentingBottomSheet = false })
}
}
}
}
}

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
private struct BottomSheet: View {

var onDismiss: () -> Void

@State private var isAppearing: Bool = false

var body: some View {
Color.black.opacity(0.2)
.edgesIgnoringSafeArea(.all)
.zIndex(-1)
.onTapGesture(perform: onDismiss)
.transition(.opacity.animation(.default))
VStack {
Text("Bottom Sheet")
.font(.title.weight(.semibold))
.padding(.vertical, 128)
Button("Dismiss", action: { isAppearing = false; onDismiss() })
}
.padding(.vertical, 32)
.frame(maxWidth: .infinity)
.background(
Color.white
.shadow(radius: 1)
.edgesIgnoringSafeArea(.all)
)
.frame(maxHeight: .infinity, alignment: .bottom)
.transition(.move(edge: .bottom))
.animation(.spring())
}
}

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
internal struct SwiftUIView_Previews: PreviewProvider {

static var previews: some View {
RootView()
.fullScreenOverlayPresentationSpace(name: "RootView")
}
}
11 changes: 0 additions & 11 deletions Tests/FullScreenOverlayTests/FullScreenOverlayTests.swift

This file was deleted.

0 comments on commit 2b251c2

Please sign in to comment.