A library to present highly-customizable popovers.

Overview

Header Image

Popovers

A library to present popovers.

  • Present any view above your app's main content.
  • Attach to source views or use picture-in-picture positioning.
  • Supports multiple popovers at the same time with smooth transitions.
  • Highly customizable API that's super simple — just add .popover.
  • Written in SwiftUI with full SwiftUI + UIKit support. No dependencies.

Showroom

Alert Color Menu Tip Standard
Alert Color Menu Tip Standard
Tutorial Picture-in-Picture Notification
Tutorial Picture in Picture Notification

Example

I wrote the example app with Swift Playgrounds 4, so you can run it right on your iPad. If you're using a Mac, download the Xcode version. Download for Swift Playgrounds 4Download for Xcode

Example app

Installation

Requires iOS 13+. Popovers can be installed through the Swift Package Manager (recommended) or Cocoapods.

Swift Package Manager
Add the Package URL:
Cocoapods
Add this to your Podfile:

https://github.com/aheze/Popovers

pod 'Popovers'

Usage

To present a popover in SwiftUI, use the .popover(present:attributes:view) modifier. By default, the popover uses the parent view as the source frame.

import SwiftUI
import Popovers

struct ContentView: View {
    @State var present = false
    
    var body: some View {
        Button("Present popover!") {
            present = true
        }
        .popover(present: $present) { /// here!
            Text("Hi, I'm a popover.")
                .padding()
                .foregroundColor(.white)
                .background(.blue)
                .cornerRadius(16)
        }
    }
}

In UIKit, create a Popover instance, then present with Popovers.present(_:). You should also set the source frame.

import SwiftUI
import Popovers

class ViewController: UIViewController {
    @IBOutlet weak var button: UIButton!
    @IBAction func buttonPressed(_ sender: Any) {
        var popover = Popover { PopoverView() }
        popover.attributes.sourceFrame = { [weak button] in
            button.windowFrame()
        }
        Popovers.present(popover) /// here!
    }
}

struct PopoverView: View {
    var body: some View {
        Text("Hi, I'm a popover.")
            .padding()
            .foregroundColor(.white)
            .background(.blue)
            .cornerRadius(16)
    }
}

Button 'Present popover!' with a popover underneath.


Customization

🔖 💠 🔲 🟩 🟥 🎾 🛑 🪟 👉 🎈 🔰

Customize popovers through the Attributes struct. Pretty much everything is customizable, including positioning, animations, and dismissal behavior.

SwiftUI
Configure in the attributes parameter.
UIKit
Modify the attributes property.

.popover(
    present: $present,
    attributes: {
        $0.position = .absolute(
            originAnchor: .bottom,
            popoverAnchor: .topLeft
        )
    }
) {
    Text("Hi, I'm a popover.")
}

var popover = Popover {
    Text("Hi, I'm a popover.")
}
popover.attributes.position = .absolute(
    originAnchor: .bottom,
    popoverAnchor: .topLeft
)
Popovers.present(popover)

🔖  Tag • String?

Tag popovers to access them later from anywhere. This is useful for updating existing popovers.

/// Set the tag.
$0.tag = "Your Tag"

/// Access it later.
let popover = Popovers.popovers(tagged: "Your Tag")

Note: When you use the .popover(selection:tag:attributes:view:) modifier, this tag is automatically set to what you provide in the parameter.

💠  Position • Position

The popover's position can either be .absolute (attached to a view) or .relative (picture-in-picture). The enum's associated value additionally configures which sides and corners are used.

  • Anchors represent sides and corners.
  • For .absolute, provide the origin anchor and popover anchor.
  • For .relative, provide the popover anchors. If there's multiple, the user will be able to drag between them like a PIP.
Anchor Reference .absolute(originAnchor: .bottom, popoverAnchor: .topLeft) .relative(popoverAnchors: [.right])

 Source Frame • (() -> CGRect)

This is the frame that the popover attaches to or is placed within, depending on its position. This must be in global window coordinates. Because frames are can change so often, this property is a closure. Whenever the device rotates or some other bounds change happens, the closure will be called.

SwiftUI
The source frame is automatically set to the parent view. You can still override it if you want.
UIKit
It's highly recommended to provide a source frame, otherwise the popover will appear in the top-left of the screen.

$0.sourceFrame = {
    /** some CGRect here */
}

 /// use `weak` to prevent a retain cycle
attributes.sourceFrame = { [weak button] in
    button.windowFrame()
}

🔲  Source Frame Inset • UIEdgeInsets

Edge insets to apply to the source frame. Positive values inset the frame, negative values expand it.

Absolute Relative
Source view has padding around it, so the popover is offset down. Source view is inset, so the popover is brought more towards the center of the screen.

 Screen Edge Padding • UIEdgeInsets

Global insets for all popovers to prevent them from overflowing off the screen. Kind of like a safe area. Default value is UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16).

🟩  Presentation • Presentation

This property stores the animation and transition that's applied when the popover appears.

/// Default values:
$0.presentation.animation = .default
$0.presentation.transition = .opacity

🟥  Dismissal • Dismissal

This property stores the popover's dismissal behavior. There's a couple sub-properties here.

/// Same thing as `Presentation`.
$0.dismissal.animation = .default
$0.dismissal.transition = .opacity

/// Advanced stuff! Here's their default values:
$0.dismissal.mode = .tapOutside
$0.dismissal.tapOutsideIncludesOtherPopovers = false
$0.dismissal.excludedFrames = { [] }
$0.dismissal.dragMovesPopoverOffScreen = true
$0.dismissal.dragDismissalProximity = CGFloat(0.25)

