SwiftUI:可以从任何视图触发的全局覆盖 [英] SwiftUI: Global Overlay That Can Be Triggered From Any View

查看:45
本文介绍了SwiftUI:可以从任何视图触发的全局覆盖的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对 SwiftUI 框架很陌生,我还没有完全了解它,所以请耐心等待.

I'm quite new to the SwiftUI framework and I haven't wrapped my head around all of it yet so please bear with me.

当绑定更改时,有没有办法从另一个视图"内部触发覆盖视图"?见下图:

Is there a way to trigger an "overlay view" from inside "another view" when its binding changes? See illustration below:

我认为这个叠加视图"会包含我所有的视图.我还不确定如何做到这一点 - 也许使用 ZIndex.我也想当绑定更改时我需要某种回调,但我也不知道该怎么做.

I figure this "overlay view" would wrap all my views. I'm not sure how to do this yet - maybe using ZIndex. I also guess I'd need some sort of callback when the binding changes, but I'm also not sure how to do that either.

这是我目前得到的:

内容视图

struct ContentView : View {
    @State private var liked: Bool = false

    var body: some View {
        VStack {
            LikeButton(liked: $liked)
        }
    }
}

点赞按钮

struct LikeButton : View {
    @Binding var liked: Bool

    var body: some View {
        Button(action: { self.toggleLiked() }) {
            Image(systemName: liked ? "heart" : "heart.fill")
        }
    }

    private func toggleLiked() {
        self.liked = !self.liked
        // NEED SOME SORT OF TOAST CALLBACK HERE
    }
}

我觉得我的 LikeButton 中需要某种回调,但我不确定这在 Swift 中是如何工作的.

I feel like I need some sort of callback inside my LikeButton, but I'm not sure how this all works in Swift.

对此的任何帮助将不胜感激.提前致谢!

Any help with this would be appreciated. Thanks in advance!

推荐答案

在 SwiftUI 中构建toast"非常简单且有趣!

It's quite easy - and entertaining - to build a "toast" in SwiftUI!

开始吧!

struct Toast<Presenting>: View where Presenting: View {

    /// The binding that decides the appropriate drawing in the body.
    @Binding var isShowing: Bool
    /// The view that will be "presenting" this toast
    let presenting: () -> Presenting
    /// The text to show
    let text: Text

    var body: some View {

        GeometryReader { geometry in

            ZStack(alignment: .center) {

                self.presenting()
                    .blur(radius: self.isShowing ? 1 : 0)

                VStack {
                    self.text
                }
                .frame(width: geometry.size.width / 2,
                       height: geometry.size.height / 5)
                .background(Color.secondary.colorInvert())
                .foregroundColor(Color.primary)
                .cornerRadius(20)
                .transition(.slide)
                .opacity(self.isShowing ? 1 : 0)

            }

        }

    }

}

正文说明:

  • GeometryReader 为我们提供了 superview 的首选大小,从而为我们的 Toast 提供了完美的大小.
  • ZStack 将视图堆叠在一起.
  • 逻辑很简单:如果不应该看到 toast (isShowing == false),那么我们渲染 presenting 视图.如果必须呈现 toast (isShowing == true),那么我们会用一点点模糊渲染 presenting 视图 - 因为我们可以 - 然后我们创建我们的 toast下一个.
  • toast 只是一个带有 TextVStack、自定义框架大小、一些设计花哨的东西(颜色和圆角半径)和一个默认的 幻灯片过渡.
  • GeometryReader gives us the preferred size of the superview , thus allowing the perfect sizing for our Toast.
  • ZStack stacks views on top of each other.
  • The logic is trivial: if the toast is not supposed to be seen (isShowing == false), then we render the presenting view. If the toast has to be presented (isShowing == true), then we render the presenting view with a little bit of blur - because we can - and we create our toast next.
  • The toast is just a VStack with a Text, with custom frame sizing, some design bells and whistles (colors and corner radius), and a default slide transition.

我在 View 上添加了这个方法,使 Toast 创建更容易:

I added this method on View to make the Toast creation easier:

extension View {

    func toast(isShowing: Binding<Bool>, text: Text) -> some View {
        Toast(isShowing: isShowing,
              presenting: { self },
              text: text)
    }

}

还有一个关于如何使用它的小演示:

And a little demo on how to use it:

struct ContentView: View {

    @State var showToast: Bool = false

    var body: some View {
        NavigationView {
            List(0..<100) { item in
                Text("\(item)")
            }
            .navigationBarTitle(Text("A List"), displayMode: .large)
            .navigationBarItems(trailing: Button(action: {
                withAnimation {
                    self.showToast.toggle()
                }
            }){
                Text("Toggle toast")
            })
        }
        .toast(isShowing: $showToast, text: Text("Hello toast!"))
    }

}

我使用了 NavigationView 来确保视图填满整个屏幕,因此 Toast 的大小和位置都正确.

I used a NavigationView to make sure the view fills the entire screen, so the Toast is sized and positioned correctly.

withAnimation 块确保应用 Toast 过渡.

The withAnimation block ensures the Toast transition is applied.

外观:

利用 SwiftUI DSL 的强大功能可以轻松扩展 Toast.

It's easy to extend the Toast with the power of SwiftUI DSL.

Text 属性可以很容易地变成一个 @ViewBuilder 闭包,以适应最奢侈的布局.

The Text property can easily become a @ViewBuilder closure to accomodate the most extravagant of the layouts.

要将其添加到您的内容视图:

struct ContentView : View {
    @State private var liked: Bool = false

    var body: some View {
        VStack {
            LikeButton(liked: $liked)
        }
        // make it bigger by using "frame" or wrapping it in "NavigationView"
        .toast(isShowing: $liked, text: Text("Hello toast!"))
    }
}

<小时>

如何在 2 秒后隐藏 toast(根据要求):

在 toast VStack 中的 .transition(.slide) 之后附加这段代码.

Append this code after .transition(.slide) in the toast VStack.

.onAppear {
    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
      withAnimation {
        self.isShowing = false
      }
    }
}

在 Xcode 11.1 上测试

这篇关于SwiftUI:可以从任何视图触发的全局覆盖的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