Giter Site home page Giter Site logo

aheze / popovers Goto Github PK

View Code? Open in Web Editor NEW
1.8K 10.0 98.0 48.93 MB

A library to present popovers. Simple, modern, and highly customizable. Not boring!

License: MIT License

Swift 99.26% Ruby 0.74%
swift swiftui ios popover alert popup notification uiwindow snackbar

popovers's Introduction

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.
  • Display multiple popovers at the same time with smooth transitions.
  • Supports SwiftUI, UIKit, and multitasking windows on iPadOS.
  • Highly customizable API that's super simple — just add .popover.
  • Drop-in replacement for iOS 14's Menu that works on iOS 13.
  • SwiftUI-based core for a lightweight structure. 0 dependencies.
  • It's 2023 — about time that popovers got interesting!

Showroom

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

Example

Includes ~20 popover examples. Download

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 its 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 UIViewController.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()
        }
        
        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
)

present(popover)

🔖 Tag • AnyHashable?

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 = popover(tagged: "Your Tag") /// Where `self` is a `UIView` or `UIViewController`.

/// If inside a SwiftUI View, use a `WindowReader`:
WindowReader { window in
    let popover = window.popover(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 update happens, the closure will be called.

SwiftUI
By default, the source frame is automatically set to the parent view. Setting this will override it.
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 = .easeInOut
$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 = .easeInOut
$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.

👓 Accessibility • Accessibilityv1.2.0

Popovers is fully accessible! The Accessibility struct provides additional options for how VoiceOver should read out content.

/// Default values:
$0.accessibility.shiftFocus = true
$0.accessibility.dismissButtonLabel = defaultDismissButtonLabel /// An X icon wrapped in `AnyView?`

Shift Focus: If enabled, VoiceOver will focus the popover as soon as it's presented.

Dismiss Button Label: A button next to the popover that appears when VoiceOver is on. By default, this is an X circle.

VoiceOver highlights the popover, which has a X button next to id.

Tip: You can also use the accessibility escape gesture (a 2-fingered Z-shape swipe) to dismiss all popovers.

👉 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.

📘 Menus

New in v1.3.0! The template Menu looks and behaves pretty much exactly like the system menu, but also works on iOS 13. It's also extremely customizable with support for manual presentation and custom views.

The system menu and Popovers' custom menu, side by side
SwiftUI (Basic)
struct ContentView: View {
    var body: some View {
        Templates.Menu {
            Templates.MenuButton(title: "Button 1", systemImage: "1.circle.fill") { print("Button 1 pressed") }
            Templates.MenuButton(title: "Button 2", systemImage: "2.circle.fill") { print("Button 2 pressed") }
        } label: { fade in
            Text("Present Menu!")
                .opacity(fade ? 0.5 : 1)
        }
    }
}
SwiftUI (Customized)
Templates.Menu(
    configuration: {
        $0.width = 360
        $0.backgroundColor = .blue.opacity(0.2)
    }
) {
    Text("Hi, I'm a menu!")
        .padding()

    Templates.MenuDivider()

    Templates.MenuItem {
        print("Item tapped")
    } label: { fade in
        Color.clear.overlay(
            AsyncImage(url: URL(string: "https://getfind.app/image.png")) {
                $0.resizable().aspectRatio(contentMode: .fill)
            } placeholder: {
                Color.clear
            }
        )
        .frame(height: 180)
        .clipped()
        .opacity(fade ? 0.5 : 1)
    }

} label: { fade in
    Text("Present Menu!")
        .opacity(fade ? 0.5 : 1)
}
SwiftUI (Manual Presentation)
struct ContentView: View {
    @State var present = false
    var body: some View {
        VStack {
            Toggle("Activate", isOn: $present)
                .padding()
                .background(.regularMaterial)
                .cornerRadius(12)
                .padding()
            
            Templates.Menu(present: $present) {
                Templates.MenuButton(title: "Button 1", systemImage: "1.circle.fill") { print("Button 1 pressed") }
                Templates.MenuButton(title: "Button 2", systemImage: "2.circle.fill") { print("Button 2 pressed") }
            } label: { fade in
                Text("Present Menu!")
                    .opacity(fade ? 0.5 : 1)
            }
        }
    }
}
UIKit (Basic)
class ViewController: UIViewController {
    @IBOutlet var label: UILabel!

    lazy var menu = Templates.UIKitMenu(sourceView: label) {
        Templates.MenuButton(title: "Button 1", systemImage: "1.circle.fill") { print("Button 1 pressed") }
        Templates.MenuButton(title: "Button 2", systemImage: "2.circle.fill") { print("Button 2 pressed") }
    } fadeLabel: { [weak self] fade in
        self?.label.alpha = fade ? 0.5 : 1
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        _ = menu /// Create the menu.
    }
}
UIKit (Customized)
class ViewController: UIViewController {
    @IBOutlet var label: UILabel!

    lazy var menu = Templates.UIKitMenu(
        sourceView: label,
        configuration: {
            $0.width = 360
            $0.backgroundColor = .blue.opacity(0.2)
        }
    ) {
        Text("Hi, I'm a menu!")
            .padding()

        Templates.MenuDivider()

        Templates.MenuItem {
            print("Item tapped")
        } label: { fade in
            Color.clear.overlay(
                AsyncImage(url: URL(string: "https://getfind.app/image.png")) {
                    $0.resizable().aspectRatio(contentMode: .fill)
                } placeholder: {
                    Color.clear
                }
            )
            .frame(height: 180)
            .clipped()
            .opacity(fade ? 0.5 : 1)
        }
    } fadeLabel: { [weak self] fade in
        UIView.animate(withDuration: 0.15) {
            self?.label.alpha = fade ? 0.5 : 1
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        _ = menu /// Create the menu.
    }
}
UIKit (Manual Presentation)
class ViewController: UIViewController {
    /// ...

    @IBAction func switchPressed(_ sender: UISwitch) {
        if menu.isPresented {
            menu.dismiss()
        } else {
            menu.present()
        }
    }
}
Basic Customized Manual Presentation
Menu with 2 buttons Menu with image and divider Manually activate the menu with a toggle switch

🧩 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 UIResponder.popover(tagged:), then call UIResponder.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 = popover(tagged: "Popover 2") {
        replace(oldPopover, with: newPopover)
    } else {
        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 = popover(tagged: "Popover 1") {
        replace(oldPopover, with: newPopover)
    } else {
        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, window, 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: context.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 includes a mechanism for tagging and reading SwiftUI view frames. You can use this to provide a popover's sourceFrame or excludedFrames. Also works great when combined with PopoverReader, for connecting lines with anchor views.

Text("This is a view")
    .frameTag("Your Tag Name") /// Adds a tag inside the window.

/// ...

WindowReader { window in
    Text("Click me!")
    .popover(
        present: $present,
        attributes: {
            $0.sourceFrame = window.frameTagged("Your Tag Name") /// Retrieves a tag from the window.
        }
    )
}

📄 Templates

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

  • AlertButtonStyle - a button style resembling a system alert.
  • VisualEffectView - lets you use UIKit blurs in SwiftUI.
  • Container - a wrapper view for the BackgroundWithArrow shape.
  • Shadow - an easier way to apply shadows.
  • 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.

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.1.0

Popovers comes with built-in support for multiple screens, but retrieving frame tags requires a reference to the hosting window. You can get this via WindowReader or PopoverReader's context.

WindowReader { window in 

}

/// If inside a popover's `view` or `background`, use `PopoverReader` instead.
PopoverReader { context in
    let window = context.window
}

Popover Hierarchy

Manage a popover's z-axis level by attaching .zIndex(_:) to its view. A higher index will bring it forwards.

Community

Author Contributing Need Help?
Popovers is made by aheze. All contributions are welcome. Just fork the repo, then make a pull request. 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.

Apps Using Popovers

Find is an app that lets you find text in real life. Popovers is used for the quick tips and as a replacements for menus — download to check it out!

Find App

AnyTracker is an app that tracks numbers, text and prices on websites. It uses Popovers to display sleek dialogs with a nice background blur.

AnyTracker

 

Track Attack! is the ultimate vehicle enthusiast app for the road and track. Modern UI. Dual front & rear cameras. GPS & sensor data. RPM and throttle. Heart rate with Apple Watch. Lap timing for the track & multi-waypoint routing for the road. Easily export and share video with data overlays. Track Attack uses Popovers to display the onboarding tutorial and in-app notifications.

TrackAttack

 

If you have an app that uses Popovers, just make a PR or message me.

License

MIT License

Copyright (c) 2023 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.

Stats

popovers's People

Contributors

adonikian avatar aehlke avatar aheze avatar anivaros avatar au5ton avatar kheravarun08 avatar shervinkoushan avatar shezhsky avatar stbdang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

popovers's Issues

How do I show popover at start?

Trying to show a popover as soon as a view appears.

@State var showingPopover: Bool = false

var body: some View {
    Button {
        showingPopover.toggle()
    } label: {
        Image(systemName: "questionmark.circle")
            .font(.title)
            .foregroundColor(.primary)
    }
    .popover(present: $showingPopover) {
        PopoverView()
    }
    .onAppear() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            showingPopover = true
        }
    }
}

Setting showingPopover = true in onAppear without delay (asyncAfter) fails 100%

[Popovers] - No window was found when presenting popover. Please file a bug report (https://github.com/aheze/Popovers/issues).

I can kinda get it to work by adding delay but this isn't super reliable.

Any ideas?

Cannot auto alert when present init true

Use SwiftUI API can auto alert when page first loaded, but your code cannot.

SwiftUI API code:
@State private var showAlert = true
VStack(spacing: 0) {
Text("首次启动弹出弹窗")
}
.alert("title", isPresented: $showAlert, actions: {
Button("取消", role: .cancel, action: {})
}, message: {
Text("Message")
})

Your code:
@State private var showAlert = true
VStack(spacing: 0) {
Text("首次启动弹出弹窗")
}
.popover(present: $showAlert) {
Text("Message")
}

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!

Disappearance transitions fail in apps with Popovers installed

Extremely weird issue. This is my app without Popovers installed:

Without.Popovers.mov

This is my app with Popovers installed:

With.Popovers.mov

The dismissal animation does not work!

I didn't even import Popovers — all I did was add the package to my app. What could be the problem?

The popover show should not be light in Dark Mode

public static let system = Self(
color: Color(.label.withAlphaComponent(0.25)),
radius: 40,
x: 0,
y: 4
)

Hello. I'm try to intergrade a popover for custom SwiftUI control. I notice the shadow template is implement with .label color. This color works well in Light Mode. But for the Dark Mode the shadow color should still be black and we needs elevate the modal background color (from black to gray) to make popover looks like floating.

For example the iPad slide over window:

Custom fonts in menu items

I'm using https://github.com/kharrison/ScaledFont to handle custom fonts in my app, and it works really well. I have custom view modifier that applies the required text style, for example:

Text("Test Text").textStyle(BodyStyle())

This applies the text style that I've defined, which in this case is a custom font. It works in all items I tried, with the exception of this library, and I can't figure it out :( I'm assuming it's something to do with the Environment, I've tried adding the .environment(\.scaledFont, standardScaledFont) to the View, but no luck.

My menu is configured like this:

Templates.Menu {
    ForEach(menuItems) { menuItem in

        if menuItems.firstIndex(where: { $0.id == menuItem.id }) != 0 {
            MyDivider()
                .padding(.leading, defaultInnerPadding)
        }

        Templates.MenuItem {
            menuItem.clickCode()
        } label: { fade in
            HStack {
                Text(menuItem.text.localized())
                    .textStyle(CalloutStyle())
                Spacer()
                Image(systemName: menuItem.systemName)
                    .font(.callout)
            }
            .padding(defaultInnerPadding)
            .opacity(fade ? 0.5 : 1)
        }
    }
} label: { fade in
    buttonContent
        .opacity(fade ? 0.5 : 1)
}
.frame(width: 55, height: 55)

My Menu Item Struct is:

struct AppMenuItem: Identifiable {
    var text: String = .empty
    var systemName: String = .empty
    var clickCode: (() -> Void) = {}
    var id: Int {
        return text.hashValue
    }
}

The font size comes through correctly, just not the correct font.

Any ideas on why?

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)
        }
}

Get the current window scene from SwiftUI

If you want support multiple screens, and use frame tags or Popovers.popover(tagged:), you need to specify the window scene. The problem is, getting a view's window scene in SwiftUI is weird.

My current workaround (involves 0.5 second hardcoded delay):

/// Get the parent window from a view. Help needed! From https://stackoverflow.com/a/63276688/14351818
struct WindowSceneReader: UIViewRepresentable {
/// A closure that's called when the window is found.
var found: ((UIWindowScene?) -> Void)
func makeUIView(context: Context) -> UIView {
let view = UIView()
/**
The 0.5 second delay is needed to wait until the window is first initialized.
However, it's hardcoded. Does anyone know how to work around this?
*/
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak view] in
if let windowScene = view?.window?.windowScene {
found(windowScene)
}
}
return view
}
func updateUIView(_ uiView: UIView, context: Context) { }
}
/**
Read the window scene from a view and inject it as an environment object. iOS 14+ due to `@StateObject`.
*/
@available(iOS 14, *)
public struct WindowSceneInjectorModifier: ViewModifier {
/// This is only iOS 14+. Looking for help in making this available for iOS 13.
@StateObject var windowSceneModel = WindowSceneModel()
public func body(content: Content) -> some View {
content
/// Inject the window into the subview.
.environmentObject(windowSceneModel)
/// Read the window.
.background(
WindowSceneReader { scene in
windowSceneModel.windowScene = scene
}
)
}
}

