Skip to content

Commit 3fa8f8e

Browse files
committed
add modifier-based presentation apis using title/subtitle
1 parent 6aedc32 commit 3fa8f8e

File tree

3 files changed

+214
-125
lines changed

3 files changed

+214
-125
lines changed

JDStatusBarNotification.xcodeproj/project.pbxproj

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10-
2884CAC02D04DCA5005E00A6 /* NotificationViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2884CABF2D04DCA5005E00A6 /* NotificationViewExtension.swift */; };
11-
2884CAC12D04DCA5005E00A6 /* NotificationViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2884CABF2D04DCA5005E00A6 /* NotificationViewExtension.swift */; };
10+
2884CAC02D04DCA5005E00A6 /* NotificationSwiftUIModifer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2884CABF2D04DCA5005E00A6 /* NotificationSwiftUIModifer.swift */; };
11+
2884CAC12D04DCA5005E00A6 /* NotificationSwiftUIModifer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2884CABF2D04DCA5005E00A6 /* NotificationSwiftUIModifer.swift */; };
1212
28D81A942B010C7500DE2CDF /* DiscoveryHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D81A932B010C7500DE2CDF /* DiscoveryHelper.swift */; };
1313
28D81A952B0110C700DE2CDF /* DiscoveryHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D81A932B010C7500DE2CDF /* DiscoveryHelper.swift */; };
1414
28D81A9B2B01217100DE2CDF /* NotificationWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D81A9A2B01217100DE2CDF /* NotificationWindow.swift */; };
@@ -202,7 +202,7 @@
202202
/* End PBXCopyFilesBuildPhase section */
203203

