在 SwiftUI 中为 children 个 VStack 设置动画 showing/hiding

Animating showing/hiding of children a VStack in SwiftUI

** 编辑 ** 怀疑这个 SwiftUI 视图嵌入(通过 UIHostingController)的 UIKit 视图是问题所在,因为@eXcore 的解决方案在预览中对我有效。这是 UIKit 布局代码:

var searchBox: UIHostingController<WKMapDropdownView> = UIHostingController(rootView: WKMapDropdownView(activityTypes: []))


searchBox.view.backgroundColor = UIColor.clear
self.view.addSubview(searchBox.view)
    
searchBox.view.translatesAutoresizingMaskIntoConstraints = false
let constraints = [
    searchBox.view.topAnchor.constraint(equalTo: self.mapView!.topAnchor, constant: 30),
    searchBox.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 20),
    searchBox.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -20),
]
NSLayoutConstraint.activate(constraints)

========

我有一个 SwiftUI 视图,其中包含一个 VStack,其中一些 children 在第一个 child 视图被点击时 shown/hidden。

动画看起来有点奇怪,因为动画会导致 already-visible child 跳到中间然后扩展回顶部。我希望它发生,所以 already-visible child 保留在原位,隐藏的选项向下滑动。

我认为 children 的高度动画可能更好,或者 VStack 本身可能是问题的原因。

有人可以帮忙吗?

视图模型:

import Foundation

enum WKMapDropdownState {
    case optionsCollapsed
    case optionsExpanded
    case dropdownCollapsed
}

enum WKMapSearchType {
    case postcode
    case name
    case activityType
}

class WKMapDropdownViewModel : ObservableObject {
    
    @Published var optionsExpanded : Bool = false
    
    @Published var titleVisible : Bool = true
    
    @Published var backArrowVisible : Bool = false
    
    private var currentState : WKMapDropdownState = .optionsCollapsed
    
    func toggleDropdown() {
        switch (currentState) {
            case .optionsCollapsed:
                expandOptions()
            case .optionsExpanded:
                collapseOptions()
            case .dropdownCollapsed:
    //                self.delegate?.dropdownBackTapped() -> Hide everything
                expandDropdown()
        }
    }
    
    func searchTypeButtonTapped(searchType: WKMapSearchType) {
        collapseDropdown()
    }
    
    private func showDropdownOptions() {
        optionsExpanded = true
    }
    
    private func hideDropdownOptions() {
        optionsExpanded = false
    }
    
    private func showCollapsedOpenviewButton() {
        titleVisible = true
        backArrowVisible = true
    }
    
    private func showExpandedOpenviewButton() {
        titleVisible = true
        backArrowVisible = false
    }
    
    private func expandOptions() {
        showDropdownOptions()
        currentState = .optionsExpanded
    }
    
    private func collapseOptions() {
        hideDropdownOptions()
        currentState = .optionsCollapsed
    }
    
    private func expandDropdown() {
        hideDropdownOptions()
        showExpandedOpenviewButton()
        currentState = .optionsCollapsed
    }
    
    private func collapseDropdown() {
        hideDropdownOptions()
        showCollapsedOpenviewButton()
        currentState = .dropdownCollapsed
    }
}

查看:

struct WKMapDropdownView: View {
    @ObservedObject var viewModel = WKMapDropdownViewModel()
        var body: some View {
            VStack {
                HStack {
                    Button(action: {
                        withAnimation {
                            self.viewModel.toggleDropdown()
                        }
                    }) {
                        HStack {
                            Text("I want to search...")
                                .opacity(self.viewModel.titleVisible ? 1 : 0)
                                .foregroundColor(.white)
                                .font(.custom("Roboto-Regular", size: 19))
                                .frame(maxWidth: .infinity, alignment: .leading)
                            Image(self.viewModel.optionsExpanded ? "searchBoxUpArrow" : "searchBoxDownArrow")
                                .frame(maxWidth: .infinity, alignment: .trailing)
                                .animation(nil)
    
                                    
                        }
                        .frame(maxWidth: .infinity)
                        .padding(EdgeInsets(top: 19, leading: 22, bottom: 18, trailing: 25))
                    }
                        .background(Color.init("Tealish"))
                }
                .frame(maxHeight: .infinity, alignment: .top)
                
                
                if self.viewModel.optionsExpanded {
                    VStack {
                        Button(action: { self.viewModel.searchTypeButtonTapped(searchType: .postcode) }) {
                            Text("By Postcode")
                                .font(.custom("Roboto-Regular", size: 19))
                                .foregroundColor(Color.init("Tealish"))
                                .background(Color.clear)
                                .padding(EdgeInsets(top: 19, leading: 22, bottom: 18, trailing: 25))
                                .frame(maxWidth: .infinity, alignment: .leading)
                        }
                        .transition(.identity)
                            
                        Button(action: { self.viewModel.searchTypeButtonTapped(searchType: .name) }) {
                            Text("All Wiki Places")
                                .font(.custom("Roboto-Regular", size: 19))
                                .foregroundColor(Color.init("Tealish"))
                                .background(Color.clear)
                                .padding(EdgeInsets(top: 19, leading: 22, bottom: 18, trailing: 25))
                                .frame(maxWidth: .infinity, alignment: .leading)
                        }
                        .transition(.identity)
                            
                        Button(action: { self.viewModel.searchTypeButtonTapped(searchType: .activityType) }) {
                            Text("By Activity Type")
                                .font(.custom("Roboto-Regular", size: 19))
                                .foregroundColor(Color.init("Tealish"))
                                .background(Color.clear)
                                .padding(EdgeInsets(top: 19, leading: 22, bottom: 18, trailing: 25))
                                .frame(maxWidth: .infinity, alignment: .leading)
                        }
                        .transition(.identity)
                    }
                    .frame(maxHeight: .infinity, alignment: .top)
                }
            }
            .frame(maxHeight: .infinity, alignment: .top)
            .background(Color(UIColor.systemBackground))
            .clipShape(RoundedRectangle(cornerRadius:10))
        }
    }
    struct WKMapDropdownView_Previews: PreviewProvider {
        static var previews: some View {
            WKMapDropdownView()
        }
}

我无法发表评论,所以我假设我正在解决正确的问题。

首先,我不太明白为什么你的 VStack 中有 maxHeight: .infinity,删除它们可能是答案的一部分,之后我有这个:

接下来,如果您想将此下拉列表固定在顶部,我认为最简单的选择是将 Spacer() 放在下拉视图下方,如下所示:

下面是修复的概述:

struct WKMapDropdownView: View {
    @ObservedObject var viewModel = WKMapDropdownViewModel()
    var body: some View {
        VStack { // Wrapped into another VStack
            VStack {
                HStack {
                   ...
                }
                .frame(alignment: .top) // Removed maxHeight
                
                
                if self.viewModel.optionsExpanded {
                    VStack {
                        ...
                    }
                    .frame(alignment: .top) // Removed maxHeight
                }
            }
            .frame(alignment: .top) // Removed maxHeight
            .background(Color(UIColor.systemBackground))
            .clipShape(RoundedRectangle(cornerRadius:10))
            Spacer() // Added Spacer
        }
    }
}