首页 > 解决方案 > SwiftUI:可以从任何视图触发的全局叠加层

问题描述

我对 SwiftUI 框架还很陌生,还没有完全理解它,所以请多多包涵。

当绑定发生变化时,有没有办法从“另一个视图”内部触发“覆盖视图”?见下图:

在此处输入图像描述

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

这是我到目前为止所得到的:

内容视图

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 中是如何工作的。

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

标签: swiftswiftui

解决方案


在 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渲染视图 - 因为我们可以 - 然后我们创建我们的 toast。presenting
  • toast 只是一个VStack带有Text,具有自定义框架大小,一些设计花哨(颜色和角半径)和默认slide过渡。

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

extension View {

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

}

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

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

}

我使用 aNavigationView来确保视图填满整个屏幕,以便Toast正确调整大小和位置。

withAnimation块确保Toast应用过渡。


它看起来如何

在此处输入图像描述

Toast使用 SwiftUI DSL 的强大功能很容易扩展。

Text物业可以很容易地成为一个@ViewBuilder封闭,以适应最奢侈的布局。


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

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 秒后隐藏吐司(根据要求)

.transition(.slide)在 toast之后附加此代码VStack

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

在 Xcode 11.1 上测试


推荐阅读