Mode: Configure how the popover should auto-dismiss. You can have multiple at the same time!

  • .tapOutside - dismiss the popover when the user taps outside it.
  • .dragDown - dismiss the popover when the user drags it down.
  • .dragUp - dismiss the popover when the user drags it up.
  • .none - don't automatically dismiss the popover.

Tap Outside Includes Other Popovers: Only applies when mode is .tapOutside. If this is enabled, the popover will be dismissed when the user taps outside, even when another presented popover is what's tapped. Normally when you tap another popover that's presented, the current one will not dismiss.

Excluded Frames: Only applies when mode is .tapOutside. When the user taps outside the popover, but the tap lands on one of these frames, the popover will stay presented. If you want multiple popovers, you should set the source frames of your other popovers as the excluded frames.

/// Set one popover's source frame as the other's excluded frame.
/// This prevents the the current popover from being dismissed before animating to the other one.

let popover1 = Popover { Text("Hello") }
popover1.attributes.sourceFrame = { [weak button1] in button1.windowFrame() }
popover1.attributes.dismissal.excludedFrames = { [weak button2] in [ button2.windowFrame() ] }

let popover2 = Popover { Text("Hello") }
popover2.attributes.sourceFrame = { [weak button2] in button2.windowFrame() }
popover2.attributes.dismissal.excludedFrames = { [weak button1] in [ button1.windowFrame() ] }

Drag Moves Popover Off Screen: Only applies when mode is .dragDown or .dragUp. If this is enabled, the popover will continue moving off the screen after the user drags.

Drag Dismissal Proximity: Only applies when mode is .dragDown or .dragUp. Represents the point on the screen that the drag must reach in order to auto-dismiss. This property is multiplied by the screen's height.

Diagram with the top 25% of the screen highlighted in blue.

🎾  Rubber Banding Mode • RubberBandingMode

Configures which axes the popover can "rubber-band" on when dragged. The default is [.xAxis, .yAxis].

  • .xAxis - enable rubber banding on the x-axis.
  • .yAxis - enable rubber banding on the y-axis.
  • .none - disable rubber banding.

🛑  Blocks Background Touches • Bool

Set this to true to prevent underlying views from being pressed.

Popover overlaid over some buttons. Tapping on the buttons has no effect.

🪟  Window Scene • UIWindowScene?v1.0.4

The window scene that the popover is tied to. By default, this is set to UIApplication.shared.keyWindow?.windowScene, which fully provides single window support and basic multi-window support on iPad. See Supporting Multiple Screens for more details.

👉  On Tap Outside • (() -> Void)?

A closure that's called whenever the user taps outside the popover.

🎈  On Dismiss • (() -> Void)?

A closure that's called when the popover is dismissed.

🔰  On Context Change • ((Context) -> Void)?

A closure that's called whenever the context changed. The context contains the popover's attributes, current frame, and other visible traits.


Utilities

🧩 🌃 📖 🏷 📄

Popovers comes with some features to make your life easier.

🧩  Animating Between Popovers

As long as the view structure is the same, you can smoothly transition from one popover to another.

SwiftUI
Use the .popover(selection:tag:attributes:view:) modifier.
UIKit
Get the existing popover using Popovers.popover(tagged:), then call Popovers.replace(_:with:).

struct ContentView: View {
    @State var selection: String?
    
    var body: some View {
        HStack {
            Button("Present First Popover") { selection = "1" }
            .popover(selection: $selection, tag: "1") {

                /// Will be presented when selection == "1".
                Text("Hi, I'm a popover.")
                    .background(.blue)
            }
            
            Button("Present Second Popover") { selection = "2" }
            .popover(selection: $selection, tag: "2") {

                /// Will be presented when selection == "2".
                Text("Hi, I'm a popover.")
                    .background(.green)
            }
        }
    }
}