You can then use this in your app's root view via injectWindowScene.

/**
Get a view's window and make it accessible to all subviews. iOS 14+ due to `@StateObject` - looking for help in making it available for iOS 13.
Only necessary if your app supports multiple windows.
*/
@available(iOS 14, *)
func injectWindowScene() -> some View {
return self.modifier(WindowSceneInjectorModifier())
}

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)
}

The hardcoded delay especially is annoying. Typing out @EnvironmentObject var windowSceneModel: WindowSceneModel every time you want to access the window scene is ok, but it's still kind of long.

Does anyone know how to get a view's window in SwiftUI? Preferably, something like this:

struct ContentView: View {
    var body: some View {
        Text("Hello")
            .frameTag("Your Frame Tag Name")
    }
}
public extension View {
    func frameTag(_ tag: String) -> some View {
        let windowScene = theWindowSceneThatHostsThisView /// Something like this? So no more need to specify the window scene manually.
        return self.modifier(FrameTagModifier(tag: tag, windowScene: windowScene))
    }
}

`onContextChange` may be called too early

I just realized: objectWillChange is called before the context changes. It would be nice if something likeobjectDidChange existed.

/// Notify when context changed.
public var changeSink: AnyCancellable?

public init() {
changeSink = objectWillChange.sink { [weak self] in
guard let self = self else { return }
self.attributes.onContextChange?(self)
}
}

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:

