├── SwiftUI_NotificationBanner_Example ├── SwiftUI_NotificationBanner_Example │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── SwiftUI_NotificationBanner_ExampleApp.swift │ ├── RootView.swift │ ├── AppDelegate.swift │ ├── SceneDelegate.swift │ └── NotificationRootView.swift ├── SwiftUI_NotificationBanner_Example_macOS │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── SwiftUI_NotificationBanner_Example_macOS.entitlements │ ├── ContentView.swift │ └── SwiftUI_NotificationBanner_Example_macOSApp.swift ├── SwiftUI_NotificationBanner_Example.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── SwiftUI_NotificationBanner_Example.xcscheme │ └── project.pbxproj ├── SwiftUI_NotificationBanner_Example_macOSUITests │ ├── SwiftUI_NotificationBanner_Example_macOSUITestsLaunchTests.swift │ └── SwiftUI_NotificationBanner_Example_macOSUITests.swift └── SwiftUI_NotificationBanner_Example_macOSTests │ └── SwiftUI_NotificationBanner_Example_macOSTests.swift ├── .gitignore ├── Sources └── SwiftUI_NotificationBanner │ ├── Model │ ├── Shadow.swift │ ├── DYNotificationTypeSettings.swift │ ├── DYNotification.swift │ └── DYNotificationHandler.swift │ ├── View │ ├── PassThroughWindow.swift │ ├── NotificationScene.swift │ ├── Example.swift │ └── DYNotificationBanner.swift │ └── Shape │ └── RoundedCornerRectangle.swift ├── .swiftpm └── xcode │ ├── package.xcworkspace │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ └── xcschemes │ └── SwiftUI_NotificationBanner.xcscheme ├── Tests └── SwiftUI_NotificationBannerTests │ └── SwiftUI_NotificationBannerTests.swift ├── LICENSE ├── Package.swift ├── SwiftUI_NotificationBanner.podspec └── README.md /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example_macOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example_macOS/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/config/registries.json 8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 9 | .netrc 10 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/SwiftUI_NotificationBanner/Model/Shadow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Dominik Butz on 24/10/2022. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | 12 | internal typealias Shadow = (color: Color, radius: CGFloat, x: CGFloat, y: CGFloat) 13 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example_macOS/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example_macOS/SwiftUI_NotificationBanner_Example_macOS.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Tests/SwiftUI_NotificationBannerTests/SwiftUI_NotificationBannerTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import SwiftUI_NotificationBanner 3 | 4 | final class SwiftUI_NotificationBannerTests: XCTestCase { 5 | func testExample() throws { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | // XCTAssertEqual(SwiftUI_NotificationBanner().text, "Hello, World!") 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example_macOS/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // SwiftUI_NotificationBanner_Example_macOS 4 | // 5 | // Created by Dominik Butz on 4/7/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | var body: some View { 12 | VStack { 13 | Image(systemName: "globe") 14 | .imageScale(.large) 15 | .foregroundColor(.accentColor) 16 | Text("Hello, world!") 17 | } 18 | .padding() 19 | } 20 | } 21 | 22 | struct ContentView_Previews: PreviewProvider { 23 | static var previews: some View { 24 | ContentView() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_ExampleApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUI_NotificationBanner_ExampleApp.swift 3 | // SwiftUI_NotificationBanner_Example 4 | // 5 | // Created by Dominik Butz on 11/11/2022. 6 | // 7 | 8 | import SwiftUI 9 | import SwiftUI_NotificationBanner 10 | 11 | @main 12 | struct SwiftUI_NotificationBanner_ExampleApp: App { 13 | 14 | @StateObject var notificationHandler = DYNotificationHandler() 15 | 16 | @UIApplicationDelegateAdaptor var delegate: AppDelegate 17 | 18 | var body: some Scene { 19 | WindowGroup { 20 | RootView().environmentObject(notificationHandler) 21 | } 22 | 23 | 24 | } 25 | } 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example/RootView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // SwiftUI_NotificationBanner_Example 4 | // 5 | // Created by Dominik Butz on 11/11/2022. 6 | // 7 | 8 | import SwiftUI 9 | import SwiftUI_NotificationBanner 10 | 11 | struct RootView: View { 12 | #if os(iOS) 13 | @EnvironmentObject var sceneDelegate: SceneDelegate 14 | #endif 15 | @EnvironmentObject var notificationHandler: DYNotificationHandler 16 | 17 | var body: some View { 18 | Example().environmentObject(notificationHandler) 19 | #if os(iOS) 20 | .onAppear { 21 | sceneDelegate.notificationHandler = notificationHandler 22 | } 23 | #endif 24 | } 25 | } 26 | 27 | //struct RootView_Previews: PreviewProvider { 28 | // static var previews: some View { 29 | // RootView() 30 | // } 31 | //} 32 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftUI_NotificationBanner_Example 4 | // 5 | // Created by Dominik Butz on 14/11/2022. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | #if os(iOS) 12 | class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject { 13 | // not required in this example 14 | // func application( 15 | // _ application: UIApplication, 16 | // didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil 17 | // ) -> Bool { 18 | // // ... 19 | // return true 20 | // } 21 | 22 | func application( 23 | _ application: UIApplication, 24 | configurationForConnecting connectingSceneSession: UISceneSession, 25 | options: UIScene.ConnectionOptions 26 | ) -> UISceneConfiguration { 27 | let sceneConfig = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role) 28 | sceneConfig.delegateClass = SceneDelegate.self 29 | return sceneConfig 30 | } 31 | } 32 | #endif 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dominik Butz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example_macOSUITests/SwiftUI_NotificationBanner_Example_macOSUITestsLaunchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUI_NotificationBanner_Example_macOSUITestsLaunchTests.swift 3 | // SwiftUI_NotificationBanner_Example_macOSUITests 4 | // 5 | // Created by Dominik Butz on 4/7/2023. 6 | // 7 | 8 | import XCTest 9 | 10 | final class SwiftUI_NotificationBanner_Example_macOSUITestsLaunchTests: XCTestCase { 11 | 12 | override class var runsForEachTargetApplicationUIConfiguration: Bool { 13 | true 14 | } 15 | 16 | override func setUpWithError() throws { 17 | continueAfterFailure = false 18 | } 19 | 20 | func testLaunch() throws { 21 | let app = XCUIApplication() 22 | app.launch() 23 | 24 | // Insert steps here to perform after app launch but before taking a screenshot, 25 | // such as logging into a test account or navigating somewhere in the app 26 | 27 | let attachment = XCTAttachment(screenshot: app.screenshot()) 28 | attachment.name = "Launch Screen" 29 | attachment.lifetime = .keepAlways 30 | add(attachment) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example_macOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.7 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SwiftUI_NotificationBanner", 8 | platforms: [ 9 | .iOS(.v14), .macOS(.v11) 10 | ], 11 | products: [ 12 | // Products define the executables and libraries a package produces, and make them visible to other packages. 13 | .library( 14 | name: "SwiftUI_NotificationBanner", 15 | targets: ["SwiftUI_NotificationBanner"]), 16 | ], 17 | dependencies: [ 18 | // Dependencies declare other packages that this package depends on. 19 | // .package(url: /* package url */, from: "1.0.0"), 20 | ], 21 | targets: [ 22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 23 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 24 | .target( 25 | name: "SwiftUI_NotificationBanner", 26 | dependencies: []), 27 | .testTarget( 28 | name: "SwiftUI_NotificationBannerTests", 29 | dependencies: ["SwiftUI_NotificationBanner"]), 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner.podspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 4 | # Any lines starting with a # are optional, but their use is encouraged 5 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 6 | # 7 | 8 | Pod::Spec.new do |s| 9 | s.name = 'SwiftUI_NotificationBanner' 10 | s.version = '0.3' 11 | s.summary = 'With SwiftUI Notification Banner it is super-easy to display in-app notifications.' 12 | s.swift_version = '5.1' 13 | 14 | 15 | s.description = <<-DESC 16 | Finally a native SwiftUI notification banner package! With SwiftUI Notification Banner it is super-easy to display in-app notifications. 17 | DESC 18 | 19 | s.homepage = 'https://github.com/DominikButz/SwiftUI_NotificationBanner' 20 | s.license = { :type => 'MIT', :file => 'LICENSE' } 21 | s.author = { 'dominikbutz' => 'dominikbutz@gmail.com' } 22 | s.source = { :git => 'https://github.com/DominikButz/SwiftUI_NotificationBanner.git', :tag => s.version.to_s } 23 | 24 | s.platform = :ios 25 | s.ios.deployment_target = '14.0' 26 | 27 | s.platform = :macos 28 | s.macos.deployment_target = '11.0' 29 | 30 | s.source_files = 'Sources/**/*' 31 | 32 | 33 | # s.public_header_files = 'SwiftUI_NotificationBanner/**/*.h' 34 | 35 | end 36 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example_macOSTests/SwiftUI_NotificationBanner_Example_macOSTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUI_NotificationBanner_Example_macOSTests.swift 3 | // SwiftUI_NotificationBanner_Example_macOSTests 4 | // 5 | // Created by Dominik Butz on 4/7/2023. 6 | // 7 | 8 | import XCTest 9 | @testable import SwiftUI_NotificationBanner_Example_macOS 10 | 11 | final class SwiftUI_NotificationBanner_Example_macOSTests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDownWithError() throws { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() throws { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | // Any test you write for XCTest can be annotated as throws and async. 25 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. 26 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. 27 | } 28 | 29 | func testPerformanceExample() throws { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Sources/SwiftUI_NotificationBanner/View/PassThroughWindow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PassThroughWindow.swift 3 | // 4 | // 5 | // Created by Federico Zanetello (www.fivestars.blog) in 2021 6 | // 7 | 8 | #if canImport(UIKit) 9 | import Foundation 10 | import UIKit 11 | /// PassThroughWindow. can used as window for a custom notification root view. This window is placed above the existing app window 12 | public class PassThroughWindow: UIWindow { 13 | public var notificationHandler: DYNotificationHandler? 14 | /// hitTest - override function 15 | /// - Parameters: 16 | /// - point: a CGPoint locating the hit event 17 | /// - event: UIEvent 18 | /// - Returns: view of the root view controller or nil 19 | public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { 20 | guard let hitView = super.hitTest(point, with: event) else { 21 | 22 | return nil 23 | } 24 | 25 | // If there's no notification showing, pass through touches to the main window 26 | guard let _ = notificationHandler?.currentNotification else { 27 | 28 | return rootViewController?.view == hitView ? nil : hitView 29 | } 30 | 31 | if let rootView = rootViewController?.view, 32 | let notificationView = rootView.hitTest(point, with: event), 33 | notificationView.isDescendant(of: rootView) 34 | { 35 | 36 | //print("passthrough window: touched the notifcation root view") 37 | return hitView 38 | } 39 | 40 | // Otherwise, pass through the touch to the window below 41 | return nil 42 | } 43 | 44 | 45 | } 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example_macOSUITests/SwiftUI_NotificationBanner_Example_macOSUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUI_NotificationBanner_Example_macOSUITests.swift 3 | // SwiftUI_NotificationBanner_Example_macOSUITests 4 | // 5 | // Created by Dominik Butz on 4/7/2023. 6 | // 7 | 8 | import XCTest 9 | 10 | final class SwiftUI_NotificationBanner_Example_macOSUITests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 19 | } 20 | 21 | override func tearDownWithError() throws { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testExample() throws { 26 | // UI tests must launch the application that they test. 27 | let app = XCUIApplication() 28 | app.launch() 29 | 30 | // Use XCTAssert and related functions to verify your tests produce the correct results. 31 | } 32 | 33 | func testLaunchPerformance() throws { 34 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { 35 | // This measures how long it takes to launch your application. 36 | measure(metrics: [XCTApplicationLaunchMetric()]) { 37 | XCUIApplication().launch() 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // SwiftUI_NotificationBanner_Example 4 | // 5 | // Created by Dominik Butz on 14/11/2022. 6 | // 7 | 8 | #if os(iOS) 9 | import Foundation 10 | import SwiftUI 11 | import SwiftUI_NotificationBanner 12 | 13 | 14 | class SceneDelegate: UIResponder, UIWindowSceneDelegate, ObservableObject { 15 | 16 | var notificationHandler: DYNotificationHandler? { 17 | didSet { 18 | setupNotificationWindow() 19 | } 20 | } 21 | 22 | var notificationWindow: UIWindow? 23 | weak var windowScene: UIWindowScene? 24 | 25 | func scene( 26 | _ scene: UIScene, 27 | willConnectTo session: UISceneSession, 28 | options connectionOptions: UIScene.ConnectionOptions 29 | ) { 30 | windowScene = scene as? UIWindowScene 31 | 32 | } 33 | 34 | // without a second app window, the notification banner will not appear above a sheet, fullScreenCover etc. 35 | // special thanks to Federico Zanetello (www.fivestars.blog) for this function 36 | func setupNotificationWindow() { 37 | guard let windowScene = windowScene, let notificationHandler = notificationHandler else { 38 | 39 | return 40 | } 41 | 42 | let notificationViewController = UIHostingController(rootView: NotificationRootView().environmentObject(notificationHandler)) 43 | notificationViewController.view.backgroundColor = .clear 44 | 45 | let notificationWindow = PassThroughWindow(windowScene: windowScene) 46 | notificationWindow.notificationHandler = notificationHandler 47 | notificationWindow.rootViewController = notificationViewController 48 | notificationWindow.isHidden = false 49 | self.notificationWindow = notificationWindow 50 | } 51 | } 52 | #endif 53 | -------------------------------------------------------------------------------- /Sources/SwiftUI_NotificationBanner/Shape/RoundedCornerRectangle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoundedCornerRectangle.swift 3 | // Progressive 4 | // 5 | // Created by Dominik Butz on 28/5/2020. 6 | // Copyright © 2020 Duoyun. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | internal struct RoundedCornerRectangle: Shape { 13 | var tl: CGFloat = 0.0 14 | var tr: CGFloat = 0.0 15 | var bl: CGFloat = 0.0 16 | var br: CGFloat = 0.0 17 | 18 | func path(in rect: CGRect) -> Path { 19 | var path = Path() 20 | 21 | let w = rect.size.width 22 | let h = rect.size.height 23 | 24 | // Make sure we do not exceed the size of the rectangle 25 | let tr = min(min(self.tr, h/2), w/2) 26 | let tl = min(min(self.tl, h/2), w/2) 27 | let bl = min(min(self.bl, h/2), w/2) 28 | let br = min(min(self.br, h/2), w/2) 29 | 30 | path.move(to: CGPoint(x: w / 2.0, y: 0)) 31 | path.addLine(to: CGPoint(x: w - tr, y: 0)) 32 | path.addArc(center: CGPoint(x: w - tr, y: tr), radius: tr, 33 | startAngle: Angle(degrees: -90), endAngle: Angle(degrees: 0), clockwise: false) 34 | 35 | path.addLine(to: CGPoint(x: w, y: h - br)) 36 | path.addArc(center: CGPoint(x: w - br, y: h - br), radius: br, 37 | startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 90), clockwise: false) 38 | 39 | path.addLine(to: CGPoint(x: bl, y: h)) 40 | path.addArc(center: CGPoint(x: bl, y: h - bl), radius: bl, 41 | startAngle: Angle(degrees: 90), endAngle: Angle(degrees: 180), clockwise: false) 42 | 43 | path.addLine(to: CGPoint(x: 0, y: tl)) 44 | path.addArc(center: CGPoint(x: tl, y: tl), radius: tl, 45 | startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false) 46 | 47 | return path 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example/NotificationRootView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DYNotificationWindowView.swift 3 | // 4 | // 5 | // Created by Dominik Butz on 14/11/2022. 6 | // 7 | 8 | import SwiftUI 9 | import SwiftUI_NotificationBanner 10 | 11 | struct NotificationRootView: View { 12 | @EnvironmentObject var notificationHandler: DYNotificationHandler 13 | @Environment(\.colorScheme) var colorScheme 14 | 15 | var body: some View { 16 | Color.clear 17 | .ignoresSafeArea(.all) 18 | .notificationView(handler: notificationHandler, notificationBanner: {notification in 19 | 20 | DYNotificationBanner(notification: notification, frameWidth: min(450, UIScreen.main.bounds.size.width)) 21 | .text(color: foregroundColorForNotification(type: notification.type)) 22 | .image(color: foregroundColorForNotification(type: notification.type)) 23 | .dropShadow(color: colorScheme == .light ? .gray.opacity(0.4) : .clear, radius: 5, x: 0, y: notification.displayEdge == .top ? 5 : -5) 24 | .cornerRadius(self.cornerRadius(displayEdge: notification.displayEdge)) 25 | 26 | 27 | }).statusBarHidden(notificationHandler.currentNotification?.displayEdge == .top) 28 | 29 | } 30 | 31 | func foregroundColorForNotification(type: DYNotificationType)->Color { 32 | switch type { 33 | case .info, .error: 34 | return .white 35 | case .warning, .success: 36 | return .black 37 | } 38 | } 39 | 40 | 41 | func cornerRadius(displayEdge: Edge)-> CGFloat { 42 | guard displayEdge != .leading && displayEdge != .trailing else { 43 | return UIDevice.current.userInterfaceIdiom == .phone ? 5 : 10 44 | } 45 | guard UIDevice.current.userInterfaceIdiom == .phone else { 46 | return 10 47 | } 48 | return UIScreen.main.bounds.width < UIScreen.main.bounds.height ? 0 : 10 49 | } 50 | } 51 | 52 | 53 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example_macOS/SwiftUI_NotificationBanner_Example_macOSApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUI_NotificationBanner_Example_macOSApp.swift 3 | // SwiftUI_NotificationBanner_Example_macOS 4 | // 5 | // Created by Dominik Butz on 4/7/2023. 6 | // 7 | import SwiftUI 8 | import SwiftUI_NotificationBanner 9 | 10 | @main 11 | struct SwiftUI_NotificationBanner_ExampleApp: App { 12 | @Environment(\.colorScheme) var colorScheme 13 | @StateObject var notificationHandler = DYNotificationHandler() 14 | 15 | var body: some Scene { 16 | WindowGroup { 17 | RootView().environmentObject(notificationHandler) 18 | .notificationView(handler: notificationHandler, notificationBanner: {notification in 19 | 20 | DYNotificationBanner(notification: notification, frameWidth: 450) 21 | .text(color: foregroundColorForNotification(type: notification.type)) 22 | .image(color: foregroundColorForNotification(type: notification.type)) 23 | // .backgroundGradientForNotificationType(success: LinearGradient(colors: [.green.opacity(0.4), .green], startPoint: .leading, endPoint: .trailing), error: LinearGradient(colors: [.red, .red.opacity(0.3)], startPoint: .top, endPoint: .bottom)) 24 | .dropShadow(color: colorScheme == .light ? .gray.opacity(0.4) : .clear, radius: 5, x: 0, y: notification.displayEdge == .top ? 5 : -5) 25 | .cornerRadius(self.cornerRadius(displayEdge: notification.displayEdge)) 26 | 27 | }) 28 | 29 | } 30 | 31 | 32 | 33 | 34 | } 35 | 36 | 37 | func foregroundColorForNotification(type: DYNotificationType)->Color { 38 | switch type { 39 | case .info, .error: 40 | return .white 41 | case .warning, .success: 42 | return .black 43 | } 44 | } 45 | 46 | 47 | func cornerRadius(displayEdge: Edge)-> CGFloat { 48 | return 10 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/SwiftUI_NotificationBanner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /Sources/SwiftUI_NotificationBanner/View/NotificationScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIView.swift 3 | // 4 | // 5 | // Created by Dominik Butz on 10/11/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | internal struct NotificationScene: ViewModifier { 11 | @ObservedObject var notificationHandler: DYNotificationHandler 12 | let animation: Animation 13 | var notificationView: (DYNotification)->N 14 | 15 | func body(content: Content)-> some View { 16 | 17 | ZStack { 18 | 19 | content 20 | 21 | VStack { 22 | 23 | if let notification = self.notificationHandler.currentNotification, notification.displayEdge == .bottom { 24 | Spacer() 25 | } 26 | 27 | if let notification = self.notificationHandler.currentNotification { 28 | self.notificationView(notification) 29 | .onTapGesture { 30 | print("tapped notification banner view") 31 | if notification.dismissOnTap { 32 | self.notificationHandler.remove(notification: notification, userInitiated: true) // remove the current notfication, initiated by user 33 | } 34 | print("calling tap handler if applicable") 35 | notification.tapHandler?() 36 | } 37 | .id(notification.id) 38 | .transition(AnyTransition.move(edge: notification.displayEdge)) 39 | 40 | } 41 | 42 | if let notification = self.notificationHandler.currentNotification, notification.displayEdge == .top { 43 | Spacer() 44 | } 45 | } 46 | 47 | 48 | } 49 | .edgesIgnoringSafeArea(.all) 50 | .animation(animation, value: notificationHandler.currentNotification) 51 | 52 | 53 | 54 | } 55 | 56 | 57 | 58 | } 59 | 60 | public extension View { 61 | 62 | /// notificationView modifier function 63 | /// - Parameters: 64 | /// - handler: a DYNotificationHandler object 65 | /// - animation: appear animation of the notfification banner 66 | /// - notificationView: notificationView closue - return a DYNotificationView or a custom view. 67 | /// - Returns: some View 68 | func notificationView(handler: DYNotificationHandler, animation: Animation = .easeInOut(duration: 0.5), notificationBanner: @escaping (DYNotification)->N)->some View { 69 | self.modifier(NotificationScene(notificationHandler: handler, animation: animation, notificationView: notificationBanner)) 70 | } 71 | 72 | } 73 | 74 | //struct DYNotificationScene_Previews: PreviewProvider { 75 | // static var previews: some View { 76 | // DYNotificationScene() 77 | // } 78 | //} 79 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example.xcodeproj/xcshareddata/xcschemes/SwiftUI_NotificationBanner_Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 42 | 44 | 50 | 51 | 52 | 53 | 59 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /Sources/SwiftUI_NotificationBanner/Model/DYNotificationTypeSettings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Dominik Butz on 10/11/2022. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | internal struct DYNotificationBannerSettings { 12 | 13 | //text settings 14 | var titleFont: Font = .headline 15 | var messageFont: Font = .body 16 | var textColor: Color = .primary 17 | 18 | // image settings 19 | var imageMaxHeight: CGFloat? = 40 20 | var imageMaxWidth: CGFloat? = 40 21 | var imageContentMode: ContentMode = .fit 22 | var imageRenderingMode: Image.TemplateRenderingMode = .template 23 | var imageColor: Color? = .primary 24 | 25 | // background settings 26 | var infoBannerBackgroundGradient: LinearGradient = LinearGradient(colors: [.blue], startPoint: .top, endPoint: .bottom) 27 | var successBannerBackgroundGradient: LinearGradient = LinearGradient(colors: [.green], startPoint: .top, endPoint: .bottom) 28 | var warningBannerBackgroundGradient: LinearGradient = LinearGradient(colors: [.yellow], startPoint: .top, endPoint: .bottom) 29 | var errorBannerBackgroundGradient: LinearGradient = LinearGradient(colors: [.red], startPoint: .top, endPoint: .bottom) 30 | var dropShadow: Shadow? = nil 31 | var cornerRadius: CGFloat = 0 32 | 33 | // 34 | // 35 | // init(titleFont: Font = .headline, messageFont: Font = .body, textColor:Color = Color.primary, imageMaxHeight: CGFloat? = 40, imageMaxWidth: CGFloat? = 40, imageContentMode: ContentMode = .fit, imageRenderingMode: Image.TemplateRenderingMode = .template, imageColor: Color? = Color.primary, infoBannerBackgroundGradient: LinearGradient = LinearGradient(colors: [.blue], startPoint: .top, endPoint: .bottom), successBannerBackgroundGradient: LinearGradient = LinearGradient(colors: [.green], startPoint: .top, endPoint: .bottom), warningBannerBackgroundGradient: LinearGradient = LinearGradient(colors: [.yellow], startPoint: .top, endPoint: .bottom), errorBannerBackgroundGradient: LinearGradient = LinearGradient(colors: [.red], startPoint: .top, endPoint: .bottom), dropShadow: Shadow? = nil, cornerRadius: CGFloat = 0) { 36 | // self.messageFont = messageFont 37 | // self.titleFont = titleFont 38 | // self.textColor = textColor 39 | // self.imageMaxHeight = imageMaxHeight 40 | // self.imageMaxWidth = imageMaxWidth 41 | // self.imageContentMode = imageContentMode 42 | // self.imageRenderingMode = imageRenderingMode 43 | // self.imageColor = imageColor 44 | // self.infoBannerBackgroundGradient = infoBannerBackgroundGradient 45 | // self.successBannerBackgroundGradient = successBannerBackgroundGradient 46 | // self.warningBannerBackgroundGradient = warningBannerBackgroundGradient 47 | // self.errorBannerBackgroundGradient = errorBannerBackgroundGradient 48 | // self.dropShadow = dropShadow 49 | // self.cornerRadius = cornerRadius 50 | // } 51 | 52 | func gradientFor(notificationType: DYNotificationType)->LinearGradient { 53 | switch notificationType { 54 | case .error: 55 | return self.errorBannerBackgroundGradient 56 | case .success: 57 | return self.successBannerBackgroundGradient 58 | case .warning: 59 | return self.warningBannerBackgroundGradient 60 | default: 61 | return self.infoBannerBackgroundGradient 62 | } 63 | } 64 | 65 | 66 | } 67 | 68 | 69 | -------------------------------------------------------------------------------- /Sources/SwiftUI_NotificationBanner/Model/DYNotification.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DYNotification.swift 3 | // 4 | // 5 | // Created by Dominik Butz on 10/11/2022. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | 12 | 13 | public struct DYNotification: Identifiable, Equatable { 14 | 15 | public let id: String 16 | public let message: String 17 | public var title: String? 18 | public var image: Image? 19 | public let type: DYNotificationType 20 | public let displayDuration: TimeInterval 21 | public let dismissOnTap: Bool 22 | public let displayEdge: Edge 23 | #if canImport(UIKit) 24 | public var hapticFeedbackType: UINotificationFeedbackGenerator.FeedbackType? 25 | #endif 26 | public var tapHandler: (()->Void)? 27 | 28 | #if canImport(UIKit) 29 | public init(id: String = UUID().uuidString, title: String? = nil, message: String, image: Image? = nil, type: DYNotificationType = .info, displayDuration: TimeInterval = 3, dismissOnTap: Bool = true, displayEdge: Edge = .top, hapticFeedbackType: UINotificationFeedbackGenerator.FeedbackType?, tapHandler: (()->Void)? = nil) { 30 | self.init(id: id, title: title, message: message, image: image, type: type, displayDuration: displayDuration, dismissOnTap: dismissOnTap, displayEdge: displayEdge, tapHandler: tapHandler) 31 | self.hapticFeedbackType = hapticFeedbackType 32 | } 33 | // #else 34 | // public init(id: String = UUID().uuidString, title: String? = nil, message: String, image: Image? = nil, type: DYNotificationType = .info, displayDuration: TimeInterval = 3, dismissOnTap: Bool = true, displayEdge: Edge = .top, tapHandler: (()->Void)? = nil) { 35 | // self.init(id: id, title: title, message: message, image: image, type: type, displayDuration: displayDuration, dismissOnTap: dismissOnTap, displayEdge: displayEdge, tapHandler: tapHandler) 36 | // 37 | // } 38 | 39 | #endif 40 | 41 | 42 | public init(id: String = UUID().uuidString, title: String? = nil, message: String, image: Image? = nil, type: DYNotificationType = .info, displayDuration: TimeInterval = 3, dismissOnTap: Bool = true, displayEdge: Edge = .top, tapHandler: (()->Void)? = nil) { 43 | 44 | self.id = id 45 | self.message = message 46 | self.title = title 47 | self.image = image 48 | self.type = type 49 | self.displayDuration = displayDuration 50 | self.dismissOnTap = dismissOnTap 51 | self.displayEdge = displayEdge 52 | self.tapHandler = tapHandler 53 | } 54 | 55 | 56 | 57 | public static func == (lhs: DYNotification, rhs: DYNotification) -> Bool { 58 | lhs.id == rhs.id 59 | } 60 | 61 | } 62 | 63 | public enum DYNotificationType { 64 | 65 | case info, success, warning, error 66 | } 67 | 68 | //public enum DYNotificationBannerEdge { 69 | // case top, bottom 70 | //} 71 | 72 | 73 | //public struct DYNotification: Identifiable, Equatable { 74 | // 75 | // public let id: String 76 | // 77 | // public let displayDuration: TimeInterval 78 | // public let dismissOnTap: Bool 79 | // public var notificationView: N 80 | // 81 | // public init(id: String = UUID().uuidString, displayDuration: TimeInterval = 3, dismissOnTap: Bool = true, notificationView: N) { 82 | // self.id = id 83 | // self.displayDuration = displayDuration 84 | // self.dismissOnTap = dismissOnTap 85 | // self.notificationView = notificationView 86 | // } 87 | // 88 | // public static func == (lhs: DYNotification, rhs: DYNotification) -> Bool { 89 | // lhs.id == rhs.id 90 | // } 91 | // 92 | //} 93 | -------------------------------------------------------------------------------- /Sources/SwiftUI_NotificationBanner/Model/DYNotificationHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DYNotificationHandler.swift 3 | // 4 | // 5 | // Created by Dominik Butz on 10/11/2022. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | 12 | /// Notification Handler 13 | public class DYNotificationHandler: ObservableObject { 14 | 15 | #if os(iOS) 16 | let feedbackGenerator: UINotificationFeedbackGenerator 17 | #endif 18 | let queue: OperationQueue 19 | 20 | var queuedOperations: [String: Operation ] = [:] 21 | var currentTimer: DispatchSourceTimer? 22 | var currentTimerNotificationId: String? 23 | 24 | @Published public var currentNotification: DYNotification? 25 | 26 | public init() { 27 | 28 | self.queue = OperationQueue() 29 | self.queue.maxConcurrentOperationCount = 1 30 | self.queue.qualityOfService = .userInteractive 31 | 32 | #if os(iOS) 33 | self.feedbackGenerator = UINotificationFeedbackGenerator() 34 | #endif 35 | 36 | 37 | } 38 | 39 | 40 | /// show function 41 | /// - Parameter notification: a DYNotificationHandler object 42 | public func show(notification: DYNotification) { 43 | #if os(iOS) 44 | self.feedbackGenerator.prepare() 45 | #endif 46 | 47 | let operation = BlockOperation() 48 | 49 | operation.addExecutionBlock { 50 | DispatchQueue.main.async { 51 | self.currentNotification = notification 52 | self.currentTimerNotificationId = notification.id 53 | 54 | #if os(iOS) 55 | if let hapticFeedbackType = self.currentNotification?.hapticFeedbackType { 56 | self.feedbackGenerator.notificationOccurred(hapticFeedbackType) 57 | } 58 | #endif 59 | 60 | // Create and start timer for auto-dismissal 61 | self.startDismissalTimer(for: notification) 62 | } 63 | } 64 | 65 | self.queuedOperations[notification.id] = operation 66 | self.queue.addOperation(operation) 67 | } 68 | 69 | private func startDismissalTimer(for notification: DYNotification) { 70 | // Cancel any existing timer 71 | currentTimer?.cancel() 72 | 73 | // Create new timer 74 | let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.main) 75 | timer.schedule(deadline: .now() + notification.displayDuration) 76 | 77 | timer.setEventHandler { [weak self] in 78 | guard let self = self else { return } 79 | 80 | // Only auto-dismiss if this is still the current notification 81 | if self.currentTimerNotificationId == notification.id { 82 | self.remove(notification: notification, userInitiated: false) 83 | } 84 | } 85 | 86 | currentTimer = timer 87 | timer.resume() 88 | } 89 | 90 | func remove(notification: DYNotification, userInitiated: Bool = false) { 91 | if self.currentNotification == notification { 92 | self.currentNotification = nil 93 | 94 | // Cancel the current timer since notification is being removed 95 | if currentTimerNotificationId == notification.id { 96 | currentTimer?.cancel() 97 | currentTimer = nil 98 | currentTimerNotificationId = nil 99 | } 100 | } 101 | 102 | if let operation = self.queuedOperations.removeValue(forKey: notification.id) as? BlockOperation { 103 | operation.cancel() 104 | 105 | // If user initiated dismissal, show next notification immediately (after brief animation delay) 106 | // If auto-dismissal, use longer delay 107 | let delay: TimeInterval = userInitiated ? 0.5 : 1.1 108 | 109 | DispatchQueue.main.asyncAfter(deadline: .now() + delay) { 110 | // The queue will automatically process the next operation 111 | } 112 | } 113 | } 114 | 115 | } 116 | 117 | -------------------------------------------------------------------------------- /Sources/SwiftUI_NotificationBanner/View/Example.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIView.swift 3 | // 4 | // 5 | // Created by Dominik Butz on 10/11/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public struct Example: View { 11 | 12 | @EnvironmentObject var notificationHandler: DYNotificationHandler 13 | @State private var sheetPresented: Bool = false 14 | 15 | public init() {} 16 | 17 | public var body: some View { 18 | GeometryReader { proxy in 19 | VStack(alignment: .center, spacing: 10) { 20 | Spacer() 21 | 22 | Button(action: { 23 | 24 | notificationHandler.show(notification: self.infoNotification) 25 | }, label: { 26 | Text("Info Banner") 27 | }).foregroundColor(.blue) 28 | 29 | Button(action: { 30 | 31 | notificationHandler.show(notification: self.warningNotification) 32 | }, label: { 33 | Text("Warning Status Banner") 34 | }).foregroundColor(.yellow) 35 | 36 | Button(action: { 37 | notificationHandler.show(notification: self.errorNotification) 38 | }, label: { 39 | Text("Error Status Banner") 40 | }).foregroundColor(.red) 41 | 42 | Spacer() 43 | 44 | #if os(iOS) 45 | Button { 46 | self.sheetPresented = true 47 | } label: { 48 | Text("Launch Sheet").padding(5).overlay(RoundedRectangle(cornerRadius: 10).stroke(.blue, lineWidth: 2)) 49 | } 50 | .sheet(isPresented: $sheetPresented) { 51 | SheetView().environmentObject(notificationHandler) 52 | }.padding() 53 | #endif 54 | } 55 | .frame(width: proxy.size.width, height: proxy.size.height) 56 | } 57 | 58 | } 59 | 60 | var infoNotification: DYNotification { 61 | 62 | let title = "Warm reminder" 63 | let message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." 64 | let image = Image(systemName: "checkmark.seal.fill") 65 | let type: DYNotificationType = .info 66 | let displayDuration: TimeInterval = 10 67 | let dismissOnTap = true 68 | let displayEdge: Edge = .leading 69 | 70 | #if os(iOS) 71 | return DYNotification(title: title, message: message, image: image, type: type, displayDuration: displayDuration, dismissOnTap: dismissOnTap, displayEdge: displayEdge, hapticFeedbackType: .success, tapHandler: { 72 | print("info banner tapped") 73 | }) 74 | 75 | #else 76 | return DYNotification(title: title, message: message, image: image, type: type, displayDuration: displayDuration, dismissOnTap: dismissOnTap, displayEdge: displayEdge, tapHandler: { 77 | print("info banner tapped") 78 | }) 79 | #endif 80 | 81 | } 82 | 83 | var warningNotification: DYNotification { 84 | let message = "Running out of time!" 85 | let image = Image(systemName: "checkmark.seal.fill") 86 | let type: DYNotificationType = .warning 87 | let displayDuration: TimeInterval = 5 88 | let dismissOnTap = true 89 | let displayEdge: Edge = .top 90 | 91 | #if os(iOS) 92 | return DYNotification(message: message, image: image, type: type, displayDuration: displayDuration, dismissOnTap: dismissOnTap, displayEdge: displayEdge, hapticFeedbackType: .warning, tapHandler: { 93 | print("warning banner tapped") 94 | }) 95 | 96 | #else 97 | return DYNotification(message: message, image: image, type: type, displayDuration: displayDuration, dismissOnTap: dismissOnTap, displayEdge: displayEdge, tapHandler: { 98 | print("warning banner tapped") 99 | }) 100 | #endif 101 | 102 | } 103 | 104 | var errorNotification: DYNotification { 105 | let title = "Error" 106 | let message = "Danger Zone! An unknown error occurred." 107 | let image = Image(systemName: "exclamationmark.triangle.fill") 108 | let type: DYNotificationType = .error 109 | let displayDuration: TimeInterval = 2 110 | let dismissOnTap = true 111 | let displayEdge: Edge = .bottom 112 | 113 | #if os(iOS) 114 | return DYNotification(title: title, message: message, image: image, type: type, displayDuration: displayDuration, dismissOnTap: dismissOnTap, displayEdge: displayEdge, hapticFeedbackType: .error, tapHandler: { 115 | print("error banner tapped") 116 | }) 117 | 118 | #else 119 | return DYNotification(title: title, message: message, image: image, type: type, displayDuration: displayDuration, dismissOnTap: dismissOnTap, displayEdge: displayEdge, tapHandler: { 120 | print("error banner tapped") 121 | }) 122 | #endif 123 | 124 | } 125 | 126 | 127 | } 128 | 129 | struct SheetView: View { 130 | @EnvironmentObject var notificationHandler: DYNotificationHandler 131 | var body: some View { 132 | 133 | VStack { 134 | Text("Sheet Header").font(.title).padding() 135 | Spacer() 136 | Button(action: { 137 | notificationHandler.show(notification: self.successNotification) 138 | }, label: { 139 | Text("Launch success banner above sheet!") 140 | }).foregroundColor(.green) 141 | Spacer() 142 | } 143 | } 144 | 145 | var successNotification: DYNotification { 146 | let title = "Success" 147 | let message = "Notification banner successfully presented above sheet!" 148 | let image = Image(systemName: "checkmark.circle") 149 | let type: DYNotificationType = .success 150 | let displayDuration: TimeInterval = 3 151 | let dismissOnTap = true 152 | let displayEdge: Edge = .top 153 | 154 | #if os(iOS) 155 | return DYNotification(title: title, message: message, image: image, type: type, displayDuration: displayDuration, dismissOnTap: dismissOnTap, displayEdge: displayEdge, hapticFeedbackType: .success, tapHandler: { 156 | print("success banner tapped") 157 | }) 158 | 159 | #else 160 | return DYNotification(title: title, message: message, image: image, type: type, displayDuration: displayDuration, dismissOnTap: dismissOnTap, displayEdge: displayEdge, tapHandler: { 161 | print("success banner tapped") 162 | }) 163 | #endif 164 | 165 | } 166 | } 167 | 168 | 169 | 170 | //#if DEBUG 171 | //struct Example_Previews: PreviewProvider { 172 | // 173 | // static var previews: some View { 174 | // 175 | // Example() 176 | // } 177 | //} 178 | //#endif 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftUI Notification Banner (v0.3) 2 | 3 | ## Features 4 | 5 | Finally a native SwiftUI notification banner package! With SwiftUI Notification Banner it is super-easy to display in-app notifications. 6 | 7 | * Attach a notificationView-modifier to the top view in your view hierarchy to make sure it will always appear on top 8 | * The notificationBanner closure is agnostic to what kind of View you pass in - you can use the default DYNotificationBanner or simply create your own banner view! 9 | * From version 0.3, the package also works natively under macOS. 10 | * Check out the code example below and the example project included in this package for more details. 11 | 12 | ## Installation 13 | 14 | ### Swift Package Manager 15 | Simply copy the github link of this project into the Swift Package Manager and install it. Don't forget to add the library to your target. 16 | To use the notificationView-modifier, DYNotificationBanner and DYNotificationHandler, import SwiftUI_NotificationBanner. 17 | 18 | ### Cocoapods 19 | platform :ios, '14.0' 20 | 21 | target '[project name]' do 22 | pod 'SwiftUI_NotificationBanner' 23 | end 24 | 25 | ## Code Example 26 | 27 | ### Basics 28 | 29 | ![Example](https://raw.githubusercontent.com/DominikButz/gitResources/main/SwiftUI_NotificationBanner/NotificationBannerExample01.gif) 30 | 31 | 32 | As shown below in the code example DYNotificationBanner can be modified easily by applying multiple modifiers. 33 | Here is a complete list including the given default values: 34 | 35 | * text(titleFont: Font = .headline, messageFont: Font = .body, color: Color = .primary) 36 | * image(maxHeight: CGFloat? = 40, maxWidth: CGFloat? = 40, contentMode: ContentMode = .fit, renderingMode: Image.TemplateRenderingMode = .template, color: Color? = .primary) 37 | * backgroundGradientForNotificationType(info: LinearGradient = LinearGradient(colors: [.blue], startPoint: .top, endPoint: .bottom), success: LinearGradient = LinearGradient(colors: [.green], startPoint: .top, endPoint: .bottom), warning: LinearGradient = LinearGradient(colors: [.yellow], startPoint: .top, endPoint: .bottom), error: LinearGradient = LinearGradient(colors: [.red], startPoint: .top, endPoint: .bottom)) 38 | * cornerRadius(_ radius: CGFloat = 0) 39 | * dropShadow(color: Color = .clear, radius: CGFloat = 5, x: CGFloat = 0, y: CGFloat = 5) 40 | 41 | 42 | 43 | 44 | ```Swift 45 | 46 | import SwiftUI 47 | import SwiftUI_NotificationBanner 48 | 49 | struct RootView: View { 50 | @EnvironmentObject var notificationHandler: DYNotificationHandler 51 | @Environment(\.colorScheme) var colorScheme 52 | 53 | var body: some View { 54 | // your root view content 55 | .notificationView(handler: notificationHandler, notificationBanner: {notification in 56 | 57 | DYNotificationBanner(notification: notification, frameWidth: min(450, UIScreen.main.bounds.size.width)) 58 | .text(color: notification.type == .info || notification.type == .error ? .white : .primary) 59 | .image(color: notification.type == .info || notification.type == .error ? .white : .primary) 60 | //change the default colors to create an actual gradient background: 61 | // .backgroundGradientForNotificationType(success: LinearGradient(colors: [.green.opacity(0.4), .green], startPoint: .leading, endPoint: .trailing), error: LinearGradient(colors: [.red, .red.opacity(0.3)], startPoint: .top, endPoint: .bottom)) 62 | .dropShadow(color: self.colorScheme == .light ? .gray.opacity(0.4) : .clear, radius: 5, x: 0, y: notification.displayEdge == .top ? 5 : -5) 63 | .cornerRadius(self.cornerRadius) 64 | 65 | }) 66 | 67 | } 68 | 69 | 70 | 71 | var cornerRadius: CGFloat { 72 | guard UIDevice.current.userInterfaceIdiom == .phone else { 73 | return 10 74 | } 75 | return UIScreen.main.bounds.width < UIScreen.main.bounds.height ? 0 : 10 76 | } 77 | } 78 | 79 | 80 | 81 | ``` 82 | 83 | How to launch a notification banner? 84 | Pass the notification handler (DYNotificationHandler) as EnvironmentObject to your sub-views. 85 | Wherever a notification should be displayed, call the notification handler's show function: 86 | 87 | ```Swift 88 | 89 | // The macOS DYNotification initialiser does not contain "haptic feedback type" since it is part of UIKit. 90 | 91 | notificationHandler.show(notification: DYNotification(title: "Warm reminder", 92 | message: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", 93 | image: Image(systemName: "checkmark.seal.fill"), 94 | type: .info, displayDuration: 3, 95 | dismissOnTap: true, 96 | hapticFeedbackType: .info, 97 | tapHandler: { 98 | print("info banner tapped") 99 | })) 100 | 101 | ``` 102 | 103 | The title and image parameters are optionals - which means that you can create a simple status bar notification banner without a title or image. 104 | 105 | Check out the included example project for details. 106 | 107 | 108 | 109 | ### Advanced Implementation: the Sheet-Problem 110 | 111 | But wait a minute, you might object: The gif above shows that the notification banner is displayed above a sheet, however, that is impossible because SwiftUI sheets as well fullScreenCovers are always presented as topmost views! So is this some kind of black magic? 112 | 113 | The sad truth is that with just one app window, a sheet and full screen cover will indeed always be shown on top, so unfortunately, any notification banner will be covered without mercy... 114 | 115 | However, there is a clever solution - we just create another app window above the main app window and attach the notificationBanner to the root view of that second window. You can find the details of that solution [here](https://www.fivestars.blog/articles/swiftui-windows/). Thanks to Federico Zanetello for this great article! 116 | 117 | In the example project that is included in this package, I have adapted this solution to displaying notification banners - simply open the example project and copy the necessary code lines into your project. 118 | 119 | This solution only works for iOS / iPadOS, not under macOS. 120 | 121 | 122 | ## Change log 123 | 124 | #### [Version 0.3.1](https://github.com/DominikButz/SwiftUI_NotificationBanner/releases/tag/0.3.1) 125 | Bug fixes: 1) Setup with passthrough window does not block tap gesture detection any more on the banner view. For this to work, you need to set the notification handler variable of the passthrough window in your Scene delegate (see example code). 2) If there is more than one banner in the queue, tap-dimissing the current banner will now display the following banner without waiting for the display duration of the current banner to elapse. 126 | 127 | #### [Version 0.3](https://github.com/DominikButz/SwiftUI_NotificationBanner/releases/tag/0.3) 128 | The package can be used under macOS now. 129 | 130 | #### [Version 0.2](https://github.com/DominikButz/SwiftUI_NotificationBanner/releases/tag/0.2) 131 | Renamed the modifier to notificationView and the default banner view to DYNotificationBanner. Updated cornerRadius logic for leading and trailing display edge. 132 | 133 | #### [Version 0.1.1](https://github.com/DominikButz/SwiftUI_NotificationBanner/releases/tag/0.1.1) 134 | Updated dropShadow modifier function and added documentation. 135 | 136 | #### [Version 0.1](https://github.com/DominikButz/SwiftUI_NotificationBanner/releases/tag/0.1) 137 | Initial public release. 138 | 139 | ## Author 140 | 141 | dominikbutz@gmail.com 142 | 143 | ## License 144 | 145 | SwiftUI NotificationBanner is available under the MIT license. See the LICENSE file for more info. 146 | 147 | -------------------------------------------------------------------------------- /Sources/SwiftUI_NotificationBanner/View/DYNotificationBanner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Dominik Butz on 10/11/2022. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | public struct DYNotificationBanner: View { 12 | 13 | // @Environment(\.colorScheme) var colorScheme 14 | var notification: DYNotification 15 | var settings: DYNotificationBannerSettings = DYNotificationBannerSettings() 16 | let frameWidth: CGFloat 17 | 18 | #if os(iOS) 19 | let topSafeArea: CGFloat = UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0 20 | let bottomSafeArea: CGFloat = UIApplication.shared.windows.first?.safeAreaInsets.bottom ?? 0 21 | #else 22 | let topSafeArea: CGFloat = NSApplication.shared.windows.first?.contentView?.safeAreaInsets.top ?? 0 23 | let bottomSafeArea: CGFloat = NSApplication.shared.windows.first?.contentView?.safeAreaInsets.bottom ?? 0 24 | #endif 25 | 26 | /// intialiser of DYNotificationBanner 27 | /// - Parameters: 28 | /// - notification: the current notification to be displayed as banner 29 | /// - frameWidth: width of the banner frame 30 | public init(notification: DYNotification, frameWidth: CGFloat) { 31 | 32 | self.notification = notification 33 | self.frameWidth = frameWidth 34 | } 35 | 36 | public var body: some View { 37 | 38 | HStack(spacing: 5) { 39 | 40 | Spacer() 41 | 42 | if let image = notification.image { 43 | image.renderingMode(settings.imageRenderingMode).resizable().aspectRatio(contentMode: settings.imageContentMode).frame(maxWidth: settings.imageMaxWidth, maxHeight: settings.imageMaxHeight).foregroundColor(settings.imageColor).padding(.horizontal, 10) 44 | 45 | } 46 | VStack(alignment: .leading, spacing:5) { 47 | if let title = notification.title { 48 | Text(title).font(settings.titleFont) 49 | } 50 | Text(notification.message).font(settings.messageFont).fixedSize(horizontal: false, vertical: true) 51 | }.foregroundColor(settings.textColor) 52 | 53 | Spacer() 54 | 55 | }.padding(.top, notification.displayEdge == .top ? topSafeArea : 5) 56 | .padding(.bottom, notification.displayEdge == .bottom ? bottomSafeArea : 5) 57 | .padding(8) 58 | .frame(width: frameWidth) 59 | .background(settings.gradientFor(notificationType: notification.type)) 60 | .clipShape(backgroundShape) 61 | .shadow(color: settings.dropShadow?.color ?? .clear, radius: settings.dropShadow?.radius ?? 0, x: settings.dropShadow?.x ?? 0, y: settings.dropShadow?.y ?? 0) 62 | 63 | } 64 | 65 | var backgroundShape: some Shape { 66 | 67 | let topCorner: CGFloat = notification.displayEdge != .top ? settings.cornerRadius : 0 68 | let bottomCorner: CGFloat = notification.displayEdge != .bottom ? settings.cornerRadius : 0 69 | 70 | return RoundedCornerRectangle(tl: topCorner, tr: topCorner, bl: bottomCorner, br: bottomCorner) 71 | } 72 | 73 | 74 | 75 | } 76 | 77 | public extension View where Self == DYNotificationBanner { 78 | 79 | /// banner text modifier function 80 | /// - Parameters: 81 | /// - titleFont: font of the title label 82 | /// - messageFont: font of the message body label 83 | /// - color: text color of title and message labels 84 | /// - Returns: modified DYNotificationBanner 85 | func text(titleFont: Font = .headline, messageFont: Font = .body, color: Color = .primary)->DYNotificationBanner { 86 | 87 | var modView = self 88 | modView.settings.titleFont = titleFont 89 | modView.settings.messageFont = messageFont 90 | modView.settings.textColor = color 91 | return modView 92 | 93 | } 94 | 95 | /// banner image modifier function 96 | /// - Parameters: 97 | /// - maxHeight: max frame height of the image 98 | /// - maxWidth: max frame width of the image 99 | /// - contentMode: image content mode 100 | /// - renderingMode: image rendering mode - original or template 101 | /// - color: foreground color of the image (if rendering mode is template) 102 | /// - Returns: modified DYNotificationBanner 103 | func image(maxHeight: CGFloat? = 40, maxWidth: CGFloat? = 40, contentMode: ContentMode = .fit, renderingMode: Image.TemplateRenderingMode = .template, color: Color? = .primary)->DYNotificationBanner { 104 | var modView = self 105 | modView.settings.imageMaxWidth = maxWidth 106 | modView.settings.imageMaxHeight = maxHeight 107 | modView.settings.imageContentMode = contentMode 108 | modView.settings.imageRenderingMode = renderingMode 109 | modView.settings.imageColor = color 110 | return modView 111 | } 112 | 113 | /// banner background color gradient modifier function 114 | /// - Parameters: 115 | /// - info: gradient of an info type banner 116 | /// - success: gradient of a success type banner 117 | /// - warning: gradient of ar warning type banner 118 | /// - error: gradient of an error type banner 119 | /// - Returns: modified DYNotificationBanner 120 | func backgroundGradientForNotificationType(info: LinearGradient = LinearGradient(colors: [.blue], startPoint: .top, endPoint: .bottom), success: LinearGradient = LinearGradient(colors: [.green], startPoint: .top, endPoint: .bottom), warning: LinearGradient = LinearGradient(colors: [.yellow], startPoint: .top, endPoint: .bottom), error: LinearGradient = LinearGradient(colors: [.red], startPoint: .top, endPoint: .bottom))-> DYNotificationBanner { 121 | 122 | var modView = self 123 | modView.settings.infoBannerBackgroundGradient = info 124 | modView.settings.successBannerBackgroundGradient = success 125 | modView.settings.warningBannerBackgroundGradient = warning 126 | modView.settings.errorBannerBackgroundGradient = error 127 | return modView 128 | 129 | } 130 | 131 | /// corner radius of the banner background 132 | /// - Parameter radius: corner radius - applies to the bottom corners if displayEdge is top, otherwise radius is applied to the two top corners. 133 | /// - Returns: modified DYNotificationBanner 134 | func cornerRadius(_ radius: CGFloat = 0)->DYNotificationBanner { 135 | var modView = self 136 | modView.settings.cornerRadius = radius 137 | return modView 138 | } 139 | 140 | 141 | /// drop shadow of the banner background 142 | /// - Parameters: 143 | /// - color: shadow color. Default is clear (= no shadow) 144 | /// - radius: radius of the shadow 145 | /// - x: x offset 146 | /// - y: y offset 147 | /// - Returns: modified DYNotificationBanner 148 | func dropShadow(color: Color = .clear, radius: CGFloat = 5, x: CGFloat = 0, y: CGFloat = 5)->DYNotificationBanner { 149 | var shadow: Shadow? 150 | if color != .clear { 151 | shadow = Shadow(color: color, radius: radius, x: x, y: y) 152 | } 153 | var modView = self 154 | modView.settings.dropShadow = shadow 155 | return modView 156 | } 157 | 158 | 159 | 160 | } 161 | 162 | struct DYNotificationBanner_Previews: PreviewProvider { 163 | static var previews: some View { 164 | GeometryReader { proxy in 165 | VStack { 166 | 167 | DYNotificationBanner(notification: DYNotification(title: "Cool Lorem Ipsum", message: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", image: Image(systemName: "checkmark.circle"), displayEdge: .top), frameWidth: min(450, proxy.size.width)) 168 | 169 | } 170 | .edgesIgnoringSafeArea(.all) 171 | } 172 | } 173 | } 174 | 175 | #if canImport(AppKit) 176 | extension NSWindow { 177 | var titlebarHeight: CGFloat { 178 | frame.height - contentRect(forFrameRect: frame).height 179 | } 180 | } 181 | #endif 182 | 183 | 184 | -------------------------------------------------------------------------------- /SwiftUI_NotificationBanner_Example/SwiftUI_NotificationBanner_Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B479B7D0291DFB6F00C55DCC /* SwiftUI_NotificationBanner_ExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B479B7CF291DFB6F00C55DCC /* SwiftUI_NotificationBanner_ExampleApp.swift */; }; 11 | B479B7D2291DFB6F00C55DCC /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B479B7D1291DFB6F00C55DCC /* RootView.swift */; }; 12 | B479B7D4291DFB7200C55DCC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B479B7D3291DFB7200C55DCC /* Assets.xcassets */; }; 13 | B479B7D7291DFB7200C55DCC /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B479B7D6291DFB7200C55DCC /* Preview Assets.xcassets */; }; 14 | B479B7E1291DFB9E00C55DCC /* SwiftUI_NotificationBanner in Frameworks */ = {isa = PBXBuildFile; productRef = B479B7E0291DFB9E00C55DCC /* SwiftUI_NotificationBanner */; }; 15 | B479B7E6292224FA00C55DCC /* NotificationRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B479B7E5292224FA00C55DCC /* NotificationRootView.swift */; }; 16 | B479B7E829223B7500C55DCC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B479B7E729223B7500C55DCC /* AppDelegate.swift */; }; 17 | B479B7EA29223BB200C55DCC /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B479B7E929223BB200C55DCC /* SceneDelegate.swift */; }; 18 | B4BDB1482A541B4200558A3E /* SwiftUI_NotificationBanner_Example_macOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4BDB1472A541B4200558A3E /* SwiftUI_NotificationBanner_Example_macOSApp.swift */; }; 19 | B4BDB14A2A541B4200558A3E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4BDB1492A541B4200558A3E /* ContentView.swift */; }; 20 | B4BDB14C2A541B4400558A3E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B4BDB14B2A541B4400558A3E /* Assets.xcassets */; }; 21 | B4BDB14F2A541B4400558A3E /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B4BDB14E2A541B4400558A3E /* Preview Assets.xcassets */; }; 22 | B4BDB1702A5665EC00558A3E /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B479B7D1291DFB6F00C55DCC /* RootView.swift */; }; 23 | B4BDB1742A56672100558A3E /* SwiftUI_NotificationBanner in Frameworks */ = {isa = PBXBuildFile; productRef = B4BDB1732A56672100558A3E /* SwiftUI_NotificationBanner */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | B479B7CC291DFB6F00C55DCC /* SwiftUI_NotificationBanner_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftUI_NotificationBanner_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | B479B7CF291DFB6F00C55DCC /* SwiftUI_NotificationBanner_ExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUI_NotificationBanner_ExampleApp.swift; sourceTree = ""; }; 29 | B479B7D1291DFB6F00C55DCC /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = ""; }; 30 | B479B7D3291DFB7200C55DCC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 31 | B479B7D6291DFB7200C55DCC /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 32 | B479B7DE291DFB9200C55DCC /* SwiftUI_NotificationBanner */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = SwiftUI_NotificationBanner; path = ..; sourceTree = ""; }; 33 | B479B7E5292224FA00C55DCC /* NotificationRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRootView.swift; sourceTree = ""; }; 34 | B479B7E729223B7500C55DCC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 35 | B479B7E929223BB200C55DCC /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 36 | B4BDB1452A541B4200558A3E /* SwiftUI_NotificationBanner_Example_macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftUI_NotificationBanner_Example_macOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; 37 | B4BDB1472A541B4200558A3E /* SwiftUI_NotificationBanner_Example_macOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUI_NotificationBanner_Example_macOSApp.swift; sourceTree = ""; }; 38 | B4BDB1492A541B4200558A3E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 39 | B4BDB14B2A541B4400558A3E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 40 | B4BDB14E2A541B4400558A3E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 41 | B4BDB1502A541B4400558A3E /* SwiftUI_NotificationBanner_Example_macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftUI_NotificationBanner_Example_macOS.entitlements; sourceTree = ""; }; 42 | B4BDB1592A541B4500558A3E /* SwiftUI_NotificationBanner_Example_macOSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUI_NotificationBanner_Example_macOSTests.swift; sourceTree = ""; }; 43 | B4BDB1632A541B4500558A3E /* SwiftUI_NotificationBanner_Example_macOSUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUI_NotificationBanner_Example_macOSUITests.swift; sourceTree = ""; }; 44 | B4BDB1652A541B4500558A3E /* SwiftUI_NotificationBanner_Example_macOSUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUI_NotificationBanner_Example_macOSUITestsLaunchTests.swift; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | B479B7C9291DFB6F00C55DCC /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | B479B7E1291DFB9E00C55DCC /* SwiftUI_NotificationBanner in Frameworks */, 53 | ); 54 | runOnlyForDeploymentPostprocessing = 0; 55 | }; 56 | B4BDB1422A541B4200558A3E /* Frameworks */ = { 57 | isa = PBXFrameworksBuildPhase; 58 | buildActionMask = 2147483647; 59 | files = ( 60 | B4BDB1742A56672100558A3E /* SwiftUI_NotificationBanner in Frameworks */, 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | /* End PBXFrameworksBuildPhase section */ 65 | 66 | /* Begin PBXGroup section */ 67 | B479B7C3291DFB6F00C55DCC = { 68 | isa = PBXGroup; 69 | children = ( 70 | B479B7DD291DFB9200C55DCC /* Packages */, 71 | B479B7CE291DFB6F00C55DCC /* SwiftUI_NotificationBanner_Example */, 72 | B4BDB1462A541B4200558A3E /* SwiftUI_NotificationBanner_Example_macOS */, 73 | B4BDB1582A541B4500558A3E /* SwiftUI_NotificationBanner_Example_macOSTests */, 74 | B4BDB1622A541B4500558A3E /* SwiftUI_NotificationBanner_Example_macOSUITests */, 75 | B479B7CD291DFB6F00C55DCC /* Products */, 76 | B479B7DF291DFB9E00C55DCC /* Frameworks */, 77 | ); 78 | sourceTree = ""; 79 | }; 80 | B479B7CD291DFB6F00C55DCC /* Products */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | B479B7CC291DFB6F00C55DCC /* SwiftUI_NotificationBanner_Example.app */, 84 | B4BDB1452A541B4200558A3E /* SwiftUI_NotificationBanner_Example_macOS.app */, 85 | ); 86 | name = Products; 87 | sourceTree = ""; 88 | }; 89 | B479B7CE291DFB6F00C55DCC /* SwiftUI_NotificationBanner_Example */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | B479B7CF291DFB6F00C55DCC /* SwiftUI_NotificationBanner_ExampleApp.swift */, 93 | B479B7E729223B7500C55DCC /* AppDelegate.swift */, 94 | B479B7E929223BB200C55DCC /* SceneDelegate.swift */, 95 | B479B7E5292224FA00C55DCC /* NotificationRootView.swift */, 96 | B479B7D1291DFB6F00C55DCC /* RootView.swift */, 97 | B479B7D3291DFB7200C55DCC /* Assets.xcassets */, 98 | B479B7D5291DFB7200C55DCC /* Preview Content */, 99 | ); 100 | path = SwiftUI_NotificationBanner_Example; 101 | sourceTree = ""; 102 | }; 103 | B479B7D5291DFB7200C55DCC /* Preview Content */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | B479B7D6291DFB7200C55DCC /* Preview Assets.xcassets */, 107 | ); 108 | path = "Preview Content"; 109 | sourceTree = ""; 110 | }; 111 | B479B7DD291DFB9200C55DCC /* Packages */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | B479B7DE291DFB9200C55DCC /* SwiftUI_NotificationBanner */, 115 | ); 116 | name = Packages; 117 | sourceTree = ""; 118 | }; 119 | B479B7DF291DFB9E00C55DCC /* Frameworks */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | ); 123 | name = Frameworks; 124 | sourceTree = ""; 125 | }; 126 | B4BDB1462A541B4200558A3E /* SwiftUI_NotificationBanner_Example_macOS */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | B4BDB1472A541B4200558A3E /* SwiftUI_NotificationBanner_Example_macOSApp.swift */, 130 | B4BDB1492A541B4200558A3E /* ContentView.swift */, 131 | B4BDB14B2A541B4400558A3E /* Assets.xcassets */, 132 | B4BDB1502A541B4400558A3E /* SwiftUI_NotificationBanner_Example_macOS.entitlements */, 133 | B4BDB14D2A541B4400558A3E /* Preview Content */, 134 | ); 135 | path = SwiftUI_NotificationBanner_Example_macOS; 136 | sourceTree = ""; 137 | }; 138 | B4BDB14D2A541B4400558A3E /* Preview Content */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | B4BDB14E2A541B4400558A3E /* Preview Assets.xcassets */, 142 | ); 143 | path = "Preview Content"; 144 | sourceTree = ""; 145 | }; 146 | B4BDB1582A541B4500558A3E /* SwiftUI_NotificationBanner_Example_macOSTests */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | B4BDB1592A541B4500558A3E /* SwiftUI_NotificationBanner_Example_macOSTests.swift */, 150 | ); 151 | path = SwiftUI_NotificationBanner_Example_macOSTests; 152 | sourceTree = ""; 153 | }; 154 | B4BDB1622A541B4500558A3E /* SwiftUI_NotificationBanner_Example_macOSUITests */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | B4BDB1632A541B4500558A3E /* SwiftUI_NotificationBanner_Example_macOSUITests.swift */, 158 | B4BDB1652A541B4500558A3E /* SwiftUI_NotificationBanner_Example_macOSUITestsLaunchTests.swift */, 159 | ); 160 | path = SwiftUI_NotificationBanner_Example_macOSUITests; 161 | sourceTree = ""; 162 | }; 163 | /* End PBXGroup section */ 164 | 165 | /* Begin PBXNativeTarget section */ 166 | B479B7CB291DFB6F00C55DCC /* SwiftUI_NotificationBanner_Example */ = { 167 | isa = PBXNativeTarget; 168 | buildConfigurationList = B479B7DA291DFB7200C55DCC /* Build configuration list for PBXNativeTarget "SwiftUI_NotificationBanner_Example" */; 169 | buildPhases = ( 170 | B479B7C8291DFB6F00C55DCC /* Sources */, 171 | B479B7C9291DFB6F00C55DCC /* Frameworks */, 172 | B479B7CA291DFB6F00C55DCC /* Resources */, 173 | ); 174 | buildRules = ( 175 | ); 176 | dependencies = ( 177 | ); 178 | name = SwiftUI_NotificationBanner_Example; 179 | packageProductDependencies = ( 180 | B479B7E0291DFB9E00C55DCC /* SwiftUI_NotificationBanner */, 181 | ); 182 | productName = SwiftUI_NotificationBanner_Example; 183 | productReference = B479B7CC291DFB6F00C55DCC /* SwiftUI_NotificationBanner_Example.app */; 184 | productType = "com.apple.product-type.application"; 185 | }; 186 | B4BDB1442A541B4200558A3E /* SwiftUI_NotificationBanner_Example_macOS */ = { 187 | isa = PBXNativeTarget; 188 | buildConfigurationList = B4BDB16D2A541B4500558A3E /* Build configuration list for PBXNativeTarget "SwiftUI_NotificationBanner_Example_macOS" */; 189 | buildPhases = ( 190 | B4BDB1412A541B4200558A3E /* Sources */, 191 | B4BDB1422A541B4200558A3E /* Frameworks */, 192 | B4BDB1432A541B4200558A3E /* Resources */, 193 | ); 194 | buildRules = ( 195 | ); 196 | dependencies = ( 197 | ); 198 | name = SwiftUI_NotificationBanner_Example_macOS; 199 | packageProductDependencies = ( 200 | B4BDB1732A56672100558A3E /* SwiftUI_NotificationBanner */, 201 | ); 202 | productName = SwiftUI_NotificationBanner_Example_macOS; 203 | productReference = B4BDB1452A541B4200558A3E /* SwiftUI_NotificationBanner_Example_macOS.app */; 204 | productType = "com.apple.product-type.application"; 205 | }; 206 | /* End PBXNativeTarget section */ 207 | 208 | /* Begin PBXProject section */ 209 | B479B7C4291DFB6F00C55DCC /* Project object */ = { 210 | isa = PBXProject; 211 | attributes = { 212 | BuildIndependentTargetsInParallel = 1; 213 | LastSwiftUpdateCheck = 1430; 214 | LastUpgradeCheck = 1400; 215 | TargetAttributes = { 216 | B479B7CB291DFB6F00C55DCC = { 217 | CreatedOnToolsVersion = 14.0.1; 218 | }; 219 | B4BDB1442A541B4200558A3E = { 220 | CreatedOnToolsVersion = 14.3; 221 | }; 222 | }; 223 | }; 224 | buildConfigurationList = B479B7C7291DFB6F00C55DCC /* Build configuration list for PBXProject "SwiftUI_NotificationBanner_Example" */; 225 | compatibilityVersion = "Xcode 14.0"; 226 | developmentRegion = en; 227 | hasScannedForEncodings = 0; 228 | knownRegions = ( 229 | en, 230 | Base, 231 | ); 232 | mainGroup = B479B7C3291DFB6F00C55DCC; 233 | productRefGroup = B479B7CD291DFB6F00C55DCC /* Products */; 234 | projectDirPath = ""; 235 | projectRoot = ""; 236 | targets = ( 237 | B479B7CB291DFB6F00C55DCC /* SwiftUI_NotificationBanner_Example */, 238 | B4BDB1442A541B4200558A3E /* SwiftUI_NotificationBanner_Example_macOS */, 239 | ); 240 | }; 241 | /* End PBXProject section */ 242 | 243 | /* Begin PBXResourcesBuildPhase section */ 244 | B479B7CA291DFB6F00C55DCC /* Resources */ = { 245 | isa = PBXResourcesBuildPhase; 246 | buildActionMask = 2147483647; 247 | files = ( 248 | B479B7D7291DFB7200C55DCC /* Preview Assets.xcassets in Resources */, 249 | B479B7D4291DFB7200C55DCC /* Assets.xcassets in Resources */, 250 | ); 251 | runOnlyForDeploymentPostprocessing = 0; 252 | }; 253 | B4BDB1432A541B4200558A3E /* Resources */ = { 254 | isa = PBXResourcesBuildPhase; 255 | buildActionMask = 2147483647; 256 | files = ( 257 | B4BDB14F2A541B4400558A3E /* Preview Assets.xcassets in Resources */, 258 | B4BDB14C2A541B4400558A3E /* Assets.xcassets in Resources */, 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | }; 262 | /* End PBXResourcesBuildPhase section */ 263 | 264 | /* Begin PBXSourcesBuildPhase section */ 265 | B479B7C8291DFB6F00C55DCC /* Sources */ = { 266 | isa = PBXSourcesBuildPhase; 267 | buildActionMask = 2147483647; 268 | files = ( 269 | B479B7EA29223BB200C55DCC /* SceneDelegate.swift in Sources */, 270 | B479B7E829223B7500C55DCC /* AppDelegate.swift in Sources */, 271 | B479B7E6292224FA00C55DCC /* NotificationRootView.swift in Sources */, 272 | B479B7D2291DFB6F00C55DCC /* RootView.swift in Sources */, 273 | B479B7D0291DFB6F00C55DCC /* SwiftUI_NotificationBanner_ExampleApp.swift in Sources */, 274 | ); 275 | runOnlyForDeploymentPostprocessing = 0; 276 | }; 277 | B4BDB1412A541B4200558A3E /* Sources */ = { 278 | isa = PBXSourcesBuildPhase; 279 | buildActionMask = 2147483647; 280 | files = ( 281 | B4BDB14A2A541B4200558A3E /* ContentView.swift in Sources */, 282 | B4BDB1482A541B4200558A3E /* SwiftUI_NotificationBanner_Example_macOSApp.swift in Sources */, 283 | B4BDB1702A5665EC00558A3E /* RootView.swift in Sources */, 284 | ); 285 | runOnlyForDeploymentPostprocessing = 0; 286 | }; 287 | /* End PBXSourcesBuildPhase section */ 288 | 289 | /* Begin XCBuildConfiguration section */ 290 | B479B7D8291DFB7200C55DCC /* Debug */ = { 291 | isa = XCBuildConfiguration; 292 | buildSettings = { 293 | ALWAYS_SEARCH_USER_PATHS = NO; 294 | CLANG_ANALYZER_NONNULL = YES; 295 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 296 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 297 | CLANG_ENABLE_MODULES = YES; 298 | CLANG_ENABLE_OBJC_ARC = YES; 299 | CLANG_ENABLE_OBJC_WEAK = YES; 300 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 301 | CLANG_WARN_BOOL_CONVERSION = YES; 302 | CLANG_WARN_COMMA = YES; 303 | CLANG_WARN_CONSTANT_CONVERSION = YES; 304 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 305 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 306 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 307 | CLANG_WARN_EMPTY_BODY = YES; 308 | CLANG_WARN_ENUM_CONVERSION = YES; 309 | CLANG_WARN_INFINITE_RECURSION = YES; 310 | CLANG_WARN_INT_CONVERSION = YES; 311 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 312 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 313 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 314 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 315 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 316 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 317 | CLANG_WARN_STRICT_PROTOTYPES = YES; 318 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 319 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 320 | CLANG_WARN_UNREACHABLE_CODE = YES; 321 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 322 | COPY_PHASE_STRIP = NO; 323 | DEBUG_INFORMATION_FORMAT = dwarf; 324 | ENABLE_STRICT_OBJC_MSGSEND = YES; 325 | ENABLE_TESTABILITY = YES; 326 | GCC_C_LANGUAGE_STANDARD = gnu11; 327 | GCC_DYNAMIC_NO_PIC = NO; 328 | GCC_NO_COMMON_BLOCKS = YES; 329 | GCC_OPTIMIZATION_LEVEL = 0; 330 | GCC_PREPROCESSOR_DEFINITIONS = ( 331 | "DEBUG=1", 332 | "$(inherited)", 333 | ); 334 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 335 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 336 | GCC_WARN_UNDECLARED_SELECTOR = YES; 337 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 338 | GCC_WARN_UNUSED_FUNCTION = YES; 339 | GCC_WARN_UNUSED_VARIABLE = YES; 340 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 341 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 342 | MTL_FAST_MATH = YES; 343 | ONLY_ACTIVE_ARCH = YES; 344 | SDKROOT = iphoneos; 345 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 346 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 347 | }; 348 | name = Debug; 349 | }; 350 | B479B7D9291DFB7200C55DCC /* Release */ = { 351 | isa = XCBuildConfiguration; 352 | buildSettings = { 353 | ALWAYS_SEARCH_USER_PATHS = NO; 354 | CLANG_ANALYZER_NONNULL = YES; 355 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 356 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 357 | CLANG_ENABLE_MODULES = YES; 358 | CLANG_ENABLE_OBJC_ARC = YES; 359 | CLANG_ENABLE_OBJC_WEAK = YES; 360 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 361 | CLANG_WARN_BOOL_CONVERSION = YES; 362 | CLANG_WARN_COMMA = YES; 363 | CLANG_WARN_CONSTANT_CONVERSION = YES; 364 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 365 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 366 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 367 | CLANG_WARN_EMPTY_BODY = YES; 368 | CLANG_WARN_ENUM_CONVERSION = YES; 369 | CLANG_WARN_INFINITE_RECURSION = YES; 370 | CLANG_WARN_INT_CONVERSION = YES; 371 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 372 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 373 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 374 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 375 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 376 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 377 | CLANG_WARN_STRICT_PROTOTYPES = YES; 378 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 379 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 380 | CLANG_WARN_UNREACHABLE_CODE = YES; 381 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 382 | COPY_PHASE_STRIP = NO; 383 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 384 | ENABLE_NS_ASSERTIONS = NO; 385 | ENABLE_STRICT_OBJC_MSGSEND = YES; 386 | GCC_C_LANGUAGE_STANDARD = gnu11; 387 | GCC_NO_COMMON_BLOCKS = YES; 388 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 389 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 390 | GCC_WARN_UNDECLARED_SELECTOR = YES; 391 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 392 | GCC_WARN_UNUSED_FUNCTION = YES; 393 | GCC_WARN_UNUSED_VARIABLE = YES; 394 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 395 | MTL_ENABLE_DEBUG_INFO = NO; 396 | MTL_FAST_MATH = YES; 397 | SDKROOT = iphoneos; 398 | SWIFT_COMPILATION_MODE = wholemodule; 399 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 400 | VALIDATE_PRODUCT = YES; 401 | }; 402 | name = Release; 403 | }; 404 | B479B7DB291DFB7200C55DCC /* Debug */ = { 405 | isa = XCBuildConfiguration; 406 | buildSettings = { 407 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 408 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 409 | CODE_SIGN_STYLE = Automatic; 410 | CURRENT_PROJECT_VERSION = 1; 411 | DEVELOPMENT_ASSET_PATHS = "\"SwiftUI_NotificationBanner_Example/Preview Content\""; 412 | DEVELOPMENT_TEAM = 76FXG652LY; 413 | ENABLE_PREVIEWS = YES; 414 | GENERATE_INFOPLIST_FILE = YES; 415 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 416 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 417 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 418 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 419 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 420 | LD_RUNPATH_SEARCH_PATHS = ( 421 | "$(inherited)", 422 | "@executable_path/Frameworks", 423 | ); 424 | MARKETING_VERSION = 1.0; 425 | PRODUCT_BUNDLE_IDENTIFIER = "me.duoyun.SwiftUI-NotificationBanner-Example"; 426 | PRODUCT_NAME = "$(TARGET_NAME)"; 427 | SWIFT_EMIT_LOC_STRINGS = YES; 428 | SWIFT_VERSION = 5.0; 429 | TARGETED_DEVICE_FAMILY = "1,2"; 430 | }; 431 | name = Debug; 432 | }; 433 | B479B7DC291DFB7200C55DCC /* Release */ = { 434 | isa = XCBuildConfiguration; 435 | buildSettings = { 436 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 437 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 438 | CODE_SIGN_STYLE = Automatic; 439 | CURRENT_PROJECT_VERSION = 1; 440 | DEVELOPMENT_ASSET_PATHS = "\"SwiftUI_NotificationBanner_Example/Preview Content\""; 441 | DEVELOPMENT_TEAM = 76FXG652LY; 442 | ENABLE_PREVIEWS = YES; 443 | GENERATE_INFOPLIST_FILE = YES; 444 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 445 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 446 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 447 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 448 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 449 | LD_RUNPATH_SEARCH_PATHS = ( 450 | "$(inherited)", 451 | "@executable_path/Frameworks", 452 | ); 453 | MARKETING_VERSION = 1.0; 454 | PRODUCT_BUNDLE_IDENTIFIER = "me.duoyun.SwiftUI-NotificationBanner-Example"; 455 | PRODUCT_NAME = "$(TARGET_NAME)"; 456 | SWIFT_EMIT_LOC_STRINGS = YES; 457 | SWIFT_VERSION = 5.0; 458 | TARGETED_DEVICE_FAMILY = "1,2"; 459 | }; 460 | name = Release; 461 | }; 462 | B4BDB1672A541B4500558A3E /* Debug */ = { 463 | isa = XCBuildConfiguration; 464 | buildSettings = { 465 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 466 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 467 | CODE_SIGN_ENTITLEMENTS = SwiftUI_NotificationBanner_Example_macOS/SwiftUI_NotificationBanner_Example_macOS.entitlements; 468 | CODE_SIGN_STYLE = Automatic; 469 | COMBINE_HIDPI_IMAGES = YES; 470 | CURRENT_PROJECT_VERSION = 1; 471 | DEVELOPMENT_ASSET_PATHS = "\"SwiftUI_NotificationBanner_Example_macOS/Preview Content\""; 472 | DEVELOPMENT_TEAM = 76FXG652LY; 473 | ENABLE_HARDENED_RUNTIME = YES; 474 | ENABLE_PREVIEWS = YES; 475 | GENERATE_INFOPLIST_FILE = YES; 476 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 477 | LD_RUNPATH_SEARCH_PATHS = ( 478 | "$(inherited)", 479 | "@executable_path/../Frameworks", 480 | ); 481 | MACOSX_DEPLOYMENT_TARGET = 13.2; 482 | MARKETING_VERSION = 1.0; 483 | PRODUCT_BUNDLE_IDENTIFIER = "me.duoyun.SwiftUI-NotificationBanner-Example-macOS"; 484 | PRODUCT_NAME = "$(TARGET_NAME)"; 485 | SDKROOT = macosx; 486 | SWIFT_EMIT_LOC_STRINGS = YES; 487 | SWIFT_VERSION = 5.0; 488 | }; 489 | name = Debug; 490 | }; 491 | B4BDB1682A541B4500558A3E /* Release */ = { 492 | isa = XCBuildConfiguration; 493 | buildSettings = { 494 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 495 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 496 | CODE_SIGN_ENTITLEMENTS = SwiftUI_NotificationBanner_Example_macOS/SwiftUI_NotificationBanner_Example_macOS.entitlements; 497 | CODE_SIGN_STYLE = Automatic; 498 | COMBINE_HIDPI_IMAGES = YES; 499 | CURRENT_PROJECT_VERSION = 1; 500 | DEVELOPMENT_ASSET_PATHS = "\"SwiftUI_NotificationBanner_Example_macOS/Preview Content\""; 501 | DEVELOPMENT_TEAM = 76FXG652LY; 502 | ENABLE_HARDENED_RUNTIME = YES; 503 | ENABLE_PREVIEWS = YES; 504 | GENERATE_INFOPLIST_FILE = YES; 505 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 506 | LD_RUNPATH_SEARCH_PATHS = ( 507 | "$(inherited)", 508 | "@executable_path/../Frameworks", 509 | ); 510 | MACOSX_DEPLOYMENT_TARGET = 13.2; 511 | MARKETING_VERSION = 1.0; 512 | PRODUCT_BUNDLE_IDENTIFIER = "me.duoyun.SwiftUI-NotificationBanner-Example-macOS"; 513 | PRODUCT_NAME = "$(TARGET_NAME)"; 514 | SDKROOT = macosx; 515 | SWIFT_EMIT_LOC_STRINGS = YES; 516 | SWIFT_VERSION = 5.0; 517 | }; 518 | name = Release; 519 | }; 520 | /* End XCBuildConfiguration section */ 521 | 522 | /* Begin XCConfigurationList section */ 523 | B479B7C7291DFB6F00C55DCC /* Build configuration list for PBXProject "SwiftUI_NotificationBanner_Example" */ = { 524 | isa = XCConfigurationList; 525 | buildConfigurations = ( 526 | B479B7D8291DFB7200C55DCC /* Debug */, 527 | B479B7D9291DFB7200C55DCC /* Release */, 528 | ); 529 | defaultConfigurationIsVisible = 0; 530 | defaultConfigurationName = Release; 531 | }; 532 | B479B7DA291DFB7200C55DCC /* Build configuration list for PBXNativeTarget "SwiftUI_NotificationBanner_Example" */ = { 533 | isa = XCConfigurationList; 534 | buildConfigurations = ( 535 | B479B7DB291DFB7200C55DCC /* Debug */, 536 | B479B7DC291DFB7200C55DCC /* Release */, 537 | ); 538 | defaultConfigurationIsVisible = 0; 539 | defaultConfigurationName = Release; 540 | }; 541 | B4BDB16D2A541B4500558A3E /* Build configuration list for PBXNativeTarget "SwiftUI_NotificationBanner_Example_macOS" */ = { 542 | isa = XCConfigurationList; 543 | buildConfigurations = ( 544 | B4BDB1672A541B4500558A3E /* Debug */, 545 | B4BDB1682A541B4500558A3E /* Release */, 546 | ); 547 | defaultConfigurationIsVisible = 0; 548 | defaultConfigurationName = Release; 549 | }; 550 | /* End XCConfigurationList section */ 551 | 552 | /* Begin XCSwiftPackageProductDependency section */ 553 | B479B7E0291DFB9E00C55DCC /* SwiftUI_NotificationBanner */ = { 554 | isa = XCSwiftPackageProductDependency; 555 | productName = SwiftUI_NotificationBanner; 556 | }; 557 | B4BDB1732A56672100558A3E /* SwiftUI_NotificationBanner */ = { 558 | isa = XCSwiftPackageProductDependency; 559 | productName = SwiftUI_NotificationBanner; 560 | }; 561 | /* End XCSwiftPackageProductDependency section */ 562 | }; 563 | rootObject = B479B7C4291DFB6F00C55DCC /* Project object */; 564 | } 565 | --------------------------------------------------------------------------------