Skip to content

Commit f924dce

Browse files
committed
✨ feat: add safe area insets handling for toast notifications
1 parent 2eef800 commit f924dce

File tree

4 files changed

+48
-10
lines changed

4 files changed

+48
-10
lines changed

SNUTT/Modules/Shared/SharedUIComponents/Sources/AnimatableTabView/AnimatableTabView.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,9 @@ public struct TabScene<T: TabItem> {
218218

219219
public init<Content>(tabItem: T, rootView: Content) where Content: View {
220220
self.tabItem = tabItem
221-
self.rootView = AnyView(rootView)
221+
self.rootView = AnyView(GeometryReader(content: { reader in
222+
rootView.reportBottomSafeArea(reader.safeAreaInsets.bottom)
223+
}))
222224
}
223225

224226
public init(tabItem: T) {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import SwiftUI
2+
3+
/// Preference key for communicating safe area insets (including TabView height) from child views
4+
struct SafeAreaBottomInsetsPreferenceKey: PreferenceKey {
5+
static let defaultValue: CGFloat = 0
6+
7+
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
8+
value = max(value, nextValue())
9+
}
10+
}
11+
12+
extension View {
13+
/// Reports the bottom safe area inset (including TabView height) to ancestor views
14+
func reportBottomSafeArea(_ inset: CGFloat) -> some View {
15+
preference(key: SafeAreaBottomInsetsPreferenceKey.self, value: inset)
16+
}
17+
}

SNUTT/Modules/Shared/SharedUIComponents/Sources/Toast/Toast+Modifier.swift

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ extension View {
3636

3737
private struct ToastContainerModifier<Content: View>: View {
3838
@State private var viewModel = ToastContainerViewModel()
39+
@State private var bottomSafeArea: CGFloat = 0
3940
let content: Content
4041

4142
init(content: Content) {
@@ -50,18 +51,27 @@ private struct ToastContainerModifier<Content: View>: View {
5051
viewModel.present(toast)
5152
}
5253
)
53-
.overlay(alignment: .bottom) {
54-
VStack(spacing: 8) {
55-
ForEach(viewModel.toasts) { toast in
56-
ToastItemView(toast: toast) {
57-
viewModel.handleButtonTap(for: toast)
54+
.onPreferenceChange(SafeAreaBottomInsetsPreferenceKey.self) { value in
55+
bottomSafeArea = value
56+
}
57+
.overlay(alignment: .bottom,) {
58+
GeometryReader { reader in
59+
/// The view inside `.overlay` already respects the safe area.
60+
/// Subtracting the default safe area inset gives us the extra bottom inset (for example, from tab bars).
61+
let additionalSafeAreaInsetBottom = bottomSafeArea - reader.safeAreaInsets.bottom
62+
VStack(spacing: 8) {
63+
ForEach(viewModel.toasts) { toast in
64+
ToastItemView(toast: toast) {
65+
viewModel.handleButtonTap(for: toast)
66+
}
67+
.transition(.move(edge: .bottom).combined(with: .blurReplace))
5868
}
59-
.transition(.move(edge: .bottom).combined(with: .blurReplace))
6069
}
70+
.padding(.horizontal, 20)
71+
.padding(.bottom, additionalSafeAreaInsetBottom + 8)
72+
.animation(.defaultSpring, value: viewModel.toasts)
73+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
6174
}
62-
.padding(.horizontal, 20)
63-
.padding(.bottom, 16)
64-
.animation(.defaultSpring, value: viewModel.toasts)
6575
}
6676
}
6777
}

SNUTT/Modules/Shared/SharedUIComponents/Sources/Toast/ToastContainer.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,19 @@ import SwiftUIUtility
44
@MainActor
55
@Observable
66
final class ToastContainerViewModel {
7+
private enum Constants {
8+
static let maxVisibleToasts = 3
9+
}
10+
711
private(set) var toasts: [Toast] = []
812
private var dismissTasks: [UUID: Task<Void, Never>] = [:]
913

1014
func present(_ toast: Toast) {
15+
// Dismiss oldest toast if we've reached the maximum
16+
if toasts.count >= Constants.maxVisibleToasts, let oldestToast = toasts.first {
17+
dismiss(oldestToast)
18+
}
19+
1120
toasts.append(toast)
1221

1322
let dismissTask = Task { @MainActor in

0 commit comments

Comments
 (0)