Popovers.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

How to dismiss popover when click source view

Button("Present popover!") {
   // how to make this 'toggle' working ???
    present.toggle()
}
.popover(present: $present) { 
    Text("Hi, I'm a popover.")
}

When popover is presented, If I click this button again, I want it to dismiss this popover.
But in fact, it first dismiss then present again.
So how to make this 'toggle' button working?

Clicking button to present popover gets "Attempt to present" error and the popover doesn't appear

Attempt to present <TtGC7SwiftUI29PresentationHostingControllerVS_7AnyView: 0x124db27e0> on <TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier_: 0x107013f20> (from <TtGC7SwiftUI41StyleContextSplitViewNavigationControllerVS_14NoStyleContext: 0x10683d600>) which is already presenting <sniffer_knife.PopoverContainerViewController: 0x116008170>.
The result of the above error:
When clicking on this error, the system can correctly feedback the Boolean value, but the view does not pop up and appear on the screen

Different margins on different iOS

Hi!
Firstly, thanks for such nice and shiny library! It is really beautiful
I want to present popover as full screen view with enjoyable animation. I have used the code below.
If I use the code on 16 it creates additional spaces at the top and bottom (safe areas)
safeAreaInsets were gotten from firstKeyWindow

 attributes: {
    $0.position = .relative(popoverAnchors: [.center])
    $0.screenEdgePadding = .init(
       top: safeAreaInsets.top,
       left: safeAreaInsets.leading,
       bottom: safeAreaInsets.bottom,
       right: safeAreaInsets.trailing
    )
    $0.sourceFrameInset = .zero
    
    $0.dismissal.dragMovesPopoverOffScreen = false
    $0.rubberBandingMode = .none
    $0.blocksBackgroundTouches = true
    $0.accessibility.shiftFocus = false
    $0.accessibility.dismissButtonLabel = nil
 }