@IBAction func button1Pressed(_ sender: Any) {
    var newPopover = Popover { Text("Hi, I'm a popover.").background(.blue) }
    newPopover.attributes.sourceFrame = { [weak button1] in button1.windowFrame() }
    newPopover.attributes.dismissal.excludedFrames = { [weak button2] in [button2.windowFrame()] }
    newPopover.attributes.tag = "Popover 1"
    
    if let oldPopover = Popovers.popover(tagged: "Popover 2") {
        Popovers.replace(oldPopover, with: newPopover)
    } else {
        Popovers.present(newPopover) /// Present if the old popover doesn't exist.
    }
}
@IBAction func button2Pressed(_ sender: Any) {
    var newPopover = Popover { Text("Hi, I'm a popover.").background(.green) }
    newPopover.attributes.sourceFrame = { [weak button2] in button2.windowFrame() }
    newPopover.attributes.dismissal.excludedFrames = { [weak button1] in [button1.windowFrame()] }
    newPopover.attributes.tag = "Popover 2"
    
    if let oldPopover = Popovers.popover(tagged: "Popover 1") {
        Popovers.replace(oldPopover, with: newPopover)
    } else {
        Popovers.present(newPopover)
    }
}
Smooth transition between popovers (from blue to green and back.

🌃  Background

You can put anything in a popover's background.

SwiftUI
Use the .popover(present:attributes:view:background:) modifier.
UIKit
Use the Popover(attributes:view:background:) initializer.

.popover(present: $present) {
    PopoverView()
} background: { /// here!
    Color.green.opacity(0.5)
}

var popover = Popover {
    PopoverView()
} background: { /// here!
    Color.green.opacity(0.5)
}

Green background over the entire screen, but underneath the popover

📖  Popover Reader

This reads the popover's context, which contains its frame, attributes, and various other properties. It's kind of like GeometryReader, but cooler. You can put it in the popover's view or its background.

.popover(present: $present) {
    PopoverView()
} background: {
    PopoverReader { context in
        Path {
            $0.move(to: context.frame.point(at: .bottom))
            $0.addLine(to: Popovers.windowBounds.point(at: .bottom))
        }
        .stroke(Color.blue, lineWidth: 4)
    }
}
Line connects the bottom of the popover with the bottom of the screen

🏷  Frame Tags

Popovers provides a mechanism for tagging and reading SwiftUI view frames. You can use this to provide a popover's sourceFrame or excludedFrames. As convenient as it is, don't use it for anything else, due to possible state issues.

Text("This is a view")
    .frameTag("Your Tag Name")

/// ...

.popover(
    present: $present,
    attributes: {
        $0.sourceFrame = Popovers.frameTagged("Your Tag Name")
    }
)

📄  Templates

Get started quickly with some templates. All of them are inside PopoverTemplates.swift with example usage in the example app.

  • AlertButtonStyle - a button style resembling a system alert.
  • VisualEffectView - lets you use UIKit blurs in SwiftUI.
  • ContainerShadow - a view modifier that applies a system-like shadow.
  • Container - a wrapper view for the BackgroundWithArrow shape.
  • BackgroundWithArrow - a shape with an arrow that looks like the system popover.
  • CurveConnector - an animatable shape with endpoints that you can set.
  • Menu - the system menu but built from scratch.
  • MenuButton - buttons to put in the Menu.

Notes

State Re-Rendering

If you directly pass a variable down to the popover's view, it might not update. Instead, move the view into its own struct and pass down a Binding.

Yes
The popover's view is in a separate struct, with $string passed down.
No
The button is directly inside the view parameter and receives string.

struct ContentView: View {
    @State var present = false
    @State var string = "Hello, I'm a popover."

    var body: some View {
        Button("Present popover!") { present = true }
        .popover(present: $present) {
            PopoverView(string: $string) /// Pass down a Binding ($).
        }
    }
}

/// Create a separate view to ensure that the button updates.
struct PopoverView: View {
    @Binding var string: String

    var body: some View {
        Button(string) { string = "The string changed." }
        .background(.mint)
        .cornerRadius(16)
    }
}

struct ContentView: View {
    @State var present = false
    @State var string = "Hello, I'm a popover."

    var body: some View {
        Button("Present popover!") {
            present = true
        }
        .popover(present: $present) {

            /// Directly passing down the variable (without $) is unsupported.
            /// The button might not update.
            Button(string) { 
                string = "The string changed."
            }
            .background(.mint)
            .cornerRadius(16)
        }
    }
}

Supporting Multiple Screens • v1.0.4

Popovers comes with built-in support for multiple screens (represented by UIWindowScene).

2 screens side by side with a popover in each.

Here's a couple things to keep in mind.

  • Popovers are tied to window scenes. This way, tapping on one side of the screen won't interfere or dismiss popovers on the other side.
  • Set a popover's window scene with attributes.windowScene. By default, this is UIApplication.shared.keyWindow?.windowScene, which is enough for basic multi-screen support.
  • Each screen will only show the popovers with the same windowScene.
  • If your app has multiple screens enabled (all SwiftUI apps by default), methods like Popovers.popover(tagged:) require specifying the window scene:
/// Get a currently-presented popover in the window scene.
Popovers.popover(tagged: "Your Tag Name", in: yourWindowScene)


/// Tag a frame (SwiftUI) in the window scene.
Text("Hello").frameTag("Your Frame Tag Name", in: yourWindowScene)

/// Get a tagged frame (SwiftUI) in the window scene.
Popovers.frameTagged("Your Frame Tag Name", in: yourWindowScene)

However, getting a view's window scene in SwiftUI is tricky. My current workaround is embedding a UIViewRepresentable and reading its window scene. This is not completely reliable — if anyone has a better method, please let me know.

/// Help me fix in https://github.com/aheze/Popovers/issues/3

WindowGroup {
    ContentView()
        .injectWindowScene() /// Make the window scene available to all subviews. Not ideal, but it works (usually).
}

struct ContentView: View {
    @EnvironmentObject var windowSceneModel: WindowSceneModel

    /// ... 

    Text("Hello").frameTag("Your Frame Tag Name", in: windowSceneModel.windowScene)
}

Popover Hierarchy

To bring a popover to front, just attach .zIndex(_:). A higher index will bring it forwards.

Popover Not Animating At First?

Make sure the library is set up by calling Popovers.prepare() when your app starts.

import SwiftUI
import Popovers

@main
struct YourApp: App {
    @Environment(\.scenePhase) private var scenePhase
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .onChange(of: scenePhase) { _ in
            Popovers.prepare() /// Make sure Popovers is ready.
        }
    }
}

Community

Author

Popovers is made by aheze.

Contributing

All contributions are welcome. Just fork the repo, then make a pull request.

Need Help?

Open an issue or join the Discord server. You can also ping me on Twitter. Or read the source code — there's lots of comments.

License

MIT License

