SwiftUI:可以从任何视图触发的全局覆盖 [英] SwiftUI: Global Overlay That Can Be Triggered From Any View
问题描述
我对 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 只是一个带有
Text
的VStack
、自定义框架大小、一些设计花哨的东西(颜色和圆角半径)和一个默认的幻灯片
过渡.
GeometryReader
gives us the preferred size of the superview , thus allowing the perfect sizing for ourToast
.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 thepresenting
view. If the toast has to be presented (isShowing == true
), then we render thepresenting
view with a little bit of blur - because we can - and we create our toast next. - The toast is just a
VStack
with aText
, with custom frame sizing, some design bells and whistles (colors and corner radius), and a defaultslide
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屋!