ios15

ios16

The second test was the same, but with $0.screenEdgePadding = .zero
It looks almost fine, but there is a small little red gap at the top of it
ios15

ios16

It seems like a bug.
And I still have a question, is here a possibility to create a full screen popover?

Thanks for answers

Does 120kb count as lightweight?

Just wondering what the average size of libraries are and if Popovers counts as "lightweight." The code isn't very complicated, but I think PopoverTemplates bloated it up a bit.

Screen Shot 2022-01-05 at 10 17 13 AM

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>.

Popup stays on screen

I have a SwiftUI app that has a TabView. On the second tab I load contacts and present a popover when done loading contacts. When the popup is on screen if I tap any other tab I get the following error:

[Popovers] - No window was found when presenting popover. Please file a bug report (https://github.com/aheze/Popovers/issues).

The popover stays on the screen

Simulator Screen Shot - iPhone 13 Pro Max - 2022-04-20 at 12 59 18
.

Values bound by UserDefaults are not accepted, resulting in popover not working

@Published var userIsFirstLaunch: Bool {
    didSet {
        UserDefaults.standard.set(userIsFirstLaunch, forKey: "userIsFirstLaunch")
    }
}
.popover(
    present: $appSettings.appNotice,
    attributes: {
        $0.sourceFrameInset = UIEdgeInsets(16)
        $0.position = .relative(
            popoverAnchors: [
                .top,
            ]
        )
        $0.presentation.transition = .move(edge: .top)
        $0.dismissal.transition = .move(edge: .top)
        $0.dismissal.mode = [.dragUp]
        $0.dismissal.dragDismissalProximity = 0.1
    }
) {
    NotificationViewPopover(msgBody: $appSettings.appMsgBody)
        .onAppear {
            presentingUUID = UUID()
            let currentID = presentingUUID
            DispatchQueue.main.asyncAfter(deadline: .now() + notificationOfDwellTime) {
                if currentID == presentingUUID {
                    appSettings.appNotice = false
                }
            }
        }
}

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.

Show popup on launch

Thanks for getting back to me on how to get the arrow on the popover. I have one more question, how can I get the popover to show up right when the view loads. I have tried putting an @State property in the view and then in the .onAppear I set the view property to true, but that doesn't make the popover show up.

Is there a way to get the popover to show up on as soon as the view appears or a second after the view appears?

Thanks
Mark

Opening downloaded Xcode example project causes Xcode to hang

I've downloaded the example project in the readme (https://github.com/aheze/Popovers/raw/main/Examples/PopoversXcodeApp.zip) and opened it from where it was downloaded (~/Downloads), and it caused Xcode to hang for a long time.

It opened eventually, and I noticed that it loaded all my folders and files in ~/, which explains why it took so long. If I clone this repo and open the example project, it opens fine without any issue.

I think there might be some hard-coded relative paths in the example Xcode project?

image

Tiny UI flaw when using standard popover

Once again, great work. When I use the standard popover, I notice this very tiny UI flaw. It is essentially a small nick. It is only visible when using. .mostCounterClockwise and .mostClockwise

Is it somehow possible to fix this, either on your end or mine?
image

If interested, this is How I apply the code:


.popover(
    present: $present,
    attributes: {
        $0.sourceFrameInset.top = -8
        $0.position = .absolute(
            originAnchor: .top,
            popoverAnchor: .bottom
        )
    }
) {
    Templates.Container(
        arrowSide: .bottom(.mostClockwise),
        backgroundColor: .green
    ) {
        Text("This is a pretty standard-looking popover with an arrow.")
            .foregroundColor(.white)
    }
    .frame(maxWidth: 300)
}

Updating to v1.1.0 (Responder-Based Model)

Version 1.1.0 has some breaking changes, but they're easily resolvable.

Changes in UIKit presentation (where self is a view controller)

Action Older Versions Version 1.1.0
Presenting Popovers.present(popover) self.present(popover) or popover.present(in: window)
Replacing Popovers.replace(oldPopover, with: newPopover) self.replace(oldPopover, with: newPopover) or oldPopover.replace(with: newPopover)
Dismissing Popovers.dismiss(popover) self.dismiss(popover) or popover.dismiss()

Changes in SwiftUI frame tags

Frame tags now support windows automatically.

Action Older Versions Version 1.1.0
Saving frame tags

.frameTag("Your Tag", in: currentWindowScene)

.frameTag("Your Tag")

Retrieving frame tags
/// where `currentWindowScene` is the current window scene
let savedFrame = Popovers.frameTagged("Your Tag", in: currentWindowScene)

/// If outside a popover
WindowReader { window in
    let savedFrame = window.frameTagged("Your Tag")
}

/// If inside a popover's view or background
PopoverReader { context in
    let savedFrame = context.window.frameTagged("Your Tag")
}

Removed methods

  • Popovers.prepare is no longer needed and has been removed.

Removed properties

  • Popover.Attributes.windowScene is no longer needed and has been removed.

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 # #

Is there ability present popover with "item"

Hi! Awesome lib, thank you for sharing it!
One feature I missing:

Apple's popover implementation has ability to init popover with Item: Identifiable property instead of just bool isPresented.
It helps to parametrise popover's view.

Does Popovers has something like that?

Unnecessary touch behind a background view

Hi! I have founded a problem with using of background. In my very case, I want to close a view by clicking on a background view. But if behind of the touch is located a text field, it will open a keyboard. If button - it will trigger an action.
How to block touches behind?
Thank you!

         .popover(present: $present ), 
        attributes: {
            $0.position = .relative(popoverAnchors: [.center])
         }, view: {
           TestView()
         }, background: {
            Color.green
         })