Copyright (c) 2021 A. Zheng

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Comments
  • Present popover within current window

    Present popover within current window

    Howdy,

    This is a quick attempt to address the status bar issue I raised earlier, as well as resolve the issues around working out which window is needed for the current popover presentation. Instead of hosting the popover's container view controller in a second window, this PR presents it on the topmost view controller in the view hierarchy instead. This has meant:

    • The shared instance model types have had to go, with each window now referencing its own model. This removes the scope for layout bugs across windows, as only the model for each window is messed with by the popovers. As the window is now resolved by the library, I've removed any references to explicitly handing down a UIWindowScene to avoid confusion.
    • Access to this model is done by walking up the responder chain to a UIWindow, and accessing its popover model by way of an associated object. It's worthwhile mentioning attempting to access window-specific model attributes from a popover that has not been presented will raise an assertion. This should not occur to clients of the library, as the container view is now only inserted into the view hierarchy when it's view controller's view is inserted into the window, however may occur when changing the library itself.
    • Accessing tagged frames or popovers is now done using instance-based views and modifiers, in place of the static model. These modifiers use the same approach as above to find the appropriate model housing the tags for the current view hierarchy - meaning there's no chance of cross-window pollution. However, this means clients now need to use a FrameTagReader or PopoverReader to access these values. I've updated the README to compensate for this, but keep in mind this is quite a breaking change.

    Wanted to raise this now as there may be a bunch of things that need changing, so go nuts if you see something dodgy. There's a layout issue with the "Selection" demo in the sample app I've not had time to look at yet, though feel free to tackle it if it's a quick win.

    Fixes #2

    opened by ShezHsky 25
  • SwiftUI sheet sometimes fails to present

    SwiftUI sheet sometimes fails to present

    Sometimes when I try to present a sheet, nothing happens, and this is printed to the console:

    2022-01-05 09:37:44.755224-0800 PopoversPlaygroundsApp[83666:1047669] [Presentation] Attempt to present <TtGC7SwiftUI29PresentationHostingControllerVS_7AnyView: 0x129811c00> on <TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier_: 0x127e0e060> (from <TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVVS_22_VariadicView_Children7ElementVS_24NavigationColumnModifier_: 0x127e09d90>) which is already presenting <Popovers.PopoverContainerViewController: 0x12c70a5d0>.

    bug help wanted fixed 
    opened by aheze 14
  • Interaction exception!The root cause of the problem was not found?

    Interaction exception!The root cause of the problem was not found?

    What bothers me is that! Popovers packages in use and the system's navigationgview column in the first column of the folded page button and sheet and several views of the reaction will work will conflict, often making popovers work abnormally! Problem Description: 1, the user enters the rendered popovers UI pop-up window, click the folding page button, the second popovers will not work, continue to test to open the sheet window, multiple clicks are invalid, wait for several clicks, everything works normally # #

    need more details 
    opened by wisepmlin 10
  • Mac catalyst mode does not seem to be supported, and the following code fails in scrolling of Scrollview running on mac

    Mac catalyst mode does not seem to be supported, and the following code fails in scrolling of Scrollview running on mac

    GeometryReader {
        proxySubType in
        ItemSelectorView(title: selecedsubtype == -1 ? "选择子类" : products.projectsubtype,
                         animating: $showSubTypePicker,
                         isSeleced: $selecedsubtype)
            .onTapGesture {
                hideKeyboard()
                if products.projecttype.isEmpty {
                    self.present = true
                    msgBody = "请先选择-品类"
                    return
                }
                if !products.projecttype.isEmpty {
                    self.showSubTypePicker = true
                }
            }
            .popover(present: $showSubTypePicker,
                     attributes: {
                $0.rubberBandingMode = .none
            }) {
                SK_Picker(selection: $products.projectsubtype,
                          selectionNum: $selecedsubtype,
                          showPicker: $showSubTypePicker,
                          array: products.projecttype == "游戏" ? palyTagOptions : applyTagOptions,
                          title: "选择子类型")
                    .padding(CGFloat.bl_4)
                    .frame(width: proxySubType.size.width, height: proxySubType.size.width)
                    .background(.bar)
                    .cornerRadius(CGFloat.bl_4.double)
                    .shadow(color: Color.black_c.opacity(0.5), radius: 8, x: 0, y: 2)
                    .padding(.vertical, CGFloat.bl_4.half)
            }
    }
    
    bug wontfix 
    opened by wisepmlin 10
  • Status bar style is being overridden after a popover is displayed

    Status bar style is being overridden after a popover is displayed

    Howdy,

    While integrating this library into an app (thanks for the work on this by the way!), I noticed the status bar is disappearing when presenting sheets. It looks like the offender is around the use of a second UIWindow to draw the popovers, as this now receives priority for status bar styling preferences. This is evident when a popover is first shown as, especially from a sheet, the status bar returns to its default colouring once the second window is prepared - meaning it blends in with the background. Future presentations will then be stuck with the status bar styling from the second window, which usually means no status bar for sheets (except in dark mode).

    I've added a commit to my fork to demonstrate the issue inside the sample app, however as the sample app already prepared the secondary window the status bar is always hidden at presentation time. To make life easier, here's a quick video:

    https://user-images.githubusercontent.com/12624320/147573233-2896523f-bc9a-43d5-a37f-0a6ec0992e15.mov

    I had a quick 15 min stab at this but didn't find a quick win. My line of thinking was to remove the window when no more popovers are being displayed as a mitigation step, so the status bar is available for future sheet presentations/once the popover is dismissed. There might be a fancier way forward by rendering the popovers onto an overlayed view controller instead of a window, but that seems like a fairly large change (and may come with its own bugs!).

    While I'll give one of the above another go in due course (i.e. post holidays), I'm raising it here in case anyone else bumps into this issue/fancies giving it a go, or if there's other ideas to consider to address this. Cheers

    bug fixed 
    opened by ShezHsky 9
  • Code is for iOS only

    Code is for iOS only

    I thought this was a helpful fill-in for porting my macOS SwiftUI app to iOS, however I'm unable to include this SPM package in a project which builds for both macOS and iOS because of how SPM works. Specifying iOS in your Package.swift does not limit the build to to only iOS. Please let me know if you know a better way to accomplish this. Thanks

    opened by aehlke 7
  • Boolean values within Templates.Menu that sometimes work and sometimes don't

    Boolean values within Templates.Menu that sometimes work and sometimes don't

    VStask {
    ...
    }.overlay(
                ZStack(alignment: .bottom) {
                    Rectangle()
                        .fill(linearGradientColor)
                        .frame(height: 80)
                        .offset(y: 40)
                        .blur(radius: 30)
                    HStack(spacing: CGFloat.bl_4) {
                        Text("\(selectedProduct.pSubtype)\(selectedProduct.pType)")
                            .modifier(SK_10(textColor: selectedProduct.pBid == appSettings.selectedProductId ? Color.brandColor : Color.all_all_white, weight: .bold))
                            .padding(.vertical, CGFloat.bl_4)
                            .padding(.horizontal, CGFloat.bl_4)
                            .background(selectedProduct.pBid == appSettings.selectedProductId ? Color.all_all_white : Color.brandColor)
                            .cornerRadius(CGFloat.bl_4)
                            .lineLimit(1)
                        Spacer()
                        Templates.Menu {
                            Templates.MenuButton(title: selectedProduct.pTop ? "取消置顶" : "置顶", systemImage: selectedProduct.pTop ? "mappin.slash.circle" : "mappin.circle.fill") {
                                withAnimation(Animation.golden_ratio_animation_two) {
                                    toggleTop(topProduct: selectedProduct)
                                }
                                print("置顶工作了")
                            }
                            Templates.MenuButton(title: "编辑", systemImage: IMAGE_EDIT) {
                                shouldPresentEditProduct = true
                                print("编辑工作了")
                            }
                            Templates.MenuButton(title: "删除", systemImage: IMAGE_TRASH) {
                                shouldShowDeleteConfirmation = true
                                print("删除工作了")
                            }.modifier(SK_14(textColor: Color.red_c, weight: .regular))
                        } label: { fade in
                            menuButton(onOf: fade)
                        }
                    }.padding(CGFloat.bl_4.triple)
                }, alignment: .bottom)
    
    截屏2022-02-27 03 24 29

    By breakingpoint, parsing, the value inside the Templates.MenuButton closure is executed twice, using toggle(), the method back to close the component function!

    need more details 
    opened by wisepmlin 7
  • regular sheet animation disappeared when I add the Popover package via Swift Package Manager

    regular sheet animation disappeared when I add the Popover package via Swift Package Manager

    regular sheets (i.e. .sheet(isPresented: <Binding>, onDismiss: <(() -> Void)?(() -> Void)?() -> Void>, content: <() -> View>) ) I use across the app appearance stopped animating. Animation is restored when popover package is removed.

    bug fixed 
    opened by tm00-git 7
  • Possible memory leak

    Possible memory leak

    I presented a bunch of popovers by rapidly clicking each button. Here's the memory report:

    Screen Shot 2022-01-29 at 6 08 54 PM

    The memory usage just keeps going up! It seems like a new PopoverContainerView is created every time you present a popover.

    To test, I added this code inside PopoverContainerView:

    if #available(iOS 15.0, *) {
        let _ = Self._printChanges()
    }
    

    Screen Shot 2022-01-29 at 6 09 11 PM

    The result was PopoverContainerView: _popoverModel changed. being printed hundred of times.

    bug help wanted 
    opened by aheze 7
  • Using ForEach, the view on the interface shows the same two sets

    Using ForEach, the view on the interface shows the same two sets

    Using ForEach, the view on the interface shows the same two sets

    
    ForEach(self.dates) { date in
        VStack(alignment: .leading, spacing:0)
        {
            HStack(spacing: CGFloat.bl_4)
            {
                HStack(spacing: CGFloat.bl_4)
                {
                    Image(systemName: IMAGE_CALENDER)
                    DayView(date: date)
                }
                .modifier(SK_14(textColor: sk_current.isToday(date: date.date) ? Color.blue_c : Color.t_t_c, weight: sk_current.isToday(date: date.date) ? .bold : .regular))
                .frame(height: CGFloat.bl_4.custom(num: 8))
                .onTapGesture(perform: {
                    present = true
                })
                .onChange(of: sk_current.selectedDate, perform: {value in
                    present = false
                    self.pageIndex = sk_current.toThisDayIndex()
                })
                .popover(
                    present: $present,
                    attributes: {
                        $0.presentation.animation = .spring(
                            response: 0.6,
                            dampingFraction: 0.6,
                            blendDuration: 1
                        )
                        $0.presentation.transition = .opacity
                        $0.dismissal.animation = .easeIn(duration: 0.5)
                        $0.dismissal.transition = .move(edge: .bottom).combined(with: .opacity)
                    }
                ) {
                    VStack(spacing: CGFloat.bl_4.double) {
                        Text("📅 日历")
                            .modifier(SK_16(textColor: Color.t_p_c, weight: .bold))
                        DatePicker("",
                                   selection: $sk_current.selectedDate, displayedComponents: .date)
                            .datePickerStyle(GraphicalDatePickerStyle())
                            .accentColor(.blue_c)
                    }
                    .frame(maxWidth: 320)
                    .padding(.top, CGFloat.bl_4.custom(num: 5))
                    .padding(.horizontal, CGFloat.bl_4.custom(num: 5))
                    .background(.bar)
                    .cornerRadius(12)
                    .shadow(radius: 20)
                    .scaleEffect(expanding ? 0.95 : 1)
                 
                }
    
    great question 
    opened by wisepmlin 6
  • Multiple screens on iPadOS results in duplicate popovers

    Multiple screens on iPadOS results in duplicate popovers

    • This occurs when you have the same app opened side-by-side on iPadOS.
    • When you present a popover, it appears on the other side too at the same position.
    • This shouldn't happen and only one popover should present.
    • Both popovers appears in the same position.
    bug enhancement 
    opened by aheze 6
  • The following block of code, sometimes requires multiple clicks to execute the code inside the closure

    The following block of code, sometimes requires multiple clicks to execute the code inside the closure

    Templates.MenuButton(title: "删除", systemImage: IMAGE_TRASH) {
                        withAnimation(.golden_ratio_animation) {
                            deleteSelectedProduct(selectedProduct: product, user: user)
                        }
                    }
    
    opened by wisepmlin 1
  • onTapOutside called twice

    onTapOutside called twice

    I found a bug where onTapOutside is called twice.

    To test this, add the following log in DismissalView1 of the sample code and tap outside the Popover.

                    $0.onTapOutside = {
                        let log = print("onTapOutside")
                        withAnimation(.easeIn(duration: 0.15)) {
                            expanding = true
                        }
                        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                            withAnimation(.easeOut(duration: 0.4)) {
                                expanding = false
                            }
                        }
                    }
    
    opened by ahbou 0
  • Popover assumes the maxWidth or maxHeight value as the size of the popover, if it was defined with .frame(maxWidth, maxHeight)

    Popover assumes the maxWidth or maxHeight value as the size of the popover, if it was defined with .frame(maxWidth, maxHeight)

    I have a case where the popover is to be bound within a certain size.

    For example:

    Templates.Container(...
       VStack {
          Text()
       }
       .frame(maxWidth: 250)
    )
    
    1. In this case, regardless of whether the subviews of the VStack did not fill up the maxWidth space, the popover frame is define to be 250 in width (+attribute.padding)

    However if I do not define the maxWidth, it will be fine for the case of elements smaller than maxWidth, but it poses a problem for those with longer than maxWidth as it takes up as much space.

    1. I also have another case where a popover is scrollable, and this is in relation to this as well, that because the content in the stack can vary, it is likely to be wrapped in a ScrollView, but a scrollView defines the popover frame height to be a very long height (probably screenHeight - padding).
    Templates.Container(...
       VStack {
          ScrollView {
             VStack {
                Text()
             }
          }
       }
       .frame(maxWidth: 250)
    )
    

    Simulator Screen Shot - iPhone 14 Pro - 2022-12-09 at 10 42 42

    I could bound the height of the container, and that kinds of resolve the issue about scrollView, but the 1st issue is still of concern where the frame height still assumes the maxHeight of 250, regardless of the content.

    Templates.Container(...
    VStack {
          ScrollView {
             VStack {
                Text()
             }
          }
       }
       .frame(maxWidth: 250, maxHeight: 250)
    )
    
    opened by JohnKuan 0
  • SwiftUI Menu fails inside popover

    SwiftUI Menu fails inside popover

    Because of this project's change in January to remove the view controller container for popovers, Menu fails inside popovers with this error (for my project):

    2022-12-06 13:49:31.731374+0900 ManabiReader[10408:14471610] [Assert] Failed to find a presenting view controller for view (<_TtGC7SwiftUIP10$1132ad86417ViewBasedUIButtonGVS_23ResolvedButtonStyleBodyVS_P10$113252bfc25BorderlessButtonStyleBase__: 0x14354dc20; baseClass = UIButton; frame = (0 0; 136.5 20.5); opaque = NO; autoresize = W+H; gestureRecognizers = <NSArray: 0x600001288900>; layer = <CALayer: 0x600001cb6e00>>) in window (<UIWindow: 0x143807ed0; frame = (0 0; 834 1194); autoresize = W+H; gestureRecognizers = <NSArray: 0x6000012233f0>; layer = <UIWindowLayer: 0x600001223270>>). The interaction's view (or an ancestor) must have an associated view controller for presentation to work.
    

    I'd prefer to use the native SwiftUI Menu instead of Popovers for the dropdown menu, because it satisfies my needs, but at least I have a workaround available. I'm also worried about what other things might fail in the future that I might want to use from within the popover.

    edit: Native SwiftUI Alert also fails, but silently (it worked previously from within a native SwiftUI popover). Likewise with .sheet

    opened by aehlke 1
  • Popover arrow following the source view incorrectly due to screen limitations

    Popover arrow following the source view incorrectly due to screen limitations

    Hi @aheze,

    I am trying out this library and it has been amazing for what it provides on popover.

    However I have this issue with the logic of the arrow being drawn, considering the sourceRect of a small button and a larger popover view. I understand there are two other ArrowAlignment other than center which I have tried, but it assumes that the arrow to be placed in the opposite end of the container and does not follow the source correctly, as how the popover for iPad has been.

    Is it possible to consider another case for ArrowAlignment such that it adjusts the arrow position with respect to the sourceView?

    Screenshots below:

    Simulator Screen Shot - iPhone 14 Pro Max - 2022-12-02 at 11 35 04 Simulator Screen Shot - iPhone 14 Pro Max - 2022-12-02 at 11 35 14 Simulator Screen Shot - iPhone 14 Pro Max - 2022-12-02 at 11 35 18 Simulator Screen Shot - iPhone 14 Pro Max - 2022-12-02 at 12 00 17

    opened by JohnKuan 2
  • Popover showing in different position based on iOS

    Popover showing in different position based on iOS

    The popover is showing up correctly on my iPhone but on the iPad it shows up in the center of the view. Here is the code Im using the create the popover: ` .popover( present: $showPopoverHints, attributes: { $0.sourceFrameInset.top = -8 $0.position = .absolute( originAnchor: .top, popoverAnchor: .bottom ) $0.presentation.animation = .easeInOut $0.presentation.transition = .opacity $0.dismissal.animation = .easeInOut $0.dismissal.transition = .opacity $0.dismissal.mode = .tapOutside } ) { Templates.Container( arrowSide: .bottom(.mostCounterClockwise), backgroundColor: Color(themeManager.selectedTheme.tintColor) ) { VStack(alignment: .leading) { Text(LocalizedAppConstants.hintText) .genericTextModifier(for: .subheadline, weight: .black, color: .systemGray4) .smallTopPadding() .largeHorizontalPadding()

          if draftProgram.isLocked {
              Text(verbatim: LocalizedAppConstants.hintToUserUnlock)
                  .genericTextModifier(for: .headline, weight: .bold, color: .white)
                  .smallTopPadding()
                  .largeHorizontalPadding()
                  .largeBottomPadding()
                  .lineLimit(2)
                  .minimumScaleFactor(0.5)
          } else {
              Text(verbatim: LocalizedAppConstants.hintToUserAddHymns)
                  .genericTextModifier(for: .headline, weight: .bold, color: .white)
                  .smallTopPadding()
                  .largeHorizontalPadding()
                  .largeBottomPadding()
                  .lineLimit(2)
                  .minimumScaleFactor(0.5)
          }
      }
    

    } .onAppear { DispatchQueue.main.asyncAfter(deadline: .now() + Wait.aFewSeconds, execute: { showPopoverHints.toggle() }) } } ` iPhone IMG_3747

    iPad IMG_0304

    opened by tazmancoder 6
Releases(1.3.2)
  • 1.3.2(Apr 29, 2022)

    Finally fixed this really annoying bug. These 5 lines of code messed up dismissal transitions and animations. Since it's an extension, it even affected apps with Popovers installed even when you didn't import Popovers.

    extension Transaction: Equatable {
        public static func == (lhs: Transaction, rhs: Transaction) -> Bool {
            lhs.animation == rhs.animation
        }
    }
    

    Fixes #25, #27, and #33.

    Source code(tar.gz)
    Source code(zip)
  • 1.3.1(Apr 27, 2022)

    Just a minor update to fix this error:

    [Popovers] - No PopoverModel present in responder chain ((self)) - has the source view been installed into a window? Please file a bug report (https://github.com/aheze/Popovers/issues).

    I still wasn't able to get rid of this entirely, but for now I just made Popovers create a new PopoverModel.

    Source code(tar.gz)
    Source code(zip)
  • 1.3.0(Feb 6, 2022)

    Menus! It's like the system menu but more customizable, and supports iOS 13 too!

    • Replaced String with AnyHashable for more flexibility in Attributes.Tag.
    • Fixed a retain cycle in Popover.Context.
    • Fixed some system animations not working when .popover was attached to NavigationView.
    • This is a semi-breaking change — but just replace PopoverTemplates with Popovers.Templates.

    https://user-images.githubusercontent.com/49819455/152694222-4c74e846-92b9-475d-989f-34f3b17a9705.mp4

    Source code(tar.gz)
    Source code(zip)
  • 1.2.0(Jan 18, 2022)

    Some much-needed additions and fixes. This is a non-breaking change.

    • Presentation changes.
      • Popovers no longer places an intermediary view controller at the top of the screen.
      • Instead, popovers are directly embedded as subviews of the base UIWindow.
      • This minimizes interference with built-in presentation APIs.
    • Software should be accessible. Popovers now comes with built-in VoiceOver support!
      • A dismiss button can be automatically added when VoiceOver is on.
      • The button is fully customizable, along with other traits, via attributes.accessibility.
    • General improvements.
      • Frame recalculations after a screen rotation are now much smoother.
      • Popovers no longer steal focus from active controls like text fields.
      • Sheets and popovers can be presented at the same time.

    | Popovers 1 2 0 | | --- |

    Thanks again to @ShezHsky for the contributions!

    Source code(tar.gz)
    Source code(zip)
  • 1.1.0(Jan 5, 2022)

    Popovers now fully supports multiple windows and won't interfere with the status bar! This is a breaking change — for migrating, check out the guide. Thanks to @ShezHsky for the pull request.

    • No more overlaying container window — popovers are now presented directly on the topmost view controller.
    • No more static Popovers class — control the lifecycle with Popover instance methods.
    • Popover models and frame tags are now tied to specific windows and stored in the UIResponder chain.
    • New WindowReader view for easily reading the hosting window in SwiftUI.
    • If inside a popover view or background, use PopoverReader + context.window instead.
    • Set context.isDraggingEnabled to enable or disable dragging at any time.

    https://user-images.githubusercontent.com/49819455/148260067-7d88c863-4a53-4ea8-a001-649e21be38d8.mp4

    Source code(tar.gz)
    Source code(zip)
  • 1.0.4(Dec 28, 2021)

    Popovers now supports multiple screens side-by-side! This is a non-breaking change.

    https://user-images.githubusercontent.com/49819455/147528243-06a7ac31-8d1b-46d9-bd40-dc7134df3b25.mp4

    Source code(tar.gz)
    Source code(zip)
  • 1.0.3(Dec 25, 2021)

  • 1.0.1(Dec 25, 2021)

  • 1.0.0(Dec 25, 2021)

    Merry Christmas! This is the first release of Popovers.

    • Present any view above your app's main content.
    • Attach to source views or use picture-in-picture positioning.
    • Supports multiple popovers at the same time with smooth transitions.
    • Popovers are interactive and can be dragged to different positions.
    • Highly customizable API that's super simple — just add .popover.
    • Written in SwiftUI with full SwiftUI and UIKit support.
    Source code(tar.gz)
    Source code(zip)
Owner
Andrew Zheng
I like coding with Swift and watching anime. WWDC21 Scholar.
Andrew Zheng
An easier constructor for UIAlertController. Present an alert from anywhere.

ALRT An easier constructor for UIAlertController. Present an alert from anywhere like this. ALRT.create(.alert, title: "Alert?").addOK().addCancel().s

Masahiro Watanabe 97 Nov 11, 2022
Present a sheet ViewController easily and control ViewController height with pangesture

PanControllerHeight is designed to present a sheet ViewController easily and control ViewController height with pangesture.

null 2 May 3, 2022
LBBottomSheet gives you the ability to present a controller in a kind of

LBBottomSheet Installation Swift Package Manager To install using Swift Package Manager, in Xcode, go to File > Add Packages..., and use this URL to f

Lunabee Studio 48 Dec 9, 2022
Swift UI Kit to present clean modal/alert

CleanyModal is a good way to use UI-Customised alerts with ease Features Present some kind of clean alerts (With same API as UIAlertViewController) Ad

Lory Huz 487 Dec 2, 2022
An easier constructor for UIAlertController. Present an alert from anywhere.

ALRT An easier constructor for UIAlertController. Present an alert from anywhere like this. ALRT.create(.alert, title: "Alert?").addOK().addCancel().s

Masahiro Watanabe 97 Nov 11, 2022
It is a highly configurable iOS library which allows easy styling with built in styles as well as extra header and footer views so that you can make extremely unique alerts and action sheets.

 CFAlertViewController CFAlertViewController is a library that helps you display and customise Alerts, Action Sheets, and Notifications on iPad and i

Crowdfire Inc. 1.1k Dec 18, 2022
Highly configurable iOS Alert Views with custom content views

NYAlertViewController NYAlertViewController is a replacement for UIAlertController/UIAlertView with support for content views and UI customization. Fe

Nealon Young 609 Nov 20, 2022
Customizable Dynamic Bottom Sheet Library for iOS

DynamicBottomSheet Powerd by Witi Corp., Seoul, South Korea. Fully Customizable Dynamic Bottom Sheet Library for iOS. This library doesn't support sto

Witi Official 10 May 7, 2022
A customizable framework to create draggable views

CFNotify CFNotify is written in Swift. Using UIKit Dynamics as animator. It can make ANY UIView object draggable and throwable. This library mainly us

Johnny Tsoi 491 Nov 20, 2022
PMAlertController is a great and customizable alert that can substitute UIAlertController

PMAlertController is a small library that allows you to substitute Apple's uncustomizable UIAlertController, with a beautiful and totally customizable

Paolo Musolino 2.5k Jan 3, 2023
A simple, customizable popup dialog for iOS written in Swift. Replaces UIAlertController alert style.

Introduction Popup Dialog is a simple, customizable popup dialog written in Swift. Features Easy to use API with hardly any boilerplate code Convenien

Orderella Ltd. 3.8k Dec 20, 2022
Fully customizable and extensible action sheet controller written in Swift

XLActionController By XMARTLABS. XLActionController is an extensible library to quickly create any custom action sheet controller. Examples The action

xmartlabs 3.3k Dec 31, 2022
A customizable, full-feature, lightweight iOS framework to be used instead of UIAlertController.

A customizable, full-feature, lightweight iOS framework to be used instead of UIAlertController.

Ali Samaiee 11 Jun 6, 2022
💌 Easy to use and customizable messages/notifications for iOS à la Tweetbot

Notice: TSMessages is no longer being maintained/updated. We recommend everyone migrate to RMessage. This repository will be kept as is for those who

Felix Krause 4.9k Dec 31, 2022
[iOS] Easy, customizable notifications displayed on top of the statusbar. With progress and activity. iPhone X ready.

JDStatusBarNotification Show messages on top of the status bar. Customizable colors, font and animation. Supports progress display and can show an act

M Emrich 3.8k Dec 27, 2022
PMAlertController is a great and customizable alert that can substitute UIAlertController

PMAlertController is a small library that allows you to substitute Apple's uncustomizable UIAlertController, with a beautiful and totally customizable

Paolo Musolino 2.5k Jan 3, 2023
FCAlertView is a Flat Customizable AlertView for iOS (Written in Objective C)

FCAlertView FCAlertView is a Flat Customizable AlertView, written in Objective C Quick Links 1. Swift 2. Installation 3. Example App 4. Adding FCAlert

Nima Tahami 794 Nov 29, 2022
A customizable framework to create draggable views

CFNotify CFNotify is written in Swift. Using UIKit Dynamics as animator. It can make ANY UIView object draggable and throwable. This library mainly us

Johnny Tsoi 491 Nov 20, 2022
Customizable simple Alert and simple ActionSheet for Swift

SimpleAlert It is simple and easily customizable alert. Can be used as UIAlertController. Appetize's Demo Requirements Swift 5.0 iOS 9.0 or later How

Kyohei Ito 397 Dec 6, 2022