SwiftUI自定义Hide修饰符仅首次生效问题排查
问题分析与解决方案
你的问题核心在于:第一次触发隐藏动画后,Text视图并未从视图层级中移除(因为message始终是非空状态),再次点击按钮时,由于message值未发生变化(或变化未触发视图重建),Hide修饰符的onAppear不会再次执行,isVisible状态停留在false,导致后续无法正常显示。
解决方案1:添加动态ID强制重建视图
在调用hide修饰符的Text上绑定一个动态id,确保每次需要显示时,视图都会被重新创建,从而重置Hide修饰符的内部状态:
struct LoginScreenContainer: View { @State private var message: String = "" @State private var showTrigger = 0 // 新增触发计数 var body: some View { NavigationStack { Button("Show Message") { showTrigger += 1 // 每次点击更新计数 message = "Error Message" } } .overlay(alignment: .bottom) { if !message.isEmpty { Text("ERROR") .id(showTrigger) // 绑定触发计数作为唯一标识 .hide(in: 2.0) } } } }
解决方案2:优化Hide修饰符的生命周期逻辑
将onAppear替换为task,利用SwiftUI的任务生命周期特性,在视图重建或重新出现时自动重置状态:
struct Hide: ViewModifier { let delay: Double @State private var isVisible = true func body(content: Content) -> some View { content .opacity(isVisible ? 1 : 0) .task { // 任务启动时强制重置可见性 isVisible = true // 延迟指定时长 try? await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000)) // 执行隐藏动画 withAnimation { isVisible = false } } } } extension View { func hide(in delay: Double) -> some View { modifier(Hide(delay: delay)) } }
配合方案1的showTrigger使用,即可实现每次点击都触发完整的显示-隐藏流程。
进阶优化:隐藏完成后自动清空消息
如果希望在隐藏动画结束后自动移除Text视图,可以给Hide修饰符添加隐藏完成回调:
struct Hide: ViewModifier { let delay: Double let onHidden: (() -> Void)? // 新增隐藏完成回调 @State private var isVisible = true func body(content: Content) -> some View { content .opacity(isVisible ? 1 : 0) .task { isVisible = true try? await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000)) withAnimation { isVisible = false } onHidden?() // 触发回调 } } } extension View { func hide(in delay: Double, onHidden: (() -> Void)? = nil) -> some View { modifier(Hide(delay: delay, onHidden: onHidden)) } }
使用时:
Text("ERROR") .id(showTrigger) .hide(in: 2.0) { message = "" // 隐藏完成后清空消息,移除视图 }
内容的提问来源于stack exchange,提问作者john doe
相关产品推荐
相关产品推荐

