{"id":18867509,"url":"https://github.com/leekurg/popupkit","last_synced_at":"2026-04-29T09:36:56.690Z","repository":{"id":256964037,"uuid":"836458714","full_name":"leekurg/PopupKit","owner":"leekurg","description":"Presentation tools for SwiftUI app","archived":false,"fork":false,"pushed_at":"2026-02-20T14:46:18.000Z","size":165,"stargazers_count":2,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-20T18:56:48.944Z","etag":null,"topics":["alert","cover","fullscreen","ios15","navigation","popup","presentation","sheet","swiftui"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/leekurg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-07-31T22:38:21.000Z","updated_at":"2025-02-05T09:51:13.000Z","dependencies_parsed_at":"2024-10-26T17:47:25.581Z","dependency_job_id":null,"html_url":"https://github.com/leekurg/PopupKit","commit_stats":null,"previous_names":["leekurg/popupkit"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/leekurg/PopupKit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leekurg%2FPopupKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leekurg%2FPopupKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leekurg%2FPopupKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leekurg%2FPopupKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/leekurg","download_url":"https://codeload.github.com/leekurg/PopupKit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leekurg%2FPopupKit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32420350,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-29T06:29:02.080Z","status":"ssl_error","status_checked_at":"2026-04-29T06:29:00.631Z","response_time":110,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["alert","cover","fullscreen","ios15","navigation","popup","presentation","sheet","swiftui"],"created_at":"2024-11-08T05:09:36.708Z","updated_at":"2026-04-29T09:36:56.672Z","avatar_url":"https://github.com/leekurg.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PopupKit\n\n`PopupKit` is a tool designed for enhanced view presentation within a `SwiftUI` app.\n\n\u003e [!WARNING]\n\u003e With the public release of `iOS 18.0`, Apple modified the internal behavior of `UIView.hitTest()`, which has\n\u003e affected the `UIWindow-layering` pattern that `PopupKit` relies on. \n\u003e As a result, `PopupKit`'s interactive `cover` presentation no longer function as expected. \n\u003e Currently, they block all user interactions with the content underneath while they are presented.\n\u003e I'm working on the solution.\n\n## Compatibility\n`SwiftUI` application targeted to **iOS 15+**.\n\n## Motivation\nI have a passion for `SwiftUI` and use it daily for work. While I appreciate its design, \nsome components — especially the presentation APIs — still (**iOS 18**) lack the flexibility developers \noften require. `PopupKit` is my attempt to bridge these gaps while respecting `SwiftUI` design principles, but \nwith added freedom and flexibility where needed.\n\n## What is PopupKit?\n`PopupKit` offers several useful and fully-customizable view presentation methods that can be useful \nin app development:\n\n\n\u003ctable\u003e\n    \u003ctbody\u003e\n        \u003ctr\u003e\n            \u003ctd\u003e \u003cp align=\"center\"\u003e \u003cstrong\u003eNotification\u003c/strong\u003e \u003c/p\u003e \u003c/td\u003e\n            \u003ctd\u003e \u003cp align=\"center\"\u003e \u003cstrong\u003eCover\u003c/strong\u003e \u003c/p\u003e \u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003e\n              \u003cimg src=\"https://github.com/user-attachments/assets/1bb247e7-83b0-44b4-b4ca-a103aaf1af14\" width=\"250\"\u003e\n            \u003c/td\u003e\n            \u003ctd\u003e\n              \u003cimg src=\"https://github.com/user-attachments/assets/ed50b67a-6ca6-4f4e-bec2-8a09a221e44c\" width=\"250\"\u003e\n            \u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003e \u003cp align=\"center\"\u003e \u003cstrong\u003eConfirm\u003c/strong\u003e \u003c/p\u003e \u003c/td\u003e\n            \u003ctd\u003e \u003cp align=\"center\"\u003e \u003cstrong\u003eFullscreen\u003c/strong\u003e \u003c/p\u003e \u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003e\n              \u003cimg src=\"https://github.com/user-attachments/assets/6f44e6db-ff56-4b22-826e-4f1e86511ba6\" width=\"250\"\u003e\n            \u003c/td\u003e\n            \u003ctd\u003e\n              \u003cimg src=\"https://github.com/user-attachments/assets/1d110614-c162-4431-9bae-4d460932159b\" width=\"250\"\u003e\n            \u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003e \u003cp align=\"center\"\u003e \u003cstrong\u003ePopup\u003c/strong\u003e \u003c/p\u003e \u003c/td\u003e\n            \u003ctd\u003e \u003cp align=\"center\"\u003e \u003cstrong\u003eAlert\u003c/strong\u003e \u003c/p\u003e \u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003e\n              \u003cimg src=\"https://github.com/user-attachments/assets/9a9321f2-4456-4a32-b29c-14f0f78e37b8\" width=\"250\"\u003e\n            \u003c/td\u003e\n            \u003ctd\u003e\n              \u003cimg src=\"https://github.com/user-attachments/assets/f6c2d950-7863-4be8-9763-823726c9d2f6\" width=\"250\"\u003e\n            \u003c/td\u003e\n        \u003c/tr\u003e\n    \u003c/tbody\u003e\n\u003c/table\u003e\n\n- **Notification**: a popup notification with text or an image styled similarly to a system push notification.\nIt is displayed above the app's view hierarchy.\n  - Customizable transition, appearance animations, and visual style.\n  - Notifications can expire after a set time and automatically dismiss.\n  - Supports user-initiated dismissal by a scroll-away gesture, just like system notifications.\n\n- **Cover**: analogue of the system `.sheet` presentation style with several enhancements:\n  - Customizable transition, appearance animations, and background.\n  - Configurable height (system `.sheet` supports this only since iOS 16).\n  - The cover's anchor point can be placed on any screen edge, not just the bottom.\n  - Flexible modality: allows you to block user interaction with content beneath the cover or with the cover itself(not working in iOS 18).\n\n- **Fullscreen**: analogue of the system `.fullscreenCover` presentation style, but with enhanced customizability of:\n  - Transition and appearance animations\n  - Background\n  - Optional *scroll-down-to-dismiss* gesture for dismissing the current fullscreen view\n \n- **Confirm**: analogue of the system `.confirmationDialog` with several features. Customize:\n  - Transition, appearance animations, and visual style\n  - Header content\n  - Actions appearence\n  - Confirm supports haptic feedback\n \n- **Popup**: popup modal window with ability to be stacked and customize parameters:\n  - Transition, appearance animations, and visual style\n  - Content\n  - Popups are stackable\n \n- **Alert**: modal window similar to system `.alert`. You can customize:\n  - Transition, appearance animations, and visual style\n  - Header content\n  - Actions appearance\n  - Alerts are stackable\n\n## Usage\nAlthough in `SwiftUI` it's possible to display views above your app's view hierarchy, system sheets and fullscreen \ncovers will still overlay these views. To bypass these restrictions and unlock the full potential of `PopupKit`, \nit's necessary to integrate it into the app's lifecycle.\n\n**Federico Zanetello** brilliantly covers the topic of overlaying `SwiftUI` content above the presentation layer in \nhis article, [How to layer multiple windows in SwiftUI](https://www.fivestars.blog/articles/swiftui-windows). `PopupKit` \nleverages the ideas presented in this article, and I would like to extend my thanks to the author for his research.\n\n\u003e [!TIP]\n\u003e Before diving into the integration steps and usage tips, I’d like to highlight that I’ve created an\n\u003e [example project](https://github.com/leekurg/PopupKitExample) showcasing the complete integration\n\u003e of `PopupKit`. This example project includes working demonstrations of all the\n\u003e available features, allowing you to explore and better understand how to implement `PopupKit`'s tools in your project.\n\n### Integration into the app\n\nIntegrating `PopupKit` into your app's lifecycle requires a bit of setup. \nThe basic principle is to configure a chain: **App** → **AppDelegate** → **SceneDelegate** → **View layer**. \nThis creates a second transparent `UIWindow` in your app, configures `PopupKit` presentation layers within it, \nand injects the presenter objects into your view layer.\n\nTo achieve this, follow the steps outlined below:\n\n1. `SceneDelegate` setup\n2. `AppDelegate` setup\n3. `App` struct setup\n4. View layer injections\n\n\n#### 1. `SceneDelegate` Setup\u003ca id='sceneDelegate-setup'\u003e\u003c/a\u003e\nThe first step is to create(if you don't have one already) a dedicated `SceneDelegate` to manage the second `UIWindow`, \nwhich `PopupKit` will use for presentation.\n\n##### Default setup\nIf you are fine with the default settings for transitions, animations, and anchor points, you can use the built-in \n`PopupKitSceneDelegate` class. If your app does not yet have a `SceneDelegate`, use this class directly. \nIf you already have a `SceneDelegate`, inherit from `PopupKitSceneDelegate` and call the superclass method:\n\n\u003cdetails\u003e\u003csummary\u003eCode\u003c/summary\u003e\u003cp\u003e\n\n```swift\nclass YourSceneDelegate: PopupKitSceneDelegate {\n    override func scene(\n        _ scene: UIScene,\n        willConnectTo session: UISceneSession,\n        options connectionOptions: UIScene.ConnectionOptions\n    ) {\n        super.scene(scene, willConnectTo: session, options: connectionOptions)\n        // Your custom code here\n    }\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\nAnd you are good to go to the next step.\n\n##### Advanced setup\nFor a more advanced approach, you can fully customize the presentation behavior by copying \nand modifying the `PopupKitSceneDelegate` code into your own `SceneDelegate`:\n\u003cdetails\u003e\u003csummary\u003eCode\u003c/summary\u003e\u003cp\u003e\n\n```swift\nclass YourSceneDelegate: NSObject, UIWindowSceneDelegate, ObservableObject {\n    private var popupKitWindow: UIWindow?\n\n    public lazy var coverPresenter = CoverPresenter()\n    public lazy var fullscreenPresenter = FullscreenPresenter()\n    public lazy var notificationPresenter = NotificationPresenter()\n    public lazy var confirmPresenter = ConfirmPresenter()\n    public lazy var popupPresenter = PopupPresenter()\n    \n    open func scene(\n        _ scene: UIScene,\n        willConnectTo session: UISceneSession,\n        options connectionOptions: UIScene.ConnectionOptions\n    ) {\n        if let scene = scene as? UIWindowScene {\n            let popupKitWindow = PassThroughUIWindow(windowScene: scene)\n\n            let popupKitViewController = PopupKitHostingController(\n                rootView: Color.clear\n                    .coverRoot()\n                    .ignoresSafeArea(.all, edges: [.all])\n                    .fullscreenRoot()\n                    .notificationRoot()\n                    .confirmRoot()\n                    .popupRoot()\n                    .environmentObject(coverPresenter)\n                    .environmentObject(fullscreenPresenter)\n                    .environmentObject(notificationPresenter)\n                    .environmentObject(confirmPresenter)\n                    .environmentObject(popupPresenter)\n                    .popupActionTint(.blue)\n            )\n\n            popupKitViewController.view.backgroundColor = .clear\n            popupKitWindow.rootViewController = popupKitViewController\n            popupKitWindow.isHidden = false\n            self.popupKitWindow = popupKitWindow\n        }\n    }\n}\n```\n\u003c/p\u003e\u003c/details\u003e\n\nThis code sets up a secondary `UIWindow` that will hold and display the `PopupKit` presentation layers. The  \ncomponents of each presentation layer setup include:\n\n- Presenter: The logical core that manages presenting and dismissing views, and keeps track of the stack.\n- Root: The frame used to display the presented views. \u003ca id='root-view-explanation'\u003e\n- Environment: This connects the presenter to the `SwiftUI` view layer.\n\nFor example, to use a *cover* presentation, you will need to:\n\n- Create a `CoverPresenter` object.\n- Add the `coverRoot()` modifier to the secondary window.\n- Inject the created `CoverPresenter` into the `SwiftUI` environment.\n\n\u003e [!TIP]\n\u003e Each `...Root()` modifier allows for configuration options, such as anchor points, transitions,\n\u003e and animations, which can be tailored to your specific needs. You can also adjust the safe areas using\n\u003e the `.ignoresSafeArea(_)` modifier between `...Root()` calls.\n\nOnce you have set up your presenters and their environment, you're ready to move on to the [next step](#appDelegate-setup).\n\n#### 2. `AppDelegate` setup\u003ca id='appDelegate-setup'\u003e\u003c/a\u003e\n\nOn this step, it is necessary to create (if you don't have one already) a dedicated `AppDelegate` \nto make use of the `SceneDelegate` that you set up in the previous step.\n\nIf you opted for the default setup in the previous step and there is no existing `AppDelegate` in your app, \nyou can proceed to the [next step](#app-struct-setup) with the default `PopupKitAppDelegate` class.\n\nHowever, if you already have an `AppDelegate`, ensure that you are using the `SceneDelegate` you configured in \nthe [earlier step](#sceneDelegate-setup):\n\n\u003cdetails\u003e\u003csummary\u003eCode\u003c/summary\u003e\u003cp\u003e\n\n```swift\nclass YourAppDelegate: NSObject, UIApplicationDelegate {\n    func application(\n        _ application: UIApplication,\n        configurationForConnecting connectingSceneSession: UISceneSession,\n        options: UIScene.ConnectionOptions\n    ) -\u003e UISceneConfiguration {\n        let sceneConfig = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)\n        sceneConfig.delegateClass = YourSceneDelegate.self\n        return sceneConfig\n    }\n}\n```\n\u003c/p\u003e\u003c/details\u003e\nOnce this is done, you are ready to proceed to the [next step](#app-struct-setup).\n\n#### 3. `App` struct setup\u003ca id='app-struct-setup'\u003e\u003c/a\u003e\nOn this step, you need to ensure that you tell to `SwiftUI` to use the configured `AppDelegate` as the delegate \nfor your app. To do this, add (or verify that it's already present) the following line of code in your `App` struct. \nReplace `YourAppDelegate` with the actual `AppDelegate` class you configured in the [previous step](#appDelegate-setup):\n\n```swift\n@main\nstruct YourApp: App {\n    @UIApplicationDelegateAdaptor var adaptor: YourAppDelegate  //this\n\n    var body: some Scene {\n        WindowGroup {\n            // App root view\n        }\n    }\n}\n```\nThis line ensures that your custom `AppDelegate` is properly set as the app’s delegate, allowing it to manage \nlifecycle events and integrate `PopupKit`. Once added, you can proceed to the last integration step.\n\n#### 4. View layer injections\nAt this point, your app's `SceneDelegate` holds a set of presenters responsible for `PopupKit`'s presentation \nfunctionality. Now, you need to inject these presenters into the view hierarchy. To achieve this, create a \ndedicated root view in your `App` struct, such as a `MainSceneView`:\n\n```swift\n@main\nstruct YourApp: App {\n    @UIApplicationDelegateAdaptor var adaptor: YourAppDelegate\n\n    var body: some Scene {\n        WindowGroup {\n            MainSceneView()  //this\n        }\n    }\n}\n```\n\nIn the `MainSceneView`, inject all the necessary `PopupKit` presenters into the `SwiftUI` environment as follows:\n\n```swift\nstruct MainSceneView: View {\n    @EnvironmentObject var sceneDelegate: YourSceneDelegate\n\n    var body: some View {\n        ContentView()\n            .environmentObject(sceneDelegate.coverPresenter)   // Injects the cover presenter\n            .environmentObject(sceneDelegate.fullscreenPresenter)  // Injects the fullscreen presenter\n            .environmentObject(sceneDelegate.notificationPresenter)  // Injects the notification presenter\n            .environmentObject(sceneDelegate.confirmPresenter)  // Injects the confirm presenter\n            .environmentObject(sceneDelegate.popupPresenter)    // Injects the popup presenter\n    }\n}\n```\n\n\u003e [!TIP]\n\u003e Alternatively, you can perform this injection within your `ContentView`, but there is something to keep in mind:\n\u003e 1. Injection requires access to `EnvironmentObject` and`SwiftUI` does not allow such access directly\n\u003e within the `App` struct.\n\u003e 2. Ensure that all presenters are injected *before* any calls to `PopupKit`'s presentation methods are made.\n\n### Presenting a view\nOnce the integration process is complete, `PopupKit` enables you to present views with a variety of tools, similar \nto how system views are presented. You can easily implement these features by adding a `PopupKit` modifier to your \nview, passing a `Binding` variable to control its state, and toggling that `Binding` to trigger the presentation.\n\n\u003cbr/\u003e\n\n#### Fullscreen\nTo present a view in fullscreen mode, you can use the `fullscreen()` modifier. Here's an example:\n\n\u003cdetails\u003e\u003csummary\u003eExample\u003c/summary\u003e\u003cp\u003e\n\n```swift\nstruct YourView: View {\n    @State private var isPresented = false\n    \n    var body: some View {\n        VStack {\n            Button(\"Show PopupKit fullscreen\") {\n                isPresented.toggle()\n            }\n            .fullscreen(\n                isPresented: $isPresented,  // 1. Controls the presentation state\n                background: .ultraThinMaterial,  // 2. Defines the background style\n                ignoresEdges: [.bottom, .leading],  // 3. Specifies which edges to ignore\n                dismissalScroll: .dismiss(predictedThreshold: 300)  // 4. Enables swipe-to-dismiss with threshold\n            ) {\n                Color.red  // Content of the fullscreen view\n            }\n        }\n    }\n}\n```\n\u003c/p\u003e\u003c/details\u003e\n\nLet's break down the key elements of the `fullscreen()` modifier:\n- **Background Customization** (background): You can define the fullscreen background using a `ShapeStyle` of \nyour choice (e.g., `.ultraThinMaterial` for a blur effect).\n- **Safe Area Ignoring** (ignoresEdges): By default, the content respects the safe areas of the device, \nbut you can specify which edges should be ignored if desired.\n- **Swipe-to-Dismiss Gesture** (dismissalScroll): Enable a *swipe-down-to-dismiss* gesture with a customizable \nthreshold, controlling how much scrolling is required to dismiss the fullscreen view.\n\n\u003cbr/\u003e\n\n#### Cover\nTo display a view using a *cover* presentation mode, you can utilize the `cover()` modifier provided by `PopupKit`. \nHere's how you can implement it:\n\n\u003cdetails\u003e\u003csummary\u003eExample\u003c/summary\u003e\u003cp\u003e\n\n```swift\nstruct YourView: View {\n    @State private var isPresented = false\n\n    var body: some View {\n        VStack {\n            Button(\"Show PopupKit Cover\") {\n                isPresented.toggle()\n            }\n            .cover(\n                isPresented: $isPresented,  // 1. Controls the presentation state\n                background: .ultraThinMaterial,  // 2. Defines the background style\n                modal: .modal(interactivity: .interactive),  // 3. Configures modality (interactive/noninteractive or none)\n                cornerRadius: 15  // 4. Sets the corner radius for the cover view\n            ) {\n                Color.red  // 5. Content of the cover view\n            }\n        }\n    }\n}\n```\n\u003c/p\u003e\u003c/details\u003e\n\nKey elements of `cover()` modifier:\n- **Background Customization** (background): You can choose the cover's background style, such as `.ultraThinMaterial` to add a subtle blur effect, or any other `ShapeStyle`.\n- **Modal behavior** (modal):\n  - **Non-modal**: The cover view does not block interaction with other views on the screen.\n  - **Modal-interactive**: A dimmed background appears around the cover, and the cover can be dismissed by \n  tapping the dimmed area or scrolling it down.\n  - **Modal-noninteractive**: Similar to the interactive modal, but the cover cannot be dismissed by tapping \n  outside the cover or scrolling.\n- **Corner Radius** (cornerRadius): You can adjust the corner radius of the cover to create a smooth, rounded edge \nfor the view.\n\nThe content inside the cover view is provided as a trailing closure. The height of the cover is determined by the \ncontent you provide. If the content’s height exceeds the device’s screen height, the cover will occupy the full \nscreen, and its content will align to the top of the screen.\n\n\u003cbr/\u003e\n\n#### Notification\nYou can display a view with a notification presentation style by using the `notification()` view modifier. \nHere’s an example implementation:\n\n\u003cdetails\u003e\u003csummary\u003eExample\u003c/summary\u003e\u003cp\u003e\n\n```swift\nstruct YourView: View {\n    @State private var isPresented = false\n\n    var body: some View {\n        VStack {\n            Button(\"Show PopupKit Notification\") {\n                isPresented.toggle()\n            }\n            .notification(\n                isPresented: $isPresented,  // 1. Controls the presentation state\n                expiration: .timeout(.seconds(2))  // 2. Defines how long the notification remains visible\n            ) {\n                RoundedRectangle(cornerRadius: 15).fill(.yellow)  // Content of the notification view\n            }\n        }\n    }\n}\n```\n\u003c/p\u003e\u003c/details\u003e\n\nKey Elements of `notification()` modifier:\n- **Expiration Time** (expiration): You can set an expiration time using `.timeout()` to specify how long the notification \nremains visible. For instance, `.seconds(2)` means the notification will automatically dismiss after 2 seconds. \nIf no expiration is set, the notification will remain until dismissed manually (e.g., by swiping).\n- **Dismissal behaviour**:\n    - **Manual Dismissal**: All notifications can be manually dismissed by the user with swipe, similar to system push\n    notifications. If no expiration time is set, manual dismissal will be the only method of removal.\n    - **Automatic Dismissal**: When an expiration time is set, the notification will automatically dismiss itself once \n    the timer expires.\n\n\u003e [!TIP]\n\u003e If multiple notifications are presented in sequence, the timer resets when a new notification is shown.\n\u003e For example, if Notification A is still active when Notification B appears, A’s timer will restart when B is\n\u003e dismissed.\n\n\u003cbr/\u003e\n\n#### Confirm\nWhen you need to make user pick one of actions you can use a *confirm* presentation mode, utilizing the `confirm()` modifier provided by `PopupKit`. \nHere's how you can implement it:\n\n\u003cdetails\u003e\u003csummary\u003eExample\u003c/summary\u003e\u003cp\u003e\n\n```swift\nstruct YourView: View {\n    @State private var isPresented = false\n\n    var body: some View {\n        VStack {\n            Button(\"Show PopupKit Cover\") {\n                isPresented.toggle()\n            }\n            .confirm(isPresented: $isPresented) {\n                Text(\"Are you sure?\")\n            } actions: {\n                Regular(\n                    text: Text(\"Maybe not\"),\n                    action: { print(\"Maybe not was picked\") }\n                )\n                Cancel(text: Text(\"Not this time\"))\n                Destructive(\n                    text: Text(\"I am sure\"),\n                    action: { print(\"I am sure was picked\") }\n                )\n            }\n        }\n    }\n}\n```\n\u003c/p\u003e\u003c/details\u003e\n\nKey elements of `confirm()` modifier:\n- **Header Customization** (header): You can use any `View` to present as dialog's header.\n- **ActionBuilder**: Simplified syntax with `@ActionBuilder` for implementig actions.\n- **Auto-sorting**: Order of actions during dialog's presentation is the same as you provides, except the **cancel** actions listed below.\n\nYou can customize actions font appearence using dedicated `EnvironmentValues` through `View` extension functions - `.popupActionTint(_)` and `.popupActionFonts(_)`. Also, a number of parameters can be customized with passing parameters to `.confirmRoot()` call:\n- **background** - background of dialog\n- **cancelBackground** - background of section with *cancel* actions.\n- **cornerRadius** - a corner radius of section with header and *regular* actions and section with *cancel* actions.\n\n\u003e [!NOTE]\n\u003e It is possible to present only one *confirm* at a time, any attempts to present a dialog, while there is presented one, will be ignored.\n\n\u003cbr/\u003e\n\n#### Popup\nTo present some information to user, request text input or some action to pick you can utilize `.popup()` presentation modifier provided by `PopupKit`. \nHere's how you can implement it:\n\n\u003cdetails\u003e\u003csummary\u003eExample\u003c/summary\u003e\u003cp\u003e\n\n```swift\nstruct YourView: View {\n    @State private var isPresented = false\n\n    var body: some View {\n        VStack {\n            Button(\"Show PopupKit Cover\") {\n                isPresented.toggle()\n            }\n            .popup(\n                isPresented: $isPresented,    // 1. Controls the presentation state\n                outTapBehavior: .dismiss,     // 2. Determines behaviour when user tap outside the view\n                ignoresEdges: []              // 3. Ignore specified edges of the safe area\n            ) {\n                PopupView()                   // Content of the popup view\n            }\n        }\n    }\n}\n```\n\u003c/p\u003e\u003c/details\u003e\n\nKey elements of `popup()` modifier:\n- **Presentation Control** (isPresented): A `Binding\u003cBool\u003e` variable controls when the popup is presented or dismissed. Toggling this binding will trigger the presentation state.\n- **Tap Outside Behaviour** (outTapBehavior): Popup could be dismissed on outside tap.\n- **Safe Area Ignoring** (ignoresEdges): By default, the content respects the safe areas of the device, \nbut you can specify which edges should be ignored if desired.\n\n\u003cbr/\u003e\n\n#### Alert\n`popupAlert()` is right tool for you if you want more freedom than system `.alert()` has to offer.\nHere's how you can implement it:\n\n\u003cdetails\u003e\u003csummary\u003eExample\u003c/summary\u003e\u003cp\u003e\n\n```swift\nstruct YourView: View {\n    @State private var isPresented = false\n\n    var body: some View {\n        VStack {\n            Button(\"Show PopupKit Cover\") {\n                isPresented.toggle()\n            }\n            .popupAlert(isPresented: $isPresented) {     // 1. Controls the presentation state\n                YouCustomHeader()                        // 2. Content of the popup view\n            } actions: {\n                Regular(                                 // 3. Actions to offer\n                    text: Text(\"Okt\"),\n                    action: { print(\"Ok\") }\n                )\n            )\n        }\n    }\n}\n```\n\u003c/p\u003e\u003c/details\u003e\n\nKey elements of `popupAlert()` modifier:\n- **Respects safe area insets**\n- **Auto scrollable**: Alert's actions list becames scrollable if height of the screen is not enough.\n- **ActionBuilder**: Simplified syntax with `@ActionBuilder` for implementig actions.\n- **Auto-sorting**: Order of actions during dialog's presentation is the same as you provides, except the **cancel** actions listed below.\n\nActions font can be changed appearence using dedicated `EnvironmentValues` through `View` extension functions - `.popupActionTint(_)` and `.popupActionFonts(_)`.\n\n### Controlling Presentation with `Presenter`\nIn addition to view modifiers, `PopupKit` offers another powerful tool for managing presentations: the `Presenter`. \nEach presentation layer in `PopupKit` has its own `Presenter`, which is injected into the `SwiftUI` environment system\nduring the integration process. `Presenter` acts as the logical core that manages presentation operations, offering a \nset of methods to control presentation flow and access the presentation stack.\n\nKey functions of `Presenter`:\n- `present()`: Triggers a new presentation, adding a view to the top of the presentation stack.\n- `dismiss()`: Dismisses the current (top-most) view on the presentation stack.\n- `popToRoot()`: Dismisses all presented views.\n- and more\n \nEach `Presenter` also maintains a presentation stack, which holds all currently presented entities in the order they \nwere shown. This stack can be checked at any time to determine which view is currently being presented.\n\n#### Debugging\n`PopupKit` provides an easy way to debug presentation behavior using the *verbose* mode on `Presenter`. By enabling \nverbose mode, any changes to the presentation stack are logged in the Xcode console, making it easy to track \npresentation events and troubleshoot any issues.\n\nYou can activate verbose mode when initializing any `Presenter` by passing the `isVerbose` argument in the `Presenter`'s \ninitializer:\n\n```\nlet presenter = CoverPresenter(isVerbose: true)\n```\n\n\u003e[!NOTE]\n\u003e Verbose mode is limited to `DEBUG` builds, ensuring that redundant logs do not appear in `RELEASE` builds.\n\u003e This helps you maintain clean, production-ready logs while benefiting from detailed output during development.\n\n## PopupKit in SwiftUI Preview\n\nDue to its deep integration at a higher app level, unfortunately, `PopupKit` \ndoesn’t fully act as expected within `SwiftUI` `Previews` as it do in simulator or on a device. \nI’ve worked to minimize the inconvenience, but some limitations remain. You can choose one of two options\nwhich is more suitable in your case.\n\n### Disabling `PopupKit` in `Previews`\nIf you don’t need `PopupKit` to work in `Previews`, you can easily disable it. Go to the \n`Package.swift` file in `PopupKit` package and uncomment the section that includes the line:\n\n```swift\n.define(«DISABLE_POPUPKIT_IN_PREVIEWS»)\n```\n\nThis will completely disable `PopupKit` from running in `Previews` and (as desribed below) will free you from writing\na bolierplate code.\n\n### Enabling `PopupKit` in `Previews`\n\nIf you **do want to preview** `PopupKit` presentation methods, use the following modifiers in \nyour previews:\n- previewPopupKit(_)\n- previewPopupKit(ignoresSafeAreaEdges)\n\nThese modifiers should be applied in every preview macro or `PreviewProvider` where `PopupKit` \nis expected to function. It's important to attach these modifiers as high as possible in the \nview hierarchy. The view you attach the modifier to is treated as the [*root view*](#root-view-explanation), \nso make sure this root view occupies the full screen — otherwise, the presentation views won’t behave as expected.\n\n## Install\n`SPM` installation: in Xcode tap **File → Add packages…**, paste is search field the URL of this page and \npress **Add package**. After that, you should complete the [integration](#integration-into-the-app).\n\n## Known issues\n❌ `NavigationStack` is not working inside a `cover`.\\\n❌ `NavigationStack` is not working inside a dismissable `fullscreen`. Fullscreen with `DismissalScroll.none` is fine.\\\n❌ Interactive covers is not letting you to interact with the content beneath on `iOS 18`.\n\n## Roadmap\n- [x] Notification\n- [x] Cover\n- [x] Fullscreen\n- [x] Confirmation dialog\n- [x] Popup\n- [x] Alert\n- [ ] Confirm support for album orientation\n- [ ] Fix [known issues](#known-issues)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleekurg%2Fpopupkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleekurg%2Fpopupkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleekurg%2Fpopupkit/lists"}