Menu not working with ForEach

I have a dynamic number of items, and I want to show menu options for each. To simplify the case, consider the following code:

var body: some View {
  Templates.Menu() {
    ForEach(0..<3) { index in
      Templates.MenuButton(title: "Button \(index)") { print("Button \(index) pressed") }
    }
  }
  label: { fade in
    Text("Tap here")
  }
}

Only the first MenuButton will be clickable.

Popover captures focus on presentation

Presenting a popover causes it to capture focus, which for a lot of situations is desirable (e.g. when it contains other controls). However there are some use cases where a popover can provide supplementary information and should not take focus away. As an example for an app looking to use this library, validation of user input with constraints should explain why the input is rejected - a good use for a popover - but once the popover appears the user must manually tap on the control again to change their input.

Screen.Recording.2022-01-06.at.12.47.50.mov

Ideally the attributes for the popover should allow designating whether the popover should obtain focus when presented, or leave focus with the currently focused view (with the default being the former for compatibilities sake). This may require moving away from hosting the popover container view in a view controller, as its presentation will always mess up the currently focused view with it being a "modal" presentation.

I've pushed a commit to my fork for reproduction convenience

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.

iOS 16 issues

There seem to be some issues with UIViewRepresentables created multiple times in iOS 16 — see https://stackoverflow.com/questions/73016607/swiftui-button-label-rendered-twice.

