我对SwiftUI框架还很陌生,我还没有完全掌握它,所以请多多包涵。
当绑定更改时,是否可以从“另一个视图”内部触发“叠加视图”?参见下图:
我认为此“重叠视图”将包裹我的所有视图。我不确定如何执行此操作-也许使用ZIndex
。我还猜想,当绑定更改时,我需要某种回调,但是我也不知道该怎么做。
这是到目前为止我得到的:
ContentView
struct ContentView : View {
@State private var liked: Bool = false
var body: some View {
VStack {
LikeButton(liked: $liked)
}
}
}
LikeButton
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中如何工作。
任何对此的帮助将不胜感激。预先感谢!
答案 0 :(得分:8)
在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
将视图彼此堆叠。isShowing == false
),则我们渲染presenting
视图。如果必须呈现烤面包(isShowing == true
),则我们将presenting
视图渲染得有些模糊-因为我们可以-然后我们接下来创建烤面包。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!"))
}
}
我使用NavigationView
来确保视图充满整个屏幕,因此Toast
的大小和位置都正确。
withAnimation
块确保应用了Toast
过渡。
外观:
借助SwiftUI DSL的功能扩展Toast
很容易。
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)
的烤面包VStack
中。
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation {
self.isShowing = false
}
}
}
答案 1 :(得分:7)
我在上面修改了Matteo Pacini的一个很好的答案,加入注释以使Toast淡入淡出并在延迟后淡出。我还修改了View扩展名,使其更加通用,并接受类似于.sheet工作方式的结尾闭包。
ContentView.swift:
struct ContentView: View {
@State private var lightsOn: Bool = false
@State private var showToast: Bool = false
var body: some View {
VStack {
Button(action: {
if (!self.showToast) {
self.lightsOn.toggle()
withAnimation {
self.showToast = true
}
}
}){
Text("switch")
} //Button
.padding(.top)
Image(systemName: self.lightsOn ? "lightbulb" : "lightbulb.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.padding(.all)
.toast(isPresented: self.$showToast) {
HStack {
Text("Lights: \(self.lightsOn ? "ON" : "OFF")")
Image(systemName: self.lightsOn ? "lightbulb" : "lightbulb.fill")
} //HStack
} //toast
} //VStack
} //body
} //ContentView
View + Toast.swift:
extension View {
func toast<Content>(isPresented: Binding<Bool>, content: @escaping () -> Content) -> some View where Content: View {
Toast(
isPresented: isPresented,
presenter: { self },
content: content
)
}
}
Toast.swift:
struct Toast<Presenting, Content>: View where Presenting: View, Content: View {
@Binding var isPresented: Bool
let presenter: () -> Presenting
let content: () -> Content
let delay: TimeInterval = 2
var body: some View {
if self.isPresented {
DispatchQueue.main.asyncAfter(deadline: .now() + self.delay) {
withAnimation {
self.isPresented = false
}
}
}
return GeometryReader { geometry in
ZStack(alignment: .bottom) {
self.presenter()
ZStack {
Capsule()
.fill(Color.gray)
self.content()
} //ZStack (inner)
.frame(width: geometry.size.width / 1.25, height: geometry.size.height / 10)
.opacity(self.isPresented ? 1 : 0)
} //ZStack (outer)
.padding(.bottom)
} //GeometryReader
} //body
} //Toast
使用此功能,您可以烘烤文本或图像(或同时显示两者,如下所示)或任何其他视图。
答案 2 :(得分:1)
如果您希望它在整个应用程序范围内,请放在整个应用程序范围内!例如,您可以将其添加到MyProjectApp.swift
文件中(或在UIKit / AppDelegate项目的sceneDelegate
文件中),如下所示:
请注意按钮和状态只是为了提供更多说明,您可以考虑以自己喜欢的方式更改它们
@main
struct SwiftUIAppPlaygroundApp: App { // <- Note that where we are!
@State var showToast = false
var body: some Scene {
WindowGroup {
Button("App-Wide Button") { showToast.toggle() }
ZStack {
ContentView() // <- The app flow
if showToast {
MyCustomToastView().ignoresSafeArea(.all, edges: .all) // <- App-wide overlays
}
}
}
}
}
看到了吗?现在,您可以在屏幕的任何位置添加任何视图,不阻止动画。只需将该@State转换为某种AppState,例如Observable
或Environment
并繁荣发展! ?你做到了!
答案 3 :(得分:0)
点击按钮时,使用.presentation()
显示警报。
在LikeButton
中:
@Binding var liked: Bool
var body: some View {
Button(action: {self.liked = !self.liked}, label: {
Image(systemName: liked ? "heart.fill" : "heart")
}).presentation($liked) { () -> Alert in
Alert.init(title: Text("Thanks for liking!"))
}
}
您还可以使用.presentation()
来呈现其他模态视图,例如Popover
或ActionSheet
。有关不同的.presentation()
选项的信息,请参见here和Apple的SwiftUI文档中该页面上的“另请参阅”部分。
编辑:使用Popover
的自定义视图的示例:
@State var liked = false
let popover = Popover(content: Text("Thanks for liking!").frame(width: 200, height: 100).background(Color.white), dismissHandler: {})
var body: some View {
Button(action: {self.liked = !self.liked}, label: {
Image(systemName: liked ? "heart.fill" : "heart")
}).presentation(liked ? popover : nil)
}
答案 4 :(得分:0)
我正在使用以下开源:https://github.com/huynguyencong/ToastSwiftUI。使用非常简单。
struct ContentView: View {
@State private var isShowingToast = false
var body: some View {
VStack(spacing: 20) {
Button("Show toast") {
self.isShowingToast = true
}
Spacer()
}
.padding()
// Just add a modifier to show a toast, with binding variable to control
.toast(isPresenting: $isShowingToast, dismissType: .after(3)) {
ToastView(message: "Hello world!", icon: .info)
}
}
}