204204
/* Begin PBXFileReference section */
205-
2884CABF2D04DCA5005E00A6 /* NotificationViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationViewExtension.swift; sourceTree = "<group>"; };
205+
2884CABF2D04DCA5005E00A6 /* NotificationSwiftUIModifer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSwiftUIModifer.swift; sourceTree = "<group>"; };
206206
28D81A932B010C7500DE2CDF /* DiscoveryHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryHelper.swift; sourceTree = "<group>"; };
207207
28D81A9A2B01217100DE2CDF /* NotificationWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationWindow.swift; sourceTree = "<group>"; };
208208
28D81A9D2B01257A00DE2CDF /* StyleCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StyleCache.swift; sourceTree = "<group>"; };
@@ -372,7 +372,7 @@
372372
7EB914CF2ADC11F4004B3435 /* NotificationPresenter.swift */,
373373
7EF32BB02B10167B000E7CAE /* NotificationPresenterLegacyOverlay.swift */,
374374
28D81AA62B0140C100DE2CDF /* NotificationStyle.swift */,
375-
2884CABF2D04DCA5005E00A6 /* NotificationViewExtension.swift */,
375+
2884CABF2D04DCA5005E00A6 /* NotificationSwiftUIModifer.swift */,
376376
);
377377
path = Public;
378378
sourceTree = "<group>";
@@ -745,7 +745,7 @@
745745
28D81A942B010C7500DE2CDF /* DiscoveryHelper.swift in Sources */,
746746
28D81A9B2B01217100DE2CDF /* NotificationWindow.swift in Sources */,
747747
7E2A4E852AE70A4B001F0DB0 /* NotificationPresenter.swift in Sources */,
748-
2884CAC12D04DCA5005E00A6 /* NotificationViewExtension.swift in Sources */,
748+
2884CAC12D04DCA5005E00A6 /* NotificationSwiftUIModifer.swift in Sources */,
749749
28D81AA72B0140C100DE2CDF /* NotificationStyle.swift in Sources */,
750750
7E5402C6286708850079C579 /* JDStatusBarNotification.docc in Sources */,
751751
);
@@ -798,7 +798,7 @@
798798
28D81A9C2B01217100DE2CDF /* NotificationWindow.swift in Sources */,
799799
7EDAEE972AF6EC5E001B6ABE /* NotificationPresenter.swift in Sources */,
800800
28D81A952B0110C700DE2CDF /* DiscoveryHelper.swift in Sources */,
801-
2884CAC02D04DCA5005E00A6 /* NotificationViewExtension.swift in Sources */,
801+
2884CAC02D04DCA5005E00A6 /* NotificationSwiftUIModifer.swift in Sources */,
802802
28D81AA82B0140C100DE2CDF /* NotificationStyle.swift in Sources */,
803803
7EDAEEA22AF6EC5E001B6ABE /* JDStatusBarNotification.docc in Sources */,
804804
);
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
//
2+
// NotificationSwiftUIModifer.swift
3+
// JDStatusBarNotification
4+
//
5+
// Created by Markus on 12/7/24.
6+
// Copyright © 2024 Markus. All rights reserved.
7+
//
8+
9+
import SwiftUI
10+
11+
private struct SwiftUINotficationState {
12+
static var notificationId: UUID? = nil
13+
static let internalStyleName = "__swiftui-extension-style"
14+
}
15+
16+
extension View {
17+
public typealias NotificationStyleClosure = (StatusBarNotificationStyle) -> Void
18+
19+
// MARK: - ViewBuilder based
20+
21+
/// Presents a notification when a given condition is true.
22+
///
23+
/// - Parameters:
24+
/// - isPresented: A binding to a Boolean value that determines whether to
25+
/// present the notification. When the notification gets dismissed for any reason,
26+
/// this value will be set to `false` again.
27+
/// - style: A ``NotificationStyleClosure`` to customize a ``StatusBarNotificationStyle`` freely.
28+
/// - viewBuilder: A ViewBuilder closure to build your custom SwiftUI view.
29+
///
30+
/// The `View` created by the `viewBuilder` will be layouted according to the selected style & the current device
31+
/// state (rotation, status bar visibility, etc.). The background will be styled & layouted
32+
/// according to the provided style. If your custom view requires custom touch handling,
33+
/// make sure to set `style.canTapToHold` to `false`. Otherwise the `customView` won't receive any touches.
34+
///
35+
nonisolated public func notification(isPresented: Binding<Bool>, style: NotificationStyleClosure, @ViewBuilder viewBuilder: () -> some View) -> some View {
36+
let styleName = NotificationPresenter.shared.addStyle(named: SwiftUINotficationState.internalStyleName) { s in
37+
let _ = style(s)
38+
return s
39+
}
40+
return notification(isPresented: isPresented, styleName: styleName, viewBuilder: viewBuilder)
41+
}
42+
43+
44+
/// Presents a notification when a given condition is true.
45+
///
46+
/// - Parameters:
47+
/// - isPresented: A binding to a Boolean value that determines whether to
48+
/// present the notification. When the notification gets dismissed for any reason,
49+
/// this value will be set to `false` again.
50+
/// - includedStyle: A predefined ``IncludedStatusBarNotificationStyle`` to style this notification.
51+
/// - viewBuilder: A ViewBuilder closure to build your custom SwiftUI view.
52+
///
53+
/// The `View` created by the `viewBuilder` will be layouted according to the selected style & the current device
54+
/// state (rotation, status bar visibility, etc.). The background will be styled & layouted
55+
/// according to the provided style. If your custom view requires custom touch handling,
56+
/// make sure to set `style.canTapToHold` to `false`. Otherwise the `customView` won't receive any touches.
57+
///
58+
nonisolated public func notification(isPresented: Binding<Bool>, includedStyle: IncludedStatusBarNotificationStyle, @ViewBuilder viewBuilder: () -> some View) -> some View {
59+
let styleName = NotificationPresenter.shared.addStyle(named: SwiftUINotficationState.internalStyleName, usingStyle: includedStyle) { return $0 }
60+
return notification(isPresented: isPresented, styleName: styleName, viewBuilder: viewBuilder)
61+
}
62+
63+
64+
/// Presents a notification when a given condition is true.
65+
///
66+
/// - Parameters:
67+
/// - isPresented: A binding to a Boolean value that determines whether to
68+
/// present the notification. When the notification gets dismissed for any reason,
69+
/// this value will be set to `false` again.
70+
/// - styleName: The name of the style. You can use styles previously added using e.g. ``addStyle(named:usingStyle:prepare:)``.
71+
/// If no style can be found for the given `styleName` or it is `nil`, the default style will be used.
72+
/// - viewBuilder: A ViewBuilder closure to build your custom SwiftUI view.
73+
///
74+
/// The `View` created by the `viewBuilder` will be layouted according to the selected style & the current device
75+
/// state (rotation, status bar visibility, etc.). The background will be styled & layouted
76+
/// according to the provided style. If your custom view requires custom touch handling,
77+
/// make sure to set `style.canTapToHold` to `false`. Otherwise the `customView` won't receive any touches.
78+
///
79+
nonisolated public func notification(isPresented: Binding<Bool>, styleName: String? = nil, @ViewBuilder viewBuilder: () -> some View) -> some View {
80+
let np = NotificationPresenter.shared
81+
82+
// dismiss if needed
83+
if !isPresented.wrappedValue && np.isVisible && np.activeNotificationId == SwiftUINotficationState.notificationId {
84+
np.dismiss(animated: true)
85+
}
86+
87+
// present if needed
88+
if isPresented.wrappedValue && SwiftUINotficationState.notificationId == nil {
89+
np.presentSwiftView(styleName: styleName, viewBuilder: viewBuilder)
90+
trackNotificationState(isPresented: isPresented)
91+
}
92+
93+
return self
94+
}
95+
96+
// MARK: - Title/Subtitle based
97+
98+
/// Presents a notification when a given condition is true.
99+
///
100+
/// - Parameters:
101+
/// - title: The text to display as title
102+
/// - subtitle: The text to display as subtitle
103+
/// - isPresented: A binding to a Boolean value that determines whether to
104+
/// present the notification. When the notification gets dismissed for any reason,
105+
/// this value will be set to `false` again.
106+
/// - style: A ``NotificationStyleClosure`` to customize a ``StatusBarNotificationStyle`` freely.
107+
///
108+
/// The `View` created by the `viewBuilder` will be layouted according to the selected style & the current device
109+
/// state (rotation, status bar visibility, etc.). The background will be styled & layouted
110+
/// according to the provided style. If your custom view requires custom touch handling,
111+
/// make sure to set `style.canTapToHold` to `false`. Otherwise the `customView` won't receive any touches.
112+
///
113+
nonisolated public func notification(title: String, subtitle: String? = nil, isPresented: Binding<Bool>, style: NotificationStyleClosure) -> some View {
114+
let styleName = NotificationPresenter.shared.addStyle(named: SwiftUINotficationState.internalStyleName) { s in
115+
let _ = style(s)
116+
return s
117+
}
118+
return notification(title: title, subtitle: subtitle, isPresented: isPresented, styleName: styleName, includedStyle: nil)
119+
}
120+
121+
122+
/// Presents a notification when a given condition is true.
123+
///
124+
/// - Parameters:
125+
/// - title: The text to display as title
126+
/// - subtitle: The text to display as subtitle
127+
/// - isPresented: A binding to a Boolean value that determines whether to
128+
/// present the notification. When the notification gets dismissed for any reason,
129+
/// this value will be set to `false` again.
130+
/// - includedStyle: A predefined ``IncludedStatusBarNotificationStyle`` to style this notification.
131+
///
132+
/// The `View` created by the `viewBuilder` will be layouted according to the selected style & the current device
133+
/// state (rotation, status bar visibility, etc.). The background will be styled & layouted
134+
/// according to the provided style. If your custom view requires custom touch handling,
135+
/// make sure to set `style.canTapToHold` to `false`. Otherwise the `customView` won't receive any touches.
136+
///
137+
nonisolated public func notification(title: String, subtitle: String? = nil, isPresented: Binding<Bool>, includedStyle: IncludedStatusBarNotificationStyle) -> some View {
138+
return notification(title: title, subtitle: subtitle, isPresented: isPresented, styleName: nil, includedStyle: includedStyle)
139+
}
140+
141+
142+
/// Presents a notification when a given condition is true.
143+
///
144+
/// - Parameters:
145+
/// - title: The text to display as title
146+
/// - subtitle: The text to display as subtitle
147+
/// - isPresented: A binding to a Boolean value that determines whether to
148+
/// present the notification. When the notification gets dismissed for any reason,
149+
/// this value will be set to `false` again.
150+
/// - styleName: The name of the style. You can use styles previously added using e.g. ``addStyle(named:usingStyle:prepare:)``.
151+
/// If no style can be found for the given `styleName` or it is `nil`, the default style will be used.
152+
///
153+
/// The `View` created by the `viewBuilder` will be layouted according to the selected style & the current device
154+
/// state (rotation, status bar visibility, etc.). The background will be styled & layouted
155+
/// according to the provided style. If your custom view requires custom touch handling,
156+
/// make sure to set `style.canTapToHold` to `false`. Otherwise the `customView` won't receive any touches.
157+
///
158+
nonisolated public func notification(title: String, subtitle: String? = nil, isPresented: Binding<Bool>, styleName: String? = nil) -> some View {
159+
return notification(title: title, subtitle: subtitle, isPresented: isPresented, styleName: styleName, includedStyle: nil)
160+
}
161+
162+
// MARK: - Internal
163+
164+
165+
nonisolated private func notification(title: String, subtitle: String? = nil, isPresented: Binding<Bool>, styleName: String? = nil, includedStyle: IncludedStatusBarNotificationStyle? = nil) -> some View {
166+
let np = NotificationPresenter.shared
167+
168+
// dismiss if needed
169+
if !isPresented.wrappedValue && np.isVisible && np.activeNotificationId == SwiftUINotficationState.notificationId {
170+
np.dismiss(animated: true)
171+
}
172+
173+
// present if needed
174+
if isPresented.wrappedValue && SwiftUINotficationState.notificationId == nil {
175+
if let includedStyle {
176+
np.present(title, subtitle: subtitle, includedStyle: includedStyle)
177+
} else {
178+
np.present(title, subtitle: subtitle, styleName: styleName)
179+
}
180+
trackNotificationState(isPresented: isPresented)
181+
}
182+
183+
return self
184+
}
185+
186+
nonisolated private func trackNotificationState(isPresented: Binding<Bool>) {
187+
let np = NotificationPresenter.shared
188+
189+
// remember id of our presentation
190+
SwiftUINotficationState.notificationId = np.activeNotificationId
191+
192+
// setup callback to react to other calls replacing this presentation
193+
np.didPresentNotificationClosure = {
194+
if $0.activeNotificationId != SwiftUINotficationState.notificationId {
195+
isPresented.wrappedValue = false
196+
SwiftUINotficationState.notificationId = nil
197+
}
198+
}
199+
200+
// reset state on dismissal
201+
np.didDismissNotificationClosure = {
202+
$0.didPresentNotificationClosure = nil
203+
$0.didDismissNotificationClosure = nil
204+
SwiftUINotficationState.notificationId = nil
205+
isPresented.wrappedValue = false
206+
}
207+
}
208+
}

JDStatusBarNotification/Public/NotificationViewExtension.swift

Lines changed: 0 additions & 119 deletions
This file was deleted.

0 commit comments

Comments
 (0)