我正在尝试在SwiftUI中隐藏和取消隐藏视图,但这似乎很困难。隐藏和取消隐藏视图时,我无法保持状态的连续性。
struct CounterView: View {
var title: String
var delay = 0.5
@State var ticker: Int = 0
@State var visible = IsVisible()
class IsVisible {
var bool: Bool = false
}
var body: some View {
Text("\(title) \(ticker)")
.onAppear {
print("\(title) onAppear")
if !self.visible.bool {
self.visible.bool = true
startCounter()
}
}
.onDisappear {
print("\(title) onDisappear")
self.visible.bool = false
}
}
func startCounter() {
self.ticker += 1
print(self.ticker)
if self.visible.bool {
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
self.startCounter()
}
}
}
}
struct ContentView: View {
@State var hide = false
var body: some View {
VStack {
VStack {
Button {
self.hide.toggle()
} label: {
Text(hide ? "Show" : "Hide")
}
myView()
}
}
}
@ViewBuilder
func myView() -> some View {
let view = CounterView(title: "Test", delay: 0.1)
if hide {
view.hidden()
} else {
view
}
}
}
复制步骤
1. Open app
2. Tap hide
3. Tap show
预期结果
Counter continues from last number When the view is hidden it gets no accidental onAppear callbacks
实际结果
Counter restarts from 0
When hiding the view a new view is created and it gets an onAppear callback
想要的解决方案
理想情况下,我要么会做
let view = CounterView(title: "Test", delay: 0.1)
if hide {
view.hidden()
} else {
view
}
甚至更好
CounterView(title: "Test", delay: 0.1).hide(true)
答案 0 :(得分:0)
为了解决此问题,我们需要以某种方式维护状态,并能够执行诸如view.hide(true)之类的操作而无需创建其他视图树。通过将视图包装在UIKit视图中,我们可以实现以下目标:
Original view structure:
- MyView
New view structure:
- HideableView (SwiftUI)
-> HideableView.Container (UIView)
-> UIHostingController (UIView)
-> MyView (SwiftUI)
然后我们可以完全控制视图的生命周期,因为我们可以通过这种方式在其超级视图(HideableView.Container)中添加和删除UIHostingController。
extension View {
func hide(_ hide: Bool) -> some View {
HideableView(isHidden: .constant(hide), view: self)
}
func hide(_ isHidden: Binding<Bool>) -> some View {
HideableView(isHidden: isHidden, view: self)
}
}
struct HideableView<Content: View>: UIViewRepresentable {
@Binding var isHidden: Bool
var view: Content
func makeUIView(context: Context) -> ViewContainer<Content> {
return ViewContainer(isContentHidden: isHidden, child: view)
}
func updateUIView(_ container: ViewContainer<Content>, context: Context) {
container.child.rootView = view
container.isContentHidden = isHidden
}
class ViewContainer<Content: View>: UIView {
var child: UIHostingController<Content>
var didShow = false
var isContentHidden: Bool {
didSet {
addOrRemove()
}
}
init(isContentHidden: Bool, child: Content) {
self.child = UIHostingController(rootView: child)
self.isContentHidden = isContentHidden
super.init(frame: .zero)
addOrRemove()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
child.view.frame = bounds
}
func addOrRemove() {
if isContentHidden && child.view.superview != nil {
child.view.removeFromSuperview()
}
if !isContentHidden && child.view.superview == nil {
if !didShow {
DispatchQueue.main.async {
if !self.isContentHidden {
self.addSubview(self.child.view)
self.didShow = true
}
}
} else {
addSubview(child.view)
}
}
}
}
}
那么我们可以简单地做到:
CounterView(title: "Test", delay: 0.1).hide(true)
或更明确地说:
HideableView(isHidden: .constant(true), view: CounterView(title: "Test", delay: 0.1))