Not sure when this started happening, but it seems like Button labels get duplicated now. Take this code:

struct ContentView: View {
    @State var active = false
    var body: some View {
        Button {
            active.toggle()
        } label: {
            Text("Toggle")
                .onChange(of: active) { newValue in
                    print("Active changed to: \(newValue)")
                }
        }
    }
}

It renders fine Button that says Toggle, but check the console:

Active changed to: true
Active changed to: true

Why is Active changed to: true printed twice?


This affects WindowReader — the window gets stored in one of the duplicate view copies sometimes, and might not be correct until you scroll, rotate the device, or do a bounds change.

I've filed feedback FB10758896 (SwiftUI - button label rendered twice)

No window was found when presenting popover.

I received the above message in the log when I attempted to present a popover. My app uses WindowGroup when setting up the root view.

Here is my popover code:

'''
.popover(present: $presentTip) {
Text("Testing")
}
'''

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)
             
            }

Popovers are overridden by the sheet

Let's look at the diagram first
IMG_332F0DCC118C-1

Popovers are overridden by the sheet, and the rendering is below the layer of the sheet
The popovers used are version 1.3.0

No Arrow

I really like the library. However, in the documentation in the ShowRoom you show a standard popover that has the arrow pointing down to something. I can't seem to figure out why an arrow is not showing on my popover, here is the code I'm using. Not sure if I'm missing something.

.popover(
    present: $promptAddContact,
    attributes: {
        $0.position = .absolute(
            originAnchor: .bottom,
            popoverAnchor: .topLeft
        )
        $0.presentation.animation = .spring()
        $0.presentation.transition = .move(edge: .top)
        $0.dismissal.animation = .spring(response: 2, dampingFraction: 0.8, blendDuration: 3)
        $0.dismissal.transition = .move(edge: .top)
    }
) {
    Text("Click here to add contacts")
        .smallPadding()
        .foregroundColor(.white)
        .background(.blue)
        .cornerRadius(10)
        .onAppear {
            DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                promptAddContact = false
            }
        }
}

Arrow extends over rounded corner

Hi,

after running the example app and trying out the standard showroom I've noticed the following:
Standard arrow

The arrow extends over the rounded corner. Looking at the code it is supposed to be fixed by using an "arrowSidePadding" but I guess something isn't working correctly?
I also see this issue when I integrate it into my app.

When changing the padding to 2.0 from 1.8 the issue is fixed. Maybe something has changed in the layout at some point?

Version: 1.3.2
Xcode 13.4
iOS 15.5

`onChange` modifier closure is not called when inside a popover

My code:

.popover(
    present: $present,
    attributes: {
        $0.rubberBandingMode = .none
        $0.sourceFrameInset.top = CGFloat.bl_4.custom(num: 5)
        $0.presentation.animation = .spring(
            response: 0.6,
            dampingFraction: 0.6,
            blendDuration: 1
        )
        $0.presentation.transition = .opacity
        $0.dismissal.animation = .easeIn(duration: 0.2)
    }
) {
    DatePicker(
        "",
        selection: $sk_current.selectedDate,
        in: selectedProduct.pSkdates.first!.dDate ... selectedProduct.pSkdates.last!.dDate,
        displayedComponents: .date
    )
    .datePickerStyle(GraphicalDatePickerStyle()
    )
    .accentColor(.blue_c)
    .padding(CGFloat.bl_4.double)
    .frame(width: 320)
    .background(.bar)
    .cornerRadius(CGFloat.bl_4.fourfold)
    .shadow(radius: 20)
    .onChange(of: sk_current.selectedDate, perform: {
        value in
        pageIndex = selectedInt(from: selectedProduct.pSkdates.first!.dDate, to: value)
